├── .gitignore
├── .idea
├── dictionaries
│ └── pengwei08.xml
├── libraries
│ ├── fastjson_1_2_58.xml
│ └── jsoup_1_12_1.xml
├── misc.xml
├── modules.xml
├── uiDesigner.xml
└── vcs.xml
├── AndroidSourceViewer.iml
├── README.md
├── downloads
├── AndroidSourceViewer-1.0.2.jar
└── AndroidSourceViewer-1.2.1.zip
├── libs
├── fastjson-1.2.58.jar
└── jsoup-1.12.1.jar
├── pack.py
├── resources
├── META-INF
│ └── plugin.xml
└── icons
│ ├── android.png
│ ├── android@2x.png
│ ├── class.png
│ ├── class.svg
│ ├── class@2x.png
│ ├── class@2x.svg
│ ├── diff.png
│ ├── diff@2x.png
│ ├── diff@2x_dark.png
│ ├── diff_dark.png
│ ├── findPlain.png
│ ├── findPlain@2x.png
│ ├── java.png
│ ├── java.svg
│ ├── java@2x.png
│ └── java@2x.svg
├── screenshot
├── AndroidSourceViewer讨论群群二维码.png
├── ss1.png
├── ss2.png
├── ss3.png
├── ss4.gif
└── ss5.gif
└── src
└── com
└── apkfuns
└── androidsourceviewer
├── action
├── AndroidDeveloperAction.java
├── CodeSearchAction.java
├── DiffSourceAction.java
├── FindNativeMethodAction.java
├── HelpAction.java
├── SourceViewerAction.java
├── base
│ ├── BaseAction.java
│ └── BaseSourceAction.java
└── view
│ ├── BaseDialog.java
│ ├── SettingDialog.form
│ └── SettingDialog.java
├── app
└── Constant.java
├── download
├── AndroidOSDownload.java
├── AndroidOSSearch.java
├── XrefDownload.java
├── XrefSearch.java
└── inteface
│ ├── FileDownload.java
│ └── IDownload.java
├── entity
├── CPPDescriptor.java
├── ClassDescriptor.java
├── ClassFile.java
├── DownloadTask.java
└── ListDoubleClickEvent.java
├── icons
└── PluginIcons.java
├── provider
└── NativeMethodProvider.java
├── util
├── DownloadManager.java
├── DownloadResult.java
├── FileUtil.java
├── GlobalStorageUtil.java
├── HttpUtil.java
├── Log.java
├── NotificationUtils.java
├── ThreadPoolManager.java
└── Utils.java
└── widget
├── LineLayout.java
└── PopListView.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 | .idea/gradle_extensions.xml
4 | .idea/libraries/Gradle__*.xml
5 | .idea/shelf
6 | .idea/usage.statistics.xml
7 | .idea/workspace.xml
8 | build/dependencies/.gradle
9 | build/dependencies/build
10 |
11 | # Log file
12 | *.log
13 | build/
14 | out/
15 |
16 | # BlueJ files
17 | *.ctxt
18 |
19 | # Mobile Tools for Java (J2ME)
20 | .mtj.tmp/
21 |
22 | # Package Files #
23 | *.war
24 | *.ear
25 | *.tar.gz
26 | *.rar
27 | .DS_Store
28 | .gradle/
29 | out/
30 |
31 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
32 | hs_err_pid*
33 |
--------------------------------------------------------------------------------
/.idea/dictionaries/pengwei08.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | androidos
5 | androidsourceviewer
6 | apkfuns
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/fastjson_1_2_58.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/jsoup_1_12_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/AndroidSourceViewer.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidSourceViewer
2 |
3 | 
4 |
5 | Android Studio 在线查看 Android 和 Java 指定版本源码插件
6 |
7 | ## Features
8 | * 支持查看 Android / Java 任意版本源码
9 | * 支持对比 Android / Java 任意两个版本源码差异
10 | * 支持 Android 官网文档查看和方法定位
11 | * 支持 Native 方法源码查看
12 |
13 | ## Screenshot
14 | 
15 | 
16 | 
17 | 
18 | 
19 |
20 | ## Download
21 | * Android Studio / Intellij IDEA 插件中心搜索 `AndroidSourceViewer`
22 | * 下载[AndroidSourceViewer.zip](./downloads)
23 |
24 | ## Improve
25 | * 不支持查找变量对象是内部类
26 | * 本地源码不支持方法的定位
27 | * 还有很多其他尚未发现的缺陷和不足,欢迎提交 issue 和 PR
28 |
29 | ## Recommend
30 | ```
31 | Google 搜索 site:android.googlesource.com <关键词>
32 | ```
33 |
34 | ## QQ群
35 | 
36 |
37 | ## Thanks
38 | * Android 源码来自 [http://androidxref.com](http://androidxref.com)、[https://www.androidos.net.cn/](https://www.androidos.net.cn/)
39 |
40 | ## License
41 |
42 | Licensed under the Apache License, Version 2.0 (the "License");
43 | you may not use this file except in compliance with the License.
44 | You may obtain a copy of the License at
45 |
46 | http://www.apache.org/licenses/LICENSE-2.0
47 |
48 | Unless required by applicable law or agreed to in writing, software
49 | distributed under the License is distributed on an "AS IS" BASIS,
50 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
51 | See the License for the specific language governing permissions and
52 | limitations under the License.
53 |
54 |
--------------------------------------------------------------------------------
/downloads/AndroidSourceViewer-1.0.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/downloads/AndroidSourceViewer-1.0.2.jar
--------------------------------------------------------------------------------
/downloads/AndroidSourceViewer-1.2.1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/downloads/AndroidSourceViewer-1.2.1.zip
--------------------------------------------------------------------------------
/libs/fastjson-1.2.58.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/libs/fastjson-1.2.58.jar
--------------------------------------------------------------------------------
/libs/jsoup-1.12.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/libs/jsoup-1.12.1.jar
--------------------------------------------------------------------------------
/pack.py:
--------------------------------------------------------------------------------
1 | import re
2 | import os
3 |
4 | text = open('./resources/META-INF/plugin.xml').read()
5 | pattern = re.compile(r"(.*) ")
6 | version = ""
7 | for m in pattern.finditer(text):
8 | version = m.group(1)
9 | print(version)
10 | path = "./downloads"
11 | if not os.path.exists(path):
12 | os.makedirs(path)
13 | os.rename('./AndroidSourceViewer.zip', "%s/AndroidSourceViewer-%s.zip"%(path, version))
14 |
15 |
--------------------------------------------------------------------------------
/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.apkfuns.androidsourceviewer
3 | AndroidSourceViewer
4 | 1.2.1
5 | 舞影凌风
6 |
7 |
9 |
10 | ]]>
11 |
12 |
14 |
15 | 1.2.0
16 | support native Source
17 |
18 |
19 | 1.0.2
20 | support android.googlesource.com
21 |
22 |
23 | 1.0
24 | 1.View Android Source
25 | 2.Diff Android Source
26 | 3.Android Developer Reference
27 |
28 |
29 | ]]>
30 |
31 |
32 |
33 |
34 |
35 |
38 |
39 |
40 | com.intellij.modules.java
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
59 |
60 |
61 |
62 |
65 |
66 |
67 |
68 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/resources/icons/android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/android.png
--------------------------------------------------------------------------------
/resources/icons/android@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/android@2x.png
--------------------------------------------------------------------------------
/resources/icons/class.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/class.png
--------------------------------------------------------------------------------
/resources/icons/class.svg:
--------------------------------------------------------------------------------
1 |
2 | class
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/resources/icons/class@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/class@2x.png
--------------------------------------------------------------------------------
/resources/icons/class@2x.svg:
--------------------------------------------------------------------------------
1 |
2 | class@2x
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/resources/icons/diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/diff.png
--------------------------------------------------------------------------------
/resources/icons/diff@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/diff@2x.png
--------------------------------------------------------------------------------
/resources/icons/diff@2x_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/diff@2x_dark.png
--------------------------------------------------------------------------------
/resources/icons/diff_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/diff_dark.png
--------------------------------------------------------------------------------
/resources/icons/findPlain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/findPlain.png
--------------------------------------------------------------------------------
/resources/icons/findPlain@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/findPlain@2x.png
--------------------------------------------------------------------------------
/resources/icons/java.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/java.png
--------------------------------------------------------------------------------
/resources/icons/java.svg:
--------------------------------------------------------------------------------
1 |
2 | java
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/resources/icons/java@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/resources/icons/java@2x.png
--------------------------------------------------------------------------------
/resources/icons/java@2x.svg:
--------------------------------------------------------------------------------
1 |
2 | java@2x
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/screenshot/AndroidSourceViewer讨论群群二维码.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/screenshot/AndroidSourceViewer讨论群群二维码.png
--------------------------------------------------------------------------------
/screenshot/ss1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/screenshot/ss1.png
--------------------------------------------------------------------------------
/screenshot/ss2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/screenshot/ss2.png
--------------------------------------------------------------------------------
/screenshot/ss3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/screenshot/ss3.png
--------------------------------------------------------------------------------
/screenshot/ss4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/screenshot/ss4.gif
--------------------------------------------------------------------------------
/screenshot/ss5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pengwei1024/AndroidSourceViewer/a3fe50d2af21a76823a2c78525f6ba95f3baa8ab/screenshot/ss5.gif
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/AndroidDeveloperAction.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action;
2 |
3 | import com.apkfuns.androidsourceviewer.action.base.BaseSourceAction;
4 | import com.apkfuns.androidsourceviewer.util.Log;
5 | import com.apkfuns.androidsourceviewer.util.NotificationUtils;
6 | import com.apkfuns.androidsourceviewer.util.Utils;
7 | import com.intellij.ide.BrowserUtil;
8 | import com.intellij.openapi.actionSystem.AnActionEvent;
9 | import com.intellij.psi.PsiClass;
10 | import com.intellij.psi.PsiElement;
11 | import com.intellij.psi.PsiMethod;
12 | import com.intellij.psi.PsiParameter;
13 | import org.jetbrains.annotations.Nullable;
14 |
15 | /**
16 | * 打开 Android 官方文档
17 | * https://developer.android.google.cn/reference/android/view/View.OnClickListener.html#onClick(android.view.View)
18 | * https://developer.android.google.cn/reference/android/app/Activity.html#onRestoreInstanceState(android.os.Bundle)
19 | */
20 | public class AndroidDeveloperAction extends BaseSourceAction {
21 | // 文档链接
22 | private static final String BASE_URL = "https://developer.android.google.cn/reference/";
23 |
24 | @Override
25 | protected void selectActionPerformed(AnActionEvent event, PsiElement element, String packageName) {
26 | if (!Utils.isAndroidClass(packageName)) {
27 | NotificationUtils.infoNotification("Must be Android class or Method");
28 | return;
29 | }
30 | String linkUrl = BASE_URL;
31 | if (element instanceof PsiMethod) {
32 | PsiMethod psiMethod = (PsiMethod) element;
33 | PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
34 | StringBuilder paramsBuilder = new StringBuilder("#" + psiMethod.getName());
35 | paramsBuilder.append("(");
36 | for (int i = 0; i < parameters.length; i++) {
37 | PsiParameter parameter = parameters[i];
38 | paramsBuilder.append(parameter.getType().getCanonicalText());
39 | if (i < parameters.length - 1) {
40 | paramsBuilder.append(",%20");
41 | }
42 | }
43 | paramsBuilder.append(")");
44 | linkUrl += getRealPackage(psiMethod.getContainingClass()) + paramsBuilder.toString();
45 | } else if (element instanceof PsiClass) {
46 | PsiClass psiClass = (PsiClass) element;
47 | linkUrl += getRealPackage(psiClass);
48 | }
49 | Log.debug("linkUrl= " + linkUrl);
50 | BrowserUtil.open(linkUrl);
51 | }
52 |
53 | @Override
54 | protected boolean shouldSelectVersion() {
55 | return false;
56 | }
57 |
58 | /**
59 | * 替换成 Developer 需要的包名
60 | * 如 android.view.View.OnClickListener 替换成 android/view/View.OnClickListener.html
61 | * @param psiClass PsiClass
62 | * @return class path
63 | */
64 | private String getRealPackage(@Nullable PsiClass psiClass) {
65 | if (psiClass == null) {
66 | return "";
67 | }
68 | String topPackage = null;
69 | if (psiClass.getContainingClass() != null) {
70 | topPackage = psiClass.getContainingClass().getQualifiedName();
71 | }
72 | String classPackage = psiClass.getQualifiedName();
73 | if (classPackage == null) {
74 | return "";
75 | }
76 | if (Utils.isEmpty(topPackage)) {
77 | return classPackage.replaceAll("\\.", "/") + ".html";
78 | }
79 | classPackage = classPackage.replaceFirst(topPackage, topPackage.replaceAll("\\.", "/"));
80 | return classPackage + ".html";
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/CodeSearchAction.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action;
2 |
3 | import com.apkfuns.androidsourceviewer.action.base.BaseAction;
4 | import com.apkfuns.androidsourceviewer.action.view.SettingDialog;
5 | import com.apkfuns.androidsourceviewer.util.NotificationUtils;
6 | import com.intellij.ide.BrowserUtil;
7 | import com.intellij.openapi.actionSystem.AnActionEvent;
8 | import com.intellij.openapi.actionSystem.CommonDataKeys;
9 | import com.intellij.openapi.actionSystem.LangDataKeys;
10 | import com.intellij.openapi.editor.Editor;
11 | import com.intellij.openapi.util.text.StringUtil;
12 | import com.intellij.psi.PsiElement;
13 |
14 | /**
15 | * 使用搜索引擎进行源代码搜索
16 | * 进行站内搜索: site:android.googlesource.com <源码关键词>
17 | * 如: https://www.google.com/search?q=site%3Aandroid.googlesource.com+ActivityThread
18 | */
19 | public class CodeSearchAction extends BaseAction {
20 |
21 | @Override
22 | public void actionPerformed(AnActionEvent event) {
23 | super.actionPerformed(event);
24 | final Editor editor = event.getRequiredData(CommonDataKeys.EDITOR);
25 | String selectText = editor.getSelectionModel().getSelectedText();
26 | if (StringUtil.isEmpty(selectText)) {
27 | PsiElement element = event.getData(LangDataKeys.PSI_ELEMENT);
28 | if (element == null) {
29 | NotificationUtils.infoNotification("selected code empty");
30 | return;
31 | }
32 | String[] result = element.toString().split(":");
33 | if (result.length == 2) {
34 | selectText = result[1];
35 | } else {
36 | selectText = element.toString();
37 | }
38 | }
39 | selectText = selectText.trim().replaceAll("\\s+", "+");
40 | String url = "https://cn.bing.com/search?q=site%3Aandroid.googlesource.com+" + selectText;
41 | if (SettingDialog.configAccessExternalNetwork()) {
42 | url = "https://www.google.com/search?q=site%3Aandroid.googlesource.com+" + selectText;
43 | }
44 | BrowserUtil.browse(url);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/DiffSourceAction.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action;
2 |
3 | import com.apkfuns.androidsourceviewer.action.base.BaseSourceAction;
4 | import com.apkfuns.androidsourceviewer.util.DownloadManager;
5 | import com.apkfuns.androidsourceviewer.app.Constant;
6 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
7 | import com.apkfuns.androidsourceviewer.util.DownloadResult;
8 | import com.apkfuns.androidsourceviewer.util.Log;
9 | import com.apkfuns.androidsourceviewer.util.NotificationUtils;
10 | import com.apkfuns.androidsourceviewer.widget.PopListView;
11 | import com.intellij.openapi.actionSystem.AnActionEvent;
12 | import com.intellij.openapi.application.ApplicationManager;
13 | import com.intellij.openapi.progress.ProgressIndicator;
14 | import com.intellij.openapi.progress.ProgressManager;
15 | import com.intellij.openapi.progress.Task;
16 | import com.intellij.openapi.vfs.LocalFileSystem;
17 | import com.intellij.openapi.vfs.VirtualFile;
18 | import org.jetbrains.annotations.NotNull;
19 | import com.intellij.diff.DiffManager;
20 | import com.intellij.diff.contents.FileDocumentContentImpl;
21 | import com.intellij.diff.requests.SimpleDiffRequest;
22 | import com.intellij.openapi.editor.Document;
23 | import com.intellij.openapi.fileEditor.FileDocumentManager;
24 | import com.intellij.openapi.project.Project;
25 |
26 | import java.io.File;
27 | import java.util.List;
28 |
29 | /**
30 | * Created by pengwei on 2017/11/6.
31 | * 对比两个类差异
32 | */
33 | public class DiffSourceAction extends BaseSourceAction implements PopListView.OnItemClickListener {
34 |
35 | // PopListView
36 | private PopListView popListView;
37 | // 选中的第一个版本
38 | private String firstValue;
39 |
40 | @Override
41 | protected void onClassSelected(AnActionEvent event, String packageName) {
42 | firstValue = null;
43 | popListView = new PopListView(event);
44 | popListView.createList("Choose a version", data, this);
45 | }
46 |
47 | @Override
48 | public void OnItemClick(int position, final String value) {
49 | if (firstValue == null) {
50 | Log.debug("first =>" + value);
51 | firstValue = value;
52 | String[] newArray = data.clone();
53 | for (int i = 0; i < newArray.length; i++) {
54 | if (value.equals(newArray[i])) {
55 | newArray[i] = null;
56 | break;
57 | }
58 | }
59 | popListView.createList("Choose another version", newArray, this);
60 | } else {
61 | Log.debug("second =>" + value);
62 | String title = "Download:" + packageName;
63 | ProgressManager.getInstance().run(new Task.Backgroundable(project, title) {
64 | @Override
65 | public void run(@NotNull ProgressIndicator progressIndicator) {
66 | final DownloadTask task1 = new DownloadTask(packageName, firstValue);
67 | DownloadTask task2 = new DownloadTask(packageName, value);
68 | DownloadManager.getInstance().downloadFile(new DownloadTask[]{task1, task2},
69 | new File(Constant.CACHE_PATH + task1.getParentPath()),
70 | new DownloadResult() {
71 | @Override
72 | public void onSuccess(@NotNull List output) {
73 | Log.debug("DownloadResult=" + output);
74 | if (output.size() < 2) {
75 | NotificationUtils.errorNotification("Error: Download " + task1);
76 | return;
77 | }
78 | diff(project, output.get(0), output.get(1));
79 | }
80 |
81 | @Override
82 | public void onFailure(@NotNull String msg, Throwable throwable) {
83 | NotificationUtils.errorNotification("Error:" + msg);
84 | }
85 | }, true);
86 | }
87 | });
88 | }
89 | }
90 |
91 | /**
92 | * 调用 Android 文件对比
93 | *
94 | * @param project project
95 | * @param f1 对比的第一个文件
96 | * @param f2 对比的第二个文件
97 | */
98 | private void diff(final Project project, final File f1, final File f2) {
99 | ApplicationManager.getApplication().invokeLater(new Runnable() {
100 | @Override
101 | public void run() {
102 | try {
103 | VirtualFile v1 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f1);
104 | Document document1 = FileDocumentManager.getInstance().getDocument(v1);
105 | FileDocumentContentImpl fileDocumentContent1 = new FileDocumentContentImpl(project, document1, v1);
106 | VirtualFile v2 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f2);
107 | Document document2 = FileDocumentManager.getInstance().getDocument(v2);
108 | FileDocumentContentImpl fileDocumentContent2 = new FileDocumentContentImpl(project, document2, v2);
109 | SimpleDiffRequest simpleDiffRequest = new SimpleDiffRequest(Constant.TITLE, fileDocumentContent1, fileDocumentContent2,
110 | f1.getName(), f2.getName());
111 | DiffManager.getInstance().showDiff(project, simpleDiffRequest);
112 | } catch (Exception e) {
113 | NotificationUtils.errorNotification("Diff Source Error:" + e.getMessage());
114 | }
115 | }
116 | });
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/FindNativeMethodAction.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action;
2 |
3 | import com.apkfuns.androidsourceviewer.action.base.BaseSourceAction;
4 | import com.apkfuns.androidsourceviewer.entity.CPPDescriptor;
5 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
6 | import com.apkfuns.androidsourceviewer.app.Constant;
7 | import com.apkfuns.androidsourceviewer.util.*;
8 | import com.apkfuns.androidsourceviewer.widget.PopListView;
9 | import com.intellij.openapi.actionSystem.AnActionEvent;
10 | import com.intellij.openapi.progress.ProgressIndicator;
11 | import com.intellij.openapi.progress.ProgressManager;
12 | import com.intellij.openapi.progress.Task;
13 | import com.intellij.psi.PsiMethod;
14 | import org.jetbrains.annotations.NotNull;
15 | import org.jetbrains.annotations.Nullable;
16 |
17 | import java.io.File;
18 | import java.util.List;
19 |
20 | /**
21 | * java native方法源码定位
22 | */
23 | public class FindNativeMethodAction extends BaseSourceAction implements PopListView.OnItemClickListener {
24 | // Native Method
25 | private final PsiMethod psiMethod;
26 |
27 | public FindNativeMethodAction(PsiMethod psiMethod) {
28 | this.psiMethod = psiMethod;
29 | }
30 |
31 | @Override
32 | protected void onClassSelected(AnActionEvent event, String packageName) {
33 | new PopListView(event).createList("Choose Source Version", data, this);
34 | }
35 |
36 | @Override
37 | public void OnItemClick(int position, final String value) {
38 | String title = "Download Native File for " + psiMethod.getName();
39 | ProgressManager.getInstance().run(new Task.Backgroundable(project, title) {
40 | @Override
41 | public void run(@NotNull ProgressIndicator progressIndicator) {
42 | final DownloadTask downloadTask = new DownloadTask(new CPPDescriptor(packageName), value);
43 | DownloadManager.getInstance().searchFile(new DownloadTask[]{downloadTask},
44 | new File(Constant.CACHE_PATH, downloadTask.getParentPath()),
45 | new DownloadResult() {
46 | @Override
47 | public void onSuccess(@NotNull List output) {
48 | if (!output.isEmpty()) {
49 | Utils.openFileInPanel(output.get(0).getPath(), project);
50 | } else {
51 | NotificationUtils.errorNotification("Error: Download " + downloadTask);
52 | }
53 | }
54 |
55 | @Override
56 | public void onFailure(@NotNull String msg, @Nullable Throwable throwable) {
57 | NotificationUtils.errorNotification("Error:" + msg);
58 | }
59 | }, true, true);
60 | }
61 | });
62 | }
63 |
64 | @Override
65 | protected String[] versionList() {
66 | return Constant.ANDROID_VERSION_LIST;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/HelpAction.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action;
2 |
3 | import com.apkfuns.androidsourceviewer.action.base.BaseAction;
4 | import com.apkfuns.androidsourceviewer.action.view.SettingDialog;
5 | import com.intellij.openapi.actionSystem.AnActionEvent;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | /**
9 | * 帮助
10 | */
11 | public class HelpAction extends BaseAction {
12 |
13 | @Override
14 | public void actionPerformed(@NotNull AnActionEvent e) {
15 | new SettingDialog(e.getProject()).setVisible(true);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/SourceViewerAction.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action;
2 |
3 | import com.apkfuns.androidsourceviewer.action.base.BaseSourceAction;
4 | import com.apkfuns.androidsourceviewer.util.DownloadManager;
5 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
6 | import com.apkfuns.androidsourceviewer.app.Constant;
7 | import com.apkfuns.androidsourceviewer.util.DownloadResult;
8 | import com.apkfuns.androidsourceviewer.util.Log;
9 | import com.apkfuns.androidsourceviewer.util.NotificationUtils;
10 | import com.apkfuns.androidsourceviewer.util.Utils;
11 | import com.apkfuns.androidsourceviewer.widget.PopListView;
12 | import com.intellij.openapi.actionSystem.AnActionEvent;
13 | import com.intellij.openapi.progress.ProgressIndicator;
14 | import com.intellij.openapi.progress.ProgressManager;
15 | import com.intellij.openapi.progress.Task;
16 | import org.jetbrains.annotations.NotNull;
17 |
18 | import java.io.File;
19 | import java.util.List;
20 |
21 | /**
22 | * Created by pengwei on 2017/11/5.
23 | * 查看源码
24 | */
25 | public class SourceViewerAction extends BaseSourceAction implements PopListView.OnItemClickListener {
26 |
27 | @Override
28 | protected void onClassSelected(AnActionEvent event, String packageName) {
29 | new PopListView(event).createList("Choose Source Version", data, this);
30 | }
31 |
32 | @Override
33 | public void OnItemClick(int position, final String version) {
34 | String title = "Download:" + version + " - " + packageName;
35 | ProgressManager.getInstance().run(new Task.Backgroundable(project, title) {
36 | @Override
37 | public void run(@NotNull ProgressIndicator progressIndicator) {
38 | final DownloadTask task = new DownloadTask(packageName, version);
39 | DownloadManager.getInstance().downloadFile(new DownloadTask[]{task},
40 | new File(Constant.CACHE_PATH + task.getParentPath()),
41 | new DownloadResult() {
42 | @Override
43 | public void onSuccess(@NotNull List output) {
44 | Log.debug("DownloadResult=" + output);
45 | if (output.isEmpty()) {
46 | NotificationUtils.errorNotification("Error: Download " + task);
47 | return;
48 | }
49 | Utils.openFileInPanel(output.get(0).getPath(), SourceViewerAction.this.project);
50 | }
51 |
52 | @Override
53 | public void onFailure(@NotNull String msg, Throwable throwable) {
54 | NotificationUtils.errorNotification("Error:" + msg);
55 | }
56 | }, true);
57 | progressIndicator.setFraction(0.5);
58 | }
59 | });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/base/BaseAction.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action.base;
2 |
3 | import com.intellij.openapi.actionSystem.AnAction;
4 | import com.intellij.openapi.actionSystem.AnActionEvent;
5 | import com.intellij.openapi.project.DumbAware;
6 | import com.intellij.openapi.project.Project;
7 |
8 | public abstract class BaseAction extends AnAction implements DumbAware {
9 |
10 | protected Project project;
11 |
12 | @Override
13 | public void actionPerformed(AnActionEvent event) {
14 | this.project = event.getProject();
15 | }
16 |
17 | @Override
18 | public final boolean isDumbAware() {
19 | return true;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/base/BaseSourceAction.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action.base;
2 |
3 | import com.apkfuns.androidsourceviewer.util.NotificationUtils;
4 | import com.apkfuns.androidsourceviewer.util.Utils;
5 | import com.intellij.openapi.actionSystem.AnActionEvent;
6 | import com.intellij.openapi.actionSystem.LangDataKeys;
7 | import com.intellij.openapi.project.DumbAware;
8 | import com.intellij.psi.PsiElement;
9 |
10 | public abstract class BaseSourceAction extends BaseAction implements DumbAware {
11 |
12 | protected String packageName;
13 | protected AnActionEvent actionEvent;
14 | protected PsiElement element;
15 | protected String[] data;
16 |
17 | @Override
18 | public final void actionPerformed(AnActionEvent event) {
19 | super.actionPerformed(event);
20 | this.actionEvent = event;
21 | packageName = Utils.getClassPath(event);
22 | element = event.getData(LangDataKeys.PSI_ELEMENT);
23 | if (Utils.isEmpty(packageName)) {
24 | NotificationUtils.infoNotification("Must be Class or Method");
25 | return;
26 | }
27 | this.selectActionPerformed(event, element, packageName);
28 | if (shouldSelectVersion()) {
29 | data = versionList();
30 | if (data == null) {
31 | data = Utils.getVersionList(packageName);
32 | }
33 | if (data == null) {
34 | NotificationUtils.infoNotification("Invalid PackageName:" + packageName);
35 | return;
36 | }
37 | this.onClassSelected(this.actionEvent, packageName);
38 | }
39 | }
40 |
41 | /**
42 | * 选择类回调
43 | * @param event
44 | * @param element 选择元素类型 (类/方法/变量等等)
45 | * @param packageName 选择的包名
46 | */
47 | protected void selectActionPerformed(AnActionEvent event, PsiElement element, String packageName) {
48 |
49 | }
50 |
51 | /**
52 | * 版本选择回调
53 | * @param event
54 | * @param packageName
55 | */
56 | protected void onClassSelected(AnActionEvent event, String packageName) {
57 |
58 | }
59 |
60 | /**
61 | * 是否需要选择版本
62 | * @return
63 | */
64 | protected boolean shouldSelectVersion() {
65 | return true;
66 | }
67 |
68 | /**
69 | * 展示版本列表
70 | * @return String[]
71 | */
72 | protected String[] versionList() {
73 | return null;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/view/BaseDialog.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action.view;
2 |
3 | import javax.swing.*;
4 | import java.awt.*;
5 | import java.awt.event.*;
6 |
7 | /**
8 | * Created by pengwei on 17/2/22.
9 | */
10 | public abstract class BaseDialog extends JDialog {
11 |
12 | public BaseDialog() {
13 | setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
14 | addWindowListener(new WindowAdapter() {
15 | public void windowClosing(WindowEvent e) {
16 | dispose();
17 | }
18 | });
19 | }
20 |
21 | /**
22 | * 界面居中
23 | */
24 | void setLocationCenter() {
25 | Dimension dimension = getSize();
26 | int windowWidth = dimension.width;
27 | int windowHeight = dimension.height;
28 | Toolkit kit = Toolkit.getDefaultToolkit();
29 | Dimension screenSize = kit.getScreenSize();
30 | int screenWidth = screenSize.width;
31 | int screenHeight = screenSize.height;
32 | this.setLocation(screenWidth / 2 - windowWidth / 2, screenHeight / 2 - windowHeight / 2); // 设置窗口居中显示
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/view/SettingDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
23 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/action/view/SettingDialog.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.action.view;
2 |
3 | import com.apkfuns.androidsourceviewer.app.Constant;
4 | import com.apkfuns.androidsourceviewer.util.GlobalStorageUtil;
5 | import com.apkfuns.androidsourceviewer.util.NotificationUtils;
6 | import com.apkfuns.androidsourceviewer.widget.LineLayout;
7 | import com.intellij.openapi.progress.ProgressIndicator;
8 | import com.intellij.openapi.progress.ProgressManager;
9 | import com.intellij.openapi.progress.Task;
10 | import com.intellij.openapi.project.Project;
11 | import com.intellij.openapi.ui.LabeledComponent;
12 | import com.intellij.util.ui.UIUtil;
13 | import org.jetbrains.annotations.NotNull;
14 | import org.apache.commons.io.FileUtils;
15 |
16 | import javax.swing.*;
17 | import javax.swing.event.ChangeEvent;
18 | import javax.swing.event.ChangeListener;
19 | import java.awt.event.ActionEvent;
20 | import java.awt.event.ActionListener;
21 | import java.io.File;
22 | import java.io.IOException;
23 |
24 | public class SettingDialog extends BaseDialog {
25 | // storage key
26 | private static final String ACCESS_EXTERNAL_NETWORK = "AccessExternalNetwork";
27 | // contentPane
28 | private JPanel contentPane;
29 | // 缓存目录
30 | private File cacheDir = new File(Constant.CACHE_PATH);
31 | // cleanDiskComponent
32 | private LabeledComponent cleanDiskComponent;
33 |
34 | public SettingDialog(final Project project) {
35 | super();
36 | setContentPane(contentPane);
37 | setModal(true);
38 | setTitle("设置");
39 | setSize(600, 350);
40 | setResizable(false);
41 | int border = 20;
42 | contentPane.setBorder(BorderFactory.createEmptyBorder(border, border, 0, 0));
43 | contentPane.setLayout(new LineLayout(10));
44 | // 判断是否可以访问外网
45 | JCheckBox checkBox = new JCheckBox("sure", configAccessExternalNetwork());
46 | checkBox.addChangeListener(new ChangeListener() {
47 | @Override
48 | public void stateChanged(ChangeEvent e) {
49 | AbstractButton abstractButton = (AbstractButton) e.getSource();
50 | ButtonModel buttonModel = abstractButton.getModel();
51 | buttonModel.isSelected();
52 | GlobalStorageUtil.put(ACCESS_EXTERNAL_NETWORK, buttonModel.isSelected() ? "1" : "0");
53 | }
54 | });
55 | LabeledComponent netComponent = LabeledComponent.create(checkBox, "Can you access Google?");
56 | contentPane.add(netComponent);
57 | // 清理缓存
58 | JButton cleanDiskButton = new JButton("Clear immediately");
59 | cleanDiskButton.addActionListener(new ActionListener() {
60 | @Override
61 | public void actionPerformed(ActionEvent event) {
62 | try {
63 | FileUtils.cleanDirectory(cacheDir);
64 | NotificationUtils.infoNotification("Clean up successfully");
65 | collectCacheFile(project);
66 | } catch (IOException e) {
67 | e.printStackTrace();
68 | }
69 | }
70 | });
71 | cleanDiskComponent = LabeledComponent.create(cleanDiskButton, "Collecting...");
72 | contentPane.add(cleanDiskComponent);
73 | collectCacheFile(project);
74 | setLocationCenter();
75 | }
76 |
77 | /**
78 | * 收集缓存信息
79 | *
80 | * @param project project
81 | */
82 | private void collectCacheFile(final Project project) {
83 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "Collecting...", false) {
84 | public void run(@NotNull ProgressIndicator indicator) {
85 | indicator.setText("Collecting...");
86 | final long size = FileUtils.sizeOfDirectory(cacheDir);
87 | UIUtil.invokeLaterIfNeeded(new Runnable() {
88 | @Override
89 | public void run() {
90 | cleanDiskComponent.setText(String.format("Currently occupy storage %.2fM", size / 1024.0 / 1024.0));
91 | }
92 | });
93 | }
94 | });
95 | }
96 |
97 | /**
98 | * 是否能访问外网
99 | *
100 | * @return bool
101 | */
102 | public static boolean configAccessExternalNetwork() {
103 | return "1".equals(GlobalStorageUtil.get(ACCESS_EXTERNAL_NETWORK, "1"));
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/app/Constant.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.app;
2 |
3 | /**
4 | * Created by pengwei on 2017/11/5.
5 | */
6 | public interface Constant {
7 |
8 | String TITLE = "Android Source Viewer";
9 |
10 | // 用户目录
11 | String USER_HOME = System.getProperty("user.home");
12 | // 基础目录
13 | String PROJECT_BASE_PATH = USER_HOME + "/.AndroidSourceViewer/";
14 | // 缓存目录
15 | String CACHE_PATH = PROJECT_BASE_PATH + "cache/";
16 | // 全局配置文件
17 | String GLOBAL_CONFIG_FILE = PROJECT_BASE_PATH + "global.properties";
18 |
19 | // 版本分布
20 | String[] ANDROID_VERSION_LIST = {"Q", "Q - 10.0.0_r6", "Pie", "Pie - 9.0.0_r3",
21 | "Oreo", "Oreo - 8.1.0_r33", "Nougat", "Nougat - 7.1.1_r6", "Nougat - 7.0.0_r1",
22 | "Marshmallow", "Marshmallow - 6.0.1_r10", "Marshmallow - 6.0.0_r5", "Marshmallow - 6.0.0_r1",
23 | "Lollipop", "Lollipop - 5.1.1_r6", "Lollipop - 5.1.0_r1", "Lollipop - 5.0.0_r2", "KitKat", "KitKat - 4.4.4_r1",
24 | "KitKat - 4.4.3_r1.1", "KitKat - 4.4.2_r2", "KitKat - 4.4.2_r1", "KitKat - 4.4",
25 | "Other", "Gingerbread - 2.3.7", "ICS - 4.0.3", "JellyBean - 4.3", "JellyBean - 4.1.2",
26 | "JellyBean - 4.2.2", "JellyBean - 4.2", "Froyo - 2.2.3", "Eclair - 2.1",
27 | "Donut - 1.6"};
28 |
29 | String[] JAVA_VERSION_LIST = {"Java8", "Java-8-b132", "Java-8u40-b25", "Java7", "Java-7-b147", "Java-7u40-b43",
30 | "Java6", "Java-6-b27", "Java-6-b14"};
31 |
32 | // 包名前缀
33 | String[] JAVA_PACKAGE_PREFIX = {"com.oracle", "com.sun", "java", "javax", "sun", "sunw",
34 | "org.xml", "org.w3c", "org.relaxng", "org.omg", "org.jcp", "org.ietf", "jdk.internal"};
35 |
36 | String[] ANDROID_PACKAGE_PREFIX = {"android", "org.chromium", "frameworks", "com.android",
37 | "com.google", "com.googlecode", "com.example.android", "org.json", "org.xml", "org.xmlpull",
38 | "org.w3c", "org.apache", "dalvik", "junit.framework", "junit.runner"};
39 |
40 | // Android 下载链接
41 | String DOWNLOAD_BASE_PATH = "http://androidxref.com/%s/raw/frameworks/base/core/java/%s";
42 |
43 | // Java 下载
44 | String JAVA_DOWNLOAD_BASE_PATH = "http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/%s/%s/?v=source&disposition=attachment";
45 | }
46 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/download/AndroidOSDownload.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.download;
2 |
3 | import com.apkfuns.androidsourceviewer.download.inteface.FileDownload;
4 | import com.apkfuns.androidsourceviewer.entity.ClassFile;
5 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
6 | import com.apkfuns.androidsourceviewer.util.Log;
7 | import com.apkfuns.androidsourceviewer.util.Utils;
8 | import com.intellij.openapi.progress.util.StatusBarProgress;
9 | import com.intellij.openapi.util.Pair;
10 | import com.intellij.util.io.HttpRequests;
11 | import org.jetbrains.annotations.NotNull;
12 |
13 | import java.io.File;
14 | import java.util.ArrayList;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 |
19 | /**
20 | * AndroidOS 源码下载
21 | * https://www.androidos.net.cn/android/9.0.0_r8/download/frameworks/support/collection/src/main/java/androidx/collection/ArrayMap.java
22 | */
23 | public class AndroidOSDownload extends FileDownload {
24 |
25 | // 请求链接
26 | private static final String REQUEST_URL = "https://www.androidos.net.cn/android/%s/download/frameworks/base/core/java/%s";
27 |
28 | // AndroidOS网 版本映射表
29 | private static final Map VERSION_MAP = new HashMap<>();
30 | static {
31 | VERSION_MAP.put("10.0", "10.0.0_r6");
32 | VERSION_MAP.put("9.0", "9.0.0_r8");
33 | VERSION_MAP.put("8.0", "8.0.0_r4");
34 | VERSION_MAP.put("7.1", "7.1.1_r28");
35 | VERSION_MAP.put("7.0", "7.0.0_r31");
36 | VERSION_MAP.put("6.0", "6.0.1_r16");
37 | VERSION_MAP.put("5.1", "5.1.0_r3");
38 | VERSION_MAP.put("5.0", "5.0.1_r1");
39 | VERSION_MAP.put("4.4", "4.4w_r1");
40 | VERSION_MAP.put("4.3", "4.3_r1");
41 | VERSION_MAP.put("4.2", "4.2.2_r1");
42 | VERSION_MAP.put("4.1", "4.1.1_r1");
43 | VERSION_MAP.put("4.0", "4.0.4_r2.1");
44 | VERSION_MAP.put("2.3", "2.3.7_r1");
45 | VERSION_MAP.put("2.2", "2.2_r1");
46 | VERSION_MAP.put("2.0", "2.0_r1");
47 | VERSION_MAP.put("1.6", "1.6_r1");
48 | }
49 |
50 | @Override
51 | public Pair> onDownload(@NotNull DownloadTask[] tasks, @NotNull File outputFolder) {
52 | List files = new ArrayList<>();
53 | for (DownloadTask task : tasks) {
54 | String version = Utils.matchVersion(VERSION_MAP, task.getVersionName());
55 | ClassFile outputFile = new ClassFile(outputFolder, task.getCustomFileName(version));
56 | outputFile.setDownloadTask(task);
57 | if (outputFile.exists()) {
58 | files.add(outputFile);
59 | continue;
60 | }
61 | String downUrl = String.format(REQUEST_URL, version, task.getFullPath());
62 | Log.debug("download:" + task + ", result:" + downUrl);
63 | try {
64 | HttpRequests.request(downUrl).saveToFile(outputFile, new StatusBarProgress());
65 | } catch (Exception e) {
66 | e.printStackTrace();
67 | }
68 | if (outputFile.exists()) {
69 | files.add(outputFile);
70 | }
71 | }
72 | return Pair.create(files.size() == tasks.length, files);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/download/AndroidOSSearch.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.download;
2 |
3 | import com.apkfuns.androidsourceviewer.download.inteface.FileDownload;
4 | import com.apkfuns.androidsourceviewer.entity.ClassFile;
5 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
6 | import com.apkfuns.androidsourceviewer.util.HttpUtil;
7 | import com.apkfuns.androidsourceviewer.util.Log;
8 | import com.intellij.openapi.progress.util.StatusBarProgress;
9 | import com.intellij.openapi.util.Pair;
10 | import com.intellij.util.io.HttpRequests;
11 | import org.jetbrains.annotations.NotNull;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.regex.Matcher;
18 | import java.util.regex.Pattern;
19 |
20 | /**
21 | * AndroidOS 源码搜索
22 | * https://www.androidos.net.cn/androidossearch?query=ArrayMap.java&from=code
23 | */
24 | public class AndroidOSSearch extends FileDownload {
25 |
26 | // 域名
27 | private static final String HOST = "https://www.androidos.net.cn";
28 | // 请求路径
29 | private static final String REQUEST_PATH = "https://www.androidos.net.cn/androidossearch?query=%S&from=code";
30 |
31 | @Override
32 | public Pair> onDownload(@NotNull DownloadTask[] tasks, @NotNull File outputFolder) {
33 | List files = new ArrayList<>();
34 | for (DownloadTask task : tasks) {
35 | String url = String.format(REQUEST_PATH, task.getClassDescriptor().getFileName());
36 | try {
37 | String content = null;
38 | try {
39 | content = HttpUtil.syncGet(url);
40 | } catch (Exception e) {
41 | e.printStackTrace();
42 | }
43 | if (content == null) {
44 | continue;
45 | }
46 | Pattern pattern = Pattern.compile(".* ");
47 | Matcher matcher = pattern.matcher(content);
48 | String backup = null;
49 | String selectClass = null;
50 | while (matcher.find()) {
51 | String searchItem = matcher.group(1);
52 | if (task.checkUrlAvailable(searchItem)) {
53 | if (backup == null) {
54 | backup = searchItem;
55 | }
56 | if (searchItem.contains(task.getAndroidVersion())) {
57 | selectClass = searchItem;
58 | break;
59 | }
60 | }
61 | }
62 | if (selectClass == null && backup != null) {
63 | selectClass = backup;
64 | }
65 | if (selectClass == null) {
66 | break;
67 | }
68 | Log.debug("download:" + task + ", result:" + selectClass);
69 | String downUrl = HOST + selectClass.replace("/xref/", "/download/");
70 | // 匹配出实际的版本
71 | Pattern versionPattern = Pattern.compile("/android/(.*)/xref/");
72 | Matcher versionMatcher = versionPattern.matcher(selectClass);
73 | String versionName = task.getVersionName();
74 | if (versionMatcher.find()) {
75 | versionName = versionMatcher.group(1);
76 | }
77 | ClassFile outputFile = new ClassFile(outputFolder, task.getCustomFileName(versionName));
78 | outputFile.setDownloadTask(task);
79 | if (outputFile.exists()) {
80 | files.add(outputFile);
81 | continue;
82 | }
83 | HttpRequests.request(downUrl).saveToFile(outputFile, new StatusBarProgress());
84 | if (outputFile.exists()) {
85 | files.add(outputFile);
86 | }
87 | } catch (IOException e) {
88 | e.printStackTrace();
89 | }
90 | }
91 | return Pair.create(files.size() == tasks.length, files);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/download/XrefDownload.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.download;
2 |
3 | import com.apkfuns.androidsourceviewer.download.inteface.FileDownload;
4 | import com.apkfuns.androidsourceviewer.entity.ClassFile;
5 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
6 | import com.apkfuns.androidsourceviewer.util.Log;
7 | import com.apkfuns.androidsourceviewer.util.Utils;
8 | import com.intellij.openapi.util.Pair;
9 | import com.intellij.platform.templates.github.DownloadUtil;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.util.ArrayList;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 |
19 | public class XrefDownload extends FileDownload {
20 |
21 | // Android 下载链接
22 | private static final String DOWNLOAD_BASE_PATH = "http://androidxref.com/%s/raw/frameworks/base/core/java/%s";
23 |
24 | // androidXref 版本映射表
25 | private static final Map VERSION_MAP = new HashMap<>();
26 | static {
27 | VERSION_MAP.put("9.0", "9.0.0_r3");
28 | VERSION_MAP.put("8.1", "8.1.0_r33");
29 | VERSION_MAP.put("8.0", "8.0.0_r4");
30 | VERSION_MAP.put("7.1", "7.1.1_r6");
31 | VERSION_MAP.put("7.0", "7.0.0_r1");
32 | VERSION_MAP.put("6.0", "6.0.1_r10");
33 | VERSION_MAP.put("5.1", "5.1.1_r6");
34 | VERSION_MAP.put("5.0", "5.0.0_r2");
35 | VERSION_MAP.put("4.4", "4.4.4_r1");
36 | VERSION_MAP.put("4.3", "4.3_r1");
37 | VERSION_MAP.put("4.2", "4.2.2_r1");
38 | VERSION_MAP.put("4.1", "4.1.1_r1");
39 | VERSION_MAP.put("4.0", "4.0.3_r1");
40 | VERSION_MAP.put("2.3", "2.3.7");
41 | VERSION_MAP.put("2.2", "2.2.3");
42 | VERSION_MAP.put("2.0", "2.1");
43 | VERSION_MAP.put("1.6", "1.6");
44 | }
45 |
46 | @Override
47 | public Pair> onDownload(@NotNull DownloadTask[] tasks, @NotNull File outputFolder) {
48 | List fileList = new ArrayList<>();
49 | for (DownloadTask task : tasks) {
50 | String version = Utils.matchVersion(VERSION_MAP, task.getVersionName());
51 | String url = String.format(DOWNLOAD_BASE_PATH, version, task.getFullPath());
52 | ClassFile outFile = new ClassFile(outputFolder, task.getCustomFileName(version));
53 | if (outFile.exists()) {
54 | fileList.add(outFile);
55 | continue;
56 | }
57 | try {
58 | DownloadUtil.downloadAtomically(null, url, outFile);
59 | } catch (IOException e) {
60 | Log.e(e);
61 | }
62 | if (outFile.exists()) {
63 | fileList.add(outFile);
64 | }
65 | }
66 | return Pair.create(tasks.length == fileList.size(), fileList);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/download/XrefSearch.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.download;
2 |
3 | import com.apkfuns.androidsourceviewer.download.inteface.FileDownload;
4 | import com.apkfuns.androidsourceviewer.entity.ClassFile;
5 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
6 | import com.apkfuns.androidsourceviewer.util.HttpUtil;
7 | import com.apkfuns.androidsourceviewer.util.Log;
8 | import com.intellij.openapi.progress.util.StatusBarProgress;
9 | import com.intellij.openapi.util.Pair;
10 | import com.intellij.util.io.HttpRequests;
11 | import org.jetbrains.annotations.NotNull;
12 | import org.jetbrains.annotations.Nullable;
13 |
14 | import java.io.File;
15 | import java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.regex.Matcher;
18 | import java.util.regex.Pattern;
19 |
20 | public class XrefSearch extends FileDownload {
21 |
22 | // androidXref 搜索
23 | private static final String ANDROID_SEARCH = "http://androidxref.com/%s/search?q=&defs=&refs=&path=%s&hist=&project=art&project=bionic&project=bootable&project=build&project=cts&project=dalvik&project=developers&project=development&project=device&project=docs&project=external&project=frameworks&project=hardware&project=kernel&project=libcore&project=libnativehelper&project=packages&project=pdk&project=platform_testing&project=prebuilts&project=sdk&project=system&project=test&project=toolchain&project=tools";
24 |
25 | @Override
26 | public Pair> onDownload(@NotNull DownloadTask[] tasks, @NotNull File outputFolder) {
27 | List files = new ArrayList<>();
28 | for (DownloadTask task : tasks) {
29 | ClassFile outputFile = new ClassFile(outputFolder, task.getSaveFileName());
30 | if (outputFile.exists()) {
31 | files.add(outputFile);
32 | continue;
33 | }
34 | try {
35 | List urlList = multiSearch(task, true);
36 | for (String url: urlList) {
37 | if (task.checkUrlAvailable(url)) {
38 | HttpRequests.request(url).saveToFile(outputFile, new StatusBarProgress());
39 | break;
40 | }
41 | }
42 | } catch (Exception e) {
43 | e.printStackTrace();
44 | }
45 | }
46 | return Pair.create(files.size() == tasks.length, files);
47 | }
48 |
49 |
50 | /**
51 | * 源码搜索,单条结果
52 | *
53 | * @param downloadTask DownloadTask
54 | * @param format 是否替换 '.' 为 '%2F'
55 | * @return String
56 | * @throws Exception e
57 | */
58 | @Nullable
59 | private String singleSearch(final DownloadTask downloadTask, boolean format) throws Exception {
60 | String requestPath = downloadTask.getClassDescriptor().getClassName();
61 | if (format) {
62 | requestPath = requestPath.replaceAll("\\.", "%2F");
63 | }
64 | String url = String.format(ANDROID_SEARCH, downloadTask.getVersionName(), requestPath);
65 | Log.debug("url=" + url);
66 | String result = HttpUtil.syncGet(url);
67 | result = result.replace("\n", "");
68 | Pattern pattern = Pattern.compile("id=\"results\".*class=\"f\">");
69 | Matcher matcher = pattern.matcher(result);
70 | if (matcher.find()) {
71 | String res = "http://androidxref.com" + matcher.group(1);
72 | return res.replace("/xref/", "/raw/");
73 | }
74 | return null;
75 | }
76 |
77 | /**
78 | * 源码搜索, 支持多条结果
79 | *
80 | * @param downloadTask DownloadTask
81 | * @param format 是否替换 '.' 为 '%2F'
82 | * @return List
83 | * @throws Exception e
84 | */
85 | @NotNull
86 | private List multiSearch(final DownloadTask downloadTask, boolean format) throws Exception {
87 | String requestPath = downloadTask.getClassDescriptor().getClassName();
88 | if (format) {
89 | requestPath = requestPath.replaceAll("\\.", "%2F");
90 | }
91 | List arrays = new ArrayList<>();
92 | String url = String.format(ANDROID_SEARCH, downloadTask.getVersionName(), requestPath);
93 | String result = HttpUtil.syncGet(url);
94 | result = result.replace("\n", "");
95 | Pattern pattern = Pattern.compile("class=\"f\">");
96 | Matcher matcher = pattern.matcher(result);
97 | while (matcher.find()) {
98 | arrays.add(matcher.group(1));
99 | }
100 | return arrays;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/download/inteface/FileDownload.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.download.inteface;
2 |
3 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
4 | import com.apkfuns.androidsourceviewer.util.Log;
5 | import com.intellij.openapi.util.Pair;
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import java.io.File;
9 | import java.util.List;
10 |
11 | /**
12 | * 文件下载
13 | */
14 | public abstract class FileDownload implements IDownload, Comparable {
15 |
16 | // 下载优先级
17 | private int priority = 0;
18 | // 平均时间
19 | private long costTimeAverage = 0;
20 |
21 | @Override
22 | public abstract Pair> onDownload(@NotNull DownloadTask[] tasks,
23 | @NotNull File outputFolder);
24 |
25 | /**
26 | * 更新优先级
27 | *
28 | * @param isSuccess 是否下载成功
29 | * @param costTime 下载花费时间
30 | */
31 | public void updatePriority(boolean isSuccess, long costTime) {
32 | Log.debug("result:" + isSuccess + ", costTime:" + costTime + ", class:" + getClass()
33 | + ", priority:" + priority + ", average:" + costTimeAverage);
34 | priority = isSuccess ? priority + 1 : priority - 1;
35 | if (isSuccess && costTime > 0) {
36 | if (costTimeAverage == 0) {
37 | costTimeAverage = costTime;
38 | } else {
39 | costTimeAverage = (costTimeAverage + costTime) / 2;
40 | }
41 | }
42 | }
43 |
44 | @Override
45 | public int compareTo(@NotNull FileDownload o) {
46 | // > 0 是大数往后排
47 | if (Math.abs(Math.abs(priority) - Math.abs(o.priority)) < 3 && costTimeAverage > 0
48 | && o.costTimeAverage > 0) {
49 | return Long.compare(costTimeAverage, o.costTimeAverage);
50 | }
51 | return -Integer.compare(priority, o.priority);
52 | }
53 |
54 | // 暂停机制, 失败次数过多,暂停使用
55 | public boolean enable() {
56 | return this.priority >= -3;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/download/inteface/IDownload.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.download.inteface;
2 |
3 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
4 | import com.intellij.openapi.util.Pair;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | import java.io.File;
8 | import java.util.List;
9 |
10 | public interface IDownload {
11 | /**
12 | * @param tasks 待下载的任务
13 | * @param outputFolder 下载保存的路径
14 | * @return {下载完成,下载结果集合}
15 | */
16 | Pair> onDownload(@NotNull DownloadTask[] tasks, @NotNull File outputFolder);
17 | }
18 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/entity/CPPDescriptor.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.entity;
2 |
3 | /**
4 | * C/CPP 文件描述
5 | */
6 | public class CPPDescriptor extends ClassDescriptor {
7 |
8 | // 扩展名
9 | private static final String[] EXT_ARRAY = {".c", ".cc", ".cpp"};
10 |
11 | public CPPDescriptor(Class source) {
12 | super(source);
13 | }
14 |
15 | public CPPDescriptor(String className) {
16 | super(className);
17 | }
18 |
19 | @Override
20 | protected void init() {
21 | this.fileName = this.className.replaceAll("\\.", "_") + getExt()[0];
22 | // 指定c++类放到 art.runtime.native
23 | this.packageName = "art.runtime.native";
24 | }
25 |
26 | @Override
27 | protected String[] getExt() {
28 | return EXT_ARRAY;
29 | }
30 |
31 | /**
32 | * @return java_lang_Thread
33 | */
34 | @Override
35 | public String getClassPath() {
36 | return this.className.replaceAll("\\.", "_");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/entity/ClassDescriptor.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.entity;
2 |
3 | import com.apkfuns.androidsourceviewer.util.Utils;
4 |
5 | /**
6 | * Java 类描述
7 | */
8 | public class ClassDescriptor {
9 | // 文件分隔
10 | protected static final String SEPARATOR = System.getProperty("file.separator");
11 | // 扩展名
12 | private static final String[] EXT_ARRAY = {".java"};
13 |
14 | // 类的完整路径 => java.util.List
15 | protected String className;
16 | // 文件名 => List.java
17 | protected String fileName;
18 | // 包名 => java.util
19 | protected String packageName;
20 |
21 | public ClassDescriptor(Class source) {
22 | this.className = source.getName();
23 | this.init();
24 | }
25 |
26 | public ClassDescriptor(String className) {
27 | this.className = className;
28 | this.init();
29 | }
30 |
31 | /**
32 | * 初始化
33 | */
34 | protected void init() {
35 | int index = this.className.lastIndexOf(".");
36 | if (index >= 0) {
37 | fileName = this.className.substring(index + 1) + getExt()[0];
38 | packageName = this.className.substring(0, index);
39 | } else {
40 | fileName = className + getExt()[0];
41 | packageName = "";
42 | }
43 | }
44 |
45 | /**
46 | * 获取文件名
47 | * @return EG. List.java
48 | */
49 | public String getFileName() {
50 | return fileName;
51 | }
52 |
53 | /**
54 | * 获取包名
55 | * @return EG. java.util
56 | */
57 | public String getPackageName() {
58 | return packageName;
59 | }
60 |
61 | /**
62 | * class 名称
63 | *
64 | * @return 名称
65 | */
66 | public String getClassName() {
67 | return className;
68 | }
69 |
70 | /**
71 | * 是否为 Android 的类
72 | * @return bool
73 | */
74 | public boolean isAndroidClass() {
75 | return Utils.isAndroidClass(className);
76 | }
77 |
78 | /**
79 | * 获取扩展名
80 | *
81 | * @return 扩展名
82 | */
83 | protected String[] getExt() {
84 | return EXT_ARRAY;
85 | }
86 |
87 | /**
88 | * class 路径 java/util/List
89 | * @return string
90 | */
91 | public String getClassPath() {
92 | return className.replaceAll("\\.", SEPARATOR);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/entity/ClassFile.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.entity;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.io.File;
7 | import java.net.URI;
8 |
9 | /**
10 | * class 文件
11 | */
12 | public class ClassFile extends File {
13 |
14 | // 对应的下载任务
15 | @Nullable
16 | private DownloadTask downloadTask;
17 |
18 | public ClassFile(@NotNull String pathname) {
19 | super(pathname);
20 | }
21 |
22 | public ClassFile(String parent, @NotNull String child) {
23 | super(parent, child);
24 | }
25 |
26 | public ClassFile(File parent, @NotNull String child) {
27 | super(parent, child);
28 | }
29 |
30 | public ClassFile(@NotNull URI uri) {
31 | super(uri);
32 | }
33 |
34 | @Nullable
35 | public DownloadTask getDownloadTask() {
36 | return downloadTask;
37 | }
38 |
39 | public void setDownloadTask(@Nullable DownloadTask downloadTask) {
40 | this.downloadTask = downloadTask;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/entity/DownloadTask.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.entity;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | /**
7 | * 下载任务描述类
8 | */
9 | public class DownloadTask {
10 | // 文件分隔
11 | private static final String SEPARATOR = System.getProperty("file.separator");
12 |
13 | // 类描述
14 | @NotNull
15 | private final ClassDescriptor classDescriptor;
16 | // 完整的版本名称 8.0.0_r4
17 | private final String fullVersionName;
18 | // Android 版本 8.0.0
19 | private String androidVersion;
20 | // 源码版本 r4
21 | private String sourceVersion;
22 |
23 | public DownloadTask(@NotNull String className, @NotNull String versionName) {
24 | this(new ClassDescriptor(className), versionName);
25 | }
26 |
27 | public DownloadTask(@NotNull ClassDescriptor descriptor, @NotNull String versionName) {
28 | this.classDescriptor = descriptor;
29 | this.fullVersionName = versionName;
30 | String[] versionGroup = fullVersionName.split("_");
31 | if (versionGroup.length == 2) {
32 | this.androidVersion = versionGroup[0];
33 | this.sourceVersion = versionGroup[1];
34 | }
35 | }
36 |
37 | /**
38 | * 获取包名对应的路径
39 | *
40 | * @return java/util
41 | */
42 | public String getParentPath() {
43 | return classDescriptor.getPackageName().replaceAll("\\.", SEPARATOR);
44 | }
45 |
46 | /**
47 | * 获取类的完整路径
48 | *
49 | * @return java/util/List.java
50 | */
51 | public String getFullPath() {
52 | return getParentPath() + SEPARATOR + classDescriptor.getFileName();
53 | }
54 |
55 | /**
56 | * 获取版本名称
57 | *
58 | * @return 版本
59 | */
60 | @NotNull
61 | public String getVersionName() {
62 | return fullVersionName;
63 | }
64 |
65 | /**
66 | * Android版本
67 | */
68 | public String getAndroidVersion() {
69 | return androidVersion;
70 | }
71 |
72 | /**
73 | * 源码版本
74 | */
75 | public String getSourceVersion() {
76 | return sourceVersion;
77 | }
78 |
79 | /**
80 | * 获取本地保存的文件名
81 | *
82 | * @return 文件名
83 | */
84 | public String getSaveFileName() {
85 | return getVersionName() + "-" + classDescriptor.getFileName();
86 | }
87 |
88 | /**
89 | * 传入版本号获取自定义文件名
90 | *
91 | * @param versionName 版本名 8.1.0_r33
92 | * @return 文件名
93 | */
94 | public String getCustomFileName(String versionName) {
95 | return versionName + "-" + classDescriptor.getFileName();
96 | }
97 |
98 | /**
99 | * 获取class 描述
100 | *
101 | * @return ClassDescriptor
102 | */
103 | @NotNull
104 | public ClassDescriptor getClassDescriptor() {
105 | return classDescriptor;
106 | }
107 |
108 | /**
109 | * 是否为 Android 的类
110 | *
111 | * @return bool
112 | */
113 | public boolean isAndroidClass() {
114 | return classDescriptor.isAndroidClass();
115 | }
116 |
117 | @Override
118 | public String toString() {
119 | return "class=" + classDescriptor.getClassName() + ", version=" + fullVersionName;
120 | }
121 |
122 | /**
123 | * 检查下载链接是否符合预期
124 | *
125 | * @param targetUrl 下载链接
126 | * @return bool
127 | */
128 | public boolean checkUrlAvailable(@Nullable String targetUrl) {
129 | if (targetUrl == null) {
130 | return false;
131 | }
132 | for (String ext : classDescriptor.getExt()) {
133 | if (targetUrl.contains(classDescriptor.getClassPath() + ext)) {
134 | return true;
135 | }
136 | }
137 | return false;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/entity/ListDoubleClickEvent.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.entity;
2 |
3 | import javax.swing.*;
4 | import java.awt.event.MouseAdapter;
5 | import java.awt.event.MouseEvent;
6 |
7 | /**
8 | * 处理 JList双击事件
9 | * @param JList数据集合T
10 | */
11 | public class ListDoubleClickEvent extends MouseAdapter {
12 |
13 | private final DoubleClickListener listener;
14 |
15 | public ListDoubleClickEvent(DoubleClickListener listener) {
16 | this.listener = listener;
17 | }
18 |
19 | @Override
20 | public void mouseClicked(MouseEvent e) {
21 | if (e.getClickCount() == 2) {
22 | JList theList = (JList) e.getSource();
23 | int index = theList.getSelectedIndex();
24 | if (this.listener != null) {
25 | this.listener.onDoubleClick(theList, index, (T) theList.getSelectedValue());
26 | }
27 | }
28 | }
29 |
30 | public interface DoubleClickListener {
31 | void onDoubleClick(JList jList, int position, T selectedValue);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/icons/PluginIcons.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.icons;
2 |
3 |
4 | import com.intellij.icons.AllIcons;
5 | import com.intellij.openapi.util.IconLoader;
6 |
7 | import javax.swing.*;
8 |
9 |
10 | /**
11 | * Created by pengwei on 2017/11/7.
12 | */
13 | public class PluginIcons {
14 |
15 | public static final Icon ICON_JAVA = intellijLoad("/fileTypes/java.png");
16 | public static final Icon ICON_DIFF = intellijLoad("/actions/diff.png");
17 | public static final Icon GradleSync = load("/icons/gradlesync.png");
18 | public static final Icon NATIVE = load("/icons/class.png");
19 |
20 | private static Icon load(String path) {
21 | try {
22 | return IconLoader.getIcon(path, PluginIcons.class);
23 | } catch (Exception e) {
24 | return null;
25 | }
26 | }
27 |
28 | private static Icon intellijLoad(String path) {
29 | return IconLoader.getIcon(path, AllIcons.class);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/provider/NativeMethodProvider.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.provider;
2 |
3 | import com.apkfuns.androidsourceviewer.action.FindNativeMethodAction;
4 | import com.apkfuns.androidsourceviewer.icons.PluginIcons;
5 | import com.apkfuns.androidsourceviewer.util.Utils;
6 | import com.intellij.codeHighlighting.Pass;
7 | import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
8 | import com.intellij.codeInsight.daemon.LineMarkerInfo;
9 | import com.intellij.codeInsight.daemon.LineMarkerProvider;
10 | import com.intellij.lang.jvm.JvmModifier;
11 | import com.intellij.openapi.actionSystem.*;
12 | import com.intellij.openapi.editor.markup.GutterIconRenderer;
13 | import com.intellij.psi.PsiElement;
14 | import com.intellij.psi.PsiMethod;
15 | import com.intellij.psi.PsiModifier;
16 | import com.intellij.psi.PsiModifierList;
17 | import org.jetbrains.annotations.NotNull;
18 | import org.jetbrains.annotations.Nullable;
19 |
20 | import java.awt.event.MouseEvent;
21 | import java.util.Collection;
22 | import java.util.List;
23 |
24 | /**
25 | * 支持Native方法跳转
26 | */
27 | public class NativeMethodProvider implements LineMarkerProvider, GutterIconNavigationHandler {
28 |
29 | @Nullable
30 | @Override
31 | public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement psiElement) {
32 | if (!(psiElement instanceof PsiMethod)) {
33 | return null;
34 | }
35 | // 只展示系统 native 方法
36 | PsiMethod method = (PsiMethod) psiElement;
37 | PsiModifierList psiModifierList = method.getModifierList();
38 | if (psiModifierList.hasExplicitModifier(PsiModifier.NATIVE) && method.getContainingClass() != null) {
39 | String packageName = method.getContainingClass().getQualifiedName();
40 | if (packageName != null && Utils.getVersionList(packageName) != null) {
41 | return new LineMarkerInfo<>(psiElement, psiElement.getTextRange(), PluginIcons.NATIVE, Pass.LINE_MARKERS,
42 | null, this,
43 | GutterIconRenderer.Alignment.LEFT);
44 | }
45 | }
46 | return null;
47 | }
48 |
49 | @Override
50 | public void collectSlowLineMarkers(@NotNull List list, @NotNull Collection collection) {
51 |
52 | }
53 |
54 | @Override
55 | public void navigate(MouseEvent mouseEvent, final PsiElement psiElement) {
56 | PsiMethod method = (PsiMethod) psiElement;
57 | AnActionEvent anActionEvent = AnActionEvent.createFromInputEvent(mouseEvent, "", new Presentation(), new DataContext() {
58 | @Nullable
59 | @Override
60 | public Object getData(String key) {
61 | if (CommonDataKeys.PROJECT.getName().equals(key)) {
62 | return psiElement.getProject();
63 | } else if (CommonDataKeys.PSI_ELEMENT.getName().equals(key)) {
64 | return psiElement;
65 | }
66 | return null;
67 | }
68 | });
69 | new FindNativeMethodAction(method).actionPerformed(anActionEvent);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/DownloadManager.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 | import com.apkfuns.androidsourceviewer.download.AndroidOSDownload;
4 | import com.apkfuns.androidsourceviewer.download.AndroidOSSearch;
5 | import com.apkfuns.androidsourceviewer.download.XrefSearch;
6 | import com.apkfuns.androidsourceviewer.download.XrefDownload;
7 | import com.apkfuns.androidsourceviewer.download.inteface.FileDownload;
8 | import com.apkfuns.androidsourceviewer.entity.DownloadTask;
9 | import com.intellij.openapi.application.ApplicationManager;
10 | import com.intellij.openapi.util.Pair;
11 | import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
12 | import org.jetbrains.annotations.NotNull;
13 |
14 | import java.io.File;
15 | import java.util.Arrays;
16 | import java.util.Collections;
17 | import java.util.List;
18 | import java.util.concurrent.Callable;
19 |
20 | /**
21 | * 下载管理
22 | * 执行下载策略
23 | */
24 | public class DownloadManager {
25 | // 单例
26 | private static volatile DownloadManager singleton;
27 |
28 | // 源码下载引擎List
29 | private List sourceEngine = Arrays.asList(new AndroidOSDownload(), new XrefDownload());
30 | // 源码搜索引擎List
31 | private List searchEngine = Arrays.asList(new AndroidOSSearch(), new XrefSearch());
32 |
33 | private DownloadManager() {
34 | }
35 |
36 | public static DownloadManager getInstance() {
37 | if (singleton == null) {
38 | synchronized (DownloadManager.class) {
39 | if (singleton == null) {
40 | singleton = new DownloadManager();
41 | }
42 | }
43 | }
44 | return singleton;
45 | }
46 |
47 | /**
48 | * 通过搜索下载源码
49 | *
50 | * @param downloadTasks 下载任务
51 | * @param outputFolder 保存的文件夹
52 | * @param result 下载结果回调
53 | * @param retryIfError 是否错误时重试
54 | * @param isSync 是否同步执行
55 | */
56 | public void searchFile(final DownloadTask[] downloadTasks, @NotNull final File outputFolder,
57 | @NonNull final DownloadResult result, final boolean retryIfError,
58 | final boolean isSync) {
59 | Runnable runnable = new Runnable() {
60 | @Override
61 | public void run() {
62 | if (callEngineDownload(searchEngine, downloadTasks, outputFolder, result, new Callable() {
63 | @Override
64 | public Boolean call() throws Exception {
65 | return !retryIfError;
66 | }
67 | })) {
68 | return;
69 | }
70 | result.onFailure("download fail", null);
71 | }
72 | };
73 | if (isSync) {
74 | runnable.run();
75 | } else {
76 | ApplicationManager.getApplication().executeOnPooledThread(runnable);
77 | }
78 | }
79 |
80 | /**
81 | * 下载源码
82 | *
83 | * @param downloadTasks 下载任务
84 | * @param outputFolder 保存文件夹
85 | * @param result 下载回调
86 | * @param isSync 是否异步
87 | */
88 | public void downloadFile(final DownloadTask[] downloadTasks, @NotNull final File outputFolder,
89 | @NonNull final DownloadResult result, final boolean isSync) {
90 | Runnable runnable = new Runnable() {
91 | @Override
92 | public void run() {
93 | try {
94 | if (callEngineDownload(sourceEngine, downloadTasks, outputFolder, result, null)) {
95 | return;
96 | }
97 | searchFile(downloadTasks, outputFolder, result, false, true);
98 | } catch (Exception e) {
99 | result.onFailure("download fail:" + e.getMessage(), e);
100 | }
101 | }
102 | };
103 | if (isSync) {
104 | runnable.run();
105 | } else {
106 | ApplicationManager.getApplication().executeOnPooledThread(runnable);
107 | }
108 | }
109 |
110 | /**
111 | * 调用下载引擎进行下载
112 | *
113 | * @param sourceEngine 引擎列表
114 | * @param downloadTasks 下载任务
115 | * @param outputFolder 保存文件夹
116 | * @param result 下载回调
117 | * @param callable 是否打断运行
118 | * @return 是否下载完成
119 | */
120 | private boolean callEngineDownload(List sourceEngine, DownloadTask[] downloadTasks, @NotNull File outputFolder,
121 | @NonNull DownloadResult result, Callable callable) {
122 | Collections.sort(sourceEngine);
123 | int index = 0;
124 | while (index < sourceEngine.size()) {
125 | FileDownload engine = sourceEngine.get(index);
126 | // 永远尝试第一个, 后续失败过多引擎不尝试, 避免浪费时间
127 | if (index > 0 && !engine.enable()) {
128 | index++;
129 | continue;
130 | }
131 | long startTime = System.currentTimeMillis();
132 | Pair> resultPair = engine.onDownload(downloadTasks, outputFolder);
133 | long costTime = System.currentTimeMillis() - startTime;
134 | engine.updatePriority(resultPair.first, costTime);
135 | if (resultPair.first) {
136 | result.onSuccess(resultPair.second);
137 | return true;
138 | }
139 | // 允许 callable 回调打断执行
140 | try {
141 | if (callable != null && callable.call()) {
142 | return false;
143 | }
144 | } catch (Exception e) {
145 | e.printStackTrace();
146 | }
147 | index++;
148 | }
149 | return false;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/DownloadResult.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Created by pengwei on 2017/11/5.
10 | */
11 | public interface DownloadResult {
12 | void onSuccess(@NotNull List output);
13 | void onFailure(@NotNull String msg, @Nullable Throwable throwable);
14 | }
15 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/FileUtil.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 | import com.intellij.util.io.HttpRequests;
4 | import com.intellij.util.net.NetUtils;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | import java.io.ByteArrayOutputStream;
8 | import java.io.IOException;
9 |
10 | public class FileUtil {
11 |
12 | /**
13 | * 下载内容到文本
14 | * @param url 请求链接
15 | * @return
16 | */
17 | public static String downloadContent(@NotNull String url) {
18 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
19 | try {
20 | HttpRequests.request(url).productNameAsUserAgent().connect(new HttpRequests.RequestProcessor() {
21 | public Object process(@NotNull HttpRequests.Request request) throws IOException {
22 | int contentLength = request.getConnection().getContentLength();
23 | NetUtils.copyStreamContent(null, request.getInputStream(), byteArrayOutputStream, contentLength);
24 | return null;
25 | }
26 | });
27 | return byteArrayOutputStream.toString();
28 | } catch (IOException e) {
29 | return null;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/GlobalStorageUtil.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 | import com.apkfuns.androidsourceviewer.app.Constant;
4 |
5 | import java.io.*;
6 | import java.util.Properties;
7 |
8 | /**
9 | * 全局存储工具类
10 | * Created by pengwei08 on 2015/7/23.
11 | */
12 | public class GlobalStorageUtil {
13 |
14 | private static String fileName = Constant.GLOBAL_CONFIG_FILE;
15 | private static Properties props = new Properties();
16 |
17 | static {
18 | initialization();
19 | }
20 |
21 | /**
22 | * 初始化
23 | */
24 | private static void initialization() {
25 | try {
26 | File proFile = new File(fileName);
27 | File parentFile = proFile.getParentFile();
28 | if (!parentFile.exists() && parentFile.mkdirs()) {
29 | Log.debug("create folder " + parentFile.getAbsolutePath());
30 | }
31 | if (!proFile.exists() && proFile.createNewFile()) {
32 | Log.debug("create file " + proFile.getName());
33 | }
34 | props.load(new FileInputStream(fileName));
35 | } catch (IOException e) {
36 | e.printStackTrace();
37 | }
38 | }
39 |
40 | /**
41 | * 保存
42 | */
43 | public static void put(String key, String value) {
44 | try {
45 | final OutputStream fos = new FileOutputStream(fileName);
46 | props.setProperty(key, value);
47 | props.store(fos, "");
48 | } catch (IOException e) {
49 | initialization();
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 | /**
55 | * 读取
56 | */
57 | public static String get(String key, String defaultValue) {
58 | String value = props.getProperty(key);
59 | return value == null ? defaultValue : value;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/HttpUtil.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 | import com.intellij.openapi.util.io.StreamUtil;
4 |
5 | import java.io.InputStream;
6 | import java.net.HttpURLConnection;
7 | import java.net.URL;
8 |
9 | public class HttpUtil {
10 | /**
11 | * Get 请求
12 | *
13 | * @param uri
14 | * @return
15 | * @throws Exception
16 | */
17 | public static String syncGet(String uri) throws Exception {
18 | URL url = new URL(uri);
19 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();
20 | conn.setConnectTimeout(5 * 1000);
21 | conn.setRequestMethod("GET");
22 | InputStream inStream = conn.getInputStream();
23 | return new String(StreamUtil.loadFromStream(inStream));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/Log.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 | import com.intellij.notification.*;
4 | import com.intellij.openapi.diagnostic.Logger;
5 |
6 |
7 | /**
8 | * 日志管理类
9 | */
10 | public class Log {
11 | private static final String NAME = "AndroidSourceViewer";
12 | private static final boolean IS_DEBUG = true;
13 | private static final Logger LOG = Logger.getInstance(Log.class);
14 |
15 | static {
16 | NotificationsConfiguration.getNotificationsConfiguration().register(NAME, NotificationDisplayType.NONE);
17 | }
18 |
19 | public static void debug(String text) {
20 | if (IS_DEBUG) {
21 | print(NotificationType.INFORMATION, text);
22 | LOG.debug(text);
23 | }
24 | }
25 |
26 | public static void warn(String text) {
27 | if (IS_DEBUG) {
28 | print(NotificationType.WARNING, text);
29 | LOG.warn(text);
30 | }
31 | }
32 |
33 | public static void error(String text) {
34 | print(NotificationType.ERROR, text);
35 | LOG.error(text);
36 | }
37 |
38 | public static void e(Throwable throwable) {
39 | error(throwable.getMessage());
40 | }
41 |
42 | private static void print(NotificationType type, String msg) {
43 | StackTraceElement ste = new Throwable().getStackTrace()[2];
44 | String prefix = ste.getFileName();
45 | int lineNum = ste.getLineNumber();
46 | String text = "[" + prefix + ":" + lineNum + "] " + msg;
47 | Notifications.Bus.notify(
48 | new Notification(NAME, NAME, text, type));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/NotificationUtils.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 | import com.apkfuns.androidsourceviewer.app.Constant;
4 | import com.intellij.notification.*;
5 | import com.intellij.openapi.application.ApplicationManager;
6 | import com.intellij.openapi.ui.Messages;
7 | import org.jetbrains.annotations.NotNull;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | import javax.swing.event.HyperlinkEvent;
11 | import java.net.URL;
12 |
13 | public class NotificationUtils {
14 |
15 | private static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.balloonGroup(Constant.TITLE);
16 |
17 | /**
18 | * show a Notification
19 | *
20 | * @param message
21 | * @param type
22 | */
23 | public static void showNotification(final String message, final NotificationType type,
24 | @Nullable final NotificationUrlClickListener listener) {
25 | ApplicationManager.getApplication().invokeLater(new Runnable() {
26 | @Override
27 | public void run() {
28 | Notification notification =
29 | NOTIFICATION_GROUP.createNotification(Constant.TITLE,
30 | message == null || message.trim().length() == 0 ? "[Empty]" : message,
31 | type, new NotificationListener() {
32 | @Override
33 | public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent hyperlinkEvent) {
34 | URL url = hyperlinkEvent.getURL();
35 | if (url == null) {
36 | return;
37 | }
38 | if (listener != null) {
39 | listener.onUrlClick(url);
40 | return;
41 | }
42 | if ("file".equals(url.getProtocol())) {
43 | if ("open".equals(url.getHost())) {
44 | String[] args = url.getQuery().split("=");
45 | if (args.length == 2) {
46 | Utils.openDirectory(args[1]);
47 | }
48 | }
49 | }
50 | }
51 | });
52 | Notifications.Bus.notify(notification);
53 | }
54 | });
55 | }
56 |
57 | /**
58 | * show a error Notification
59 | *
60 | * @param message
61 | */
62 | public static void errorNotification(final String message) {
63 | showNotification(message, NotificationType.ERROR, null);
64 | }
65 |
66 | /**
67 | * show a info Notification
68 | *
69 | * @param message
70 | */
71 | public static void infoNotification(final String message) {
72 | showNotification(message, NotificationType.INFORMATION, null);
73 | }
74 |
75 | public static void infoNotification(String message, NotificationUrlClickListener clickListener) {
76 | showNotification(message, NotificationType.INFORMATION, clickListener);
77 | }
78 |
79 | /**
80 | * 不会消失的弹出框
81 | *
82 | * @param message
83 | */
84 | public static void showMessageDialog(final String message) {
85 | ApplicationManager.getApplication().invokeLater(new Runnable() {
86 | @Override
87 | public void run() {
88 | Messages.showMessageDialog(message, "Error", Messages.getInformationIcon());
89 | }
90 | });
91 | }
92 |
93 | /**
94 | * error message dialog
95 | *
96 | * @param message
97 | */
98 | public static void errorMsgDialog(String message) {
99 | Messages.showMessageDialog(message, "Error", Messages.getInformationIcon());
100 | }
101 |
102 | public interface NotificationUrlClickListener {
103 | void onUrlClick(URL url);
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/ThreadPoolManager.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 |
4 | import java.util.concurrent.*;
5 |
6 | /**
7 | * Thread Pool Manager
8 | */
9 | public class ThreadPoolManager {
10 | private static ThreadPoolManager singleton;
11 | private ScheduledExecutorService serviceDelayManager;
12 | private ExecutorService ioActionPoolService;
13 |
14 | public static ThreadPoolManager getInstance() {
15 | if (singleton == null) {
16 | synchronized (ThreadPoolManager.class) {
17 | if (singleton == null) {
18 | singleton = new ThreadPoolManager();
19 | }
20 | }
21 | }
22 | return singleton;
23 | }
24 |
25 | private ThreadPoolManager() {
26 | int num = Runtime.getRuntime().availableProcessors();
27 | serviceDelayManager = Executors.newScheduledThreadPool(num <= 2 ? 1 : num / 2);
28 | ioActionPoolService =
29 | new ThreadPoolExecutor(num, num, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue());
30 | }
31 |
32 | public synchronized ScheduledFuture addTaskDelay(Runnable runnable, long delay) {
33 | return serviceDelayManager.schedule(runnable, delay, TimeUnit.MILLISECONDS);
34 | }
35 |
36 | public void addTask(Runnable runnable) {
37 | ioActionPoolService.execute(runnable);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/util/Utils.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.util;
2 |
3 | import com.apkfuns.androidsourceviewer.app.Constant;
4 | import com.intellij.openapi.actionSystem.AnActionEvent;
5 | import com.intellij.openapi.actionSystem.LangDataKeys;
6 | import com.intellij.openapi.application.ApplicationManager;
7 | import com.intellij.openapi.fileEditor.FileEditorManager;
8 | import com.intellij.openapi.fileEditor.FileEditorProvider;
9 | import com.intellij.openapi.fileEditor.OpenFileDescriptor;
10 | import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
11 | import com.intellij.openapi.project.Project;
12 | import com.intellij.openapi.util.text.StringUtil;
13 | import com.intellij.openapi.vfs.LocalFileSystem;
14 | import com.intellij.openapi.vfs.VirtualFile;
15 | import com.intellij.psi.*;
16 | import org.jetbrains.annotations.NotNull;
17 | import org.jetbrains.annotations.Nullable;
18 |
19 | import java.awt.*;
20 | import java.io.File;
21 | import java.io.IOException;
22 | import java.net.*;
23 | import java.util.Map;
24 |
25 | /**
26 | * Created by pengwei on 2017/11/5.
27 | */
28 | public class Utils {
29 |
30 | public static boolean isEmpty(String text) {
31 | return StringUtil.isEmpty(text);
32 | }
33 |
34 | @Nullable
35 | public static String getClassPath(@NotNull AnActionEvent event) {
36 | String packageName = null;
37 | PsiElement element = event.getData(LangDataKeys.PSI_ELEMENT);
38 | if (element == null) {
39 | return packageName;
40 | }
41 | if (element instanceof PsiClass) {
42 | PsiClass cls = (PsiClass) element;
43 | if (cls.getContainingClass() != null) {
44 | // 排除内部类的情况
45 | packageName = cls.getContainingClass().getQualifiedName();
46 | } else {
47 | packageName = cls.getQualifiedName();
48 | }
49 | Log.debug("class => " + packageName);
50 | } else if (element instanceof PsiMethod) {
51 | PsiMethod method = (PsiMethod) element;
52 | Log.debug("method => " + method.getName() + " # "
53 | + method.getContainingClass().getQualifiedName());
54 | packageName = method.getContainingClass().getQualifiedName();
55 | } else if (element instanceof PsiVariable) {
56 | PsiVariable psiVariable = (PsiVariable) element;
57 | packageName = psiVariable.getType().getCanonicalText();
58 | // 去除泛型
59 | if (!Utils.isEmpty(packageName)) {
60 | packageName = packageName.replaceAll("<.*>", "");
61 | }
62 | // FIXME: 2017/11/11 变量对应类是内部类会有问题
63 | Log.debug("PsiVariable:" + psiVariable.getType().getCanonicalText());
64 | } else {
65 | Log.debug("cls = " + element.getClass());
66 | }
67 | return packageName;
68 | }
69 |
70 | /**
71 | * 打开类文件
72 | *
73 | * @param filePath
74 | * @param project
75 | */
76 | public static void openFileInPanel(final String filePath, final Project project) {
77 | ApplicationManager.getApplication().invokeLater(new Runnable() {
78 | @Override
79 | public void run() {
80 | VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePath);
81 | if (file != null && file.isValid()) {
82 | FileEditorProvider[] providers = FileEditorProviderManager.getInstance()
83 | .getProviders(project, file);
84 | if (providers.length != 0) {
85 | OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file);
86 | FileEditorManager.getInstance(project).openTextEditor(descriptor, true);
87 | }
88 | }
89 | }
90 | });
91 | }
92 |
93 | /**
94 | * 根据包名前缀选择版本展示列表
95 | *
96 | * @param packageName
97 | * @return
98 | */
99 | @Nullable
100 | public static String[] getVersionList(@NotNull String packageName) {
101 | for (String prefix : Constant.ANDROID_PACKAGE_PREFIX) {
102 | if (packageName.startsWith(prefix)) {
103 | return Constant.ANDROID_VERSION_LIST;
104 | }
105 | }
106 | for (String prefix : Constant.JAVA_PACKAGE_PREFIX) {
107 | if (packageName.startsWith(prefix)) {
108 | return Constant.JAVA_VERSION_LIST;
109 | }
110 | }
111 | return null;
112 | }
113 |
114 | /**
115 | * 下载链接
116 | *
117 | * @param packageName
118 | * @param classPath
119 | * @param version
120 | * @return
121 | */
122 | public static String getDownloadUrl(String packageName, String classPath, String version) {
123 | for (String prefix : Constant.ANDROID_PACKAGE_PREFIX) {
124 | if (packageName.startsWith(prefix)) {
125 | return String.format(Constant.DOWNLOAD_BASE_PATH, version, classPath);
126 | }
127 | }
128 | for (String prefix : Constant.JAVA_PACKAGE_PREFIX) {
129 | if (packageName.startsWith(prefix)) {
130 | return String.format(Constant.JAVA_DOWNLOAD_BASE_PATH, version, classPath);
131 | }
132 | }
133 | return null;
134 | }
135 |
136 | /**
137 | * 打开文件夹
138 | */
139 | public static void openDirectory(String directory) {
140 | File file = new File(directory);
141 | if (!file.exists() || !file.isDirectory()) {
142 | return;
143 | }
144 | try {
145 | Desktop.getDesktop().open(file);
146 | } catch (IOException e) {
147 | e.printStackTrace();
148 | }
149 | }
150 |
151 | /**
152 | * 创建可点击的链接
153 | *
154 | * @param path
155 | * @return
156 | */
157 | public static String createClickableUrl(String path) {
158 | return "打开文件夹 ";
159 | }
160 |
161 | /**
162 | * 是否为 Android 类
163 | *
164 | * @param packageName 包名
165 | * @return bool
166 | */
167 | public static boolean isAndroidClass(String packageName) {
168 | for (String prefix : Constant.ANDROID_PACKAGE_PREFIX) {
169 | if (packageName.startsWith(prefix)) {
170 | return true;
171 | }
172 | }
173 | return false;
174 | }
175 |
176 | /**
177 | * 检测服务是否正常
178 | *
179 | * @param url 请求链接
180 | * @return bool
181 | */
182 | public static boolean isConnected(String url) {
183 | try {
184 | HttpURLConnection conn =
185 | (HttpURLConnection) new URL(url).openConnection();
186 | conn.setConnectTimeout(3000);
187 | conn.setReadTimeout(3000);
188 | conn.setRequestMethod("GET");
189 | return conn.getResponseCode() == HttpURLConnection.HTTP_OK;
190 | } catch (IOException e) {
191 | e.printStackTrace();
192 | }
193 | return false;
194 | }
195 |
196 | /**
197 | * 匹配最合适的版本
198 | *
199 | * @param version 8.1.0_r3
200 | * @return 8.0.0_r4
201 | */
202 | public static String matchVersion(Map matchMap, String version) {
203 | // 优先全匹配
204 | for (String key: matchMap.keySet()) {
205 | if (version.startsWith(key)) {
206 | return matchMap.get(key);
207 | }
208 | }
209 | // 配置不到的情况下选择大版本, 如8.1配置8.0.0
210 | String[] versionGroup = version.split("\\.");
211 | if (versionGroup.length > 0) {
212 | for (String key: matchMap.keySet()) {
213 | if (key.startsWith(versionGroup[0])) {
214 | return matchMap.get(key);
215 | }
216 | }
217 | }
218 | return version;
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/widget/LineLayout.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.widget;
2 |
3 | import java.awt.*;
4 |
5 | /**
6 | * 水平布局的 LayoutManager
7 | * Created by pengwei on 17/2/20.
8 | */
9 | public class LineLayout implements LayoutManager {
10 |
11 | private int padding;
12 |
13 | public LineLayout() {
14 | padding = 1;
15 | }
16 |
17 | public LineLayout(int padding) {
18 | this.padding = padding;
19 | }
20 |
21 | @Override
22 | public void addLayoutComponent(String name, Component comp) {
23 |
24 | }
25 |
26 | @Override
27 | public void removeLayoutComponent(Component comp) {
28 |
29 | }
30 |
31 | @Override
32 | public Dimension preferredLayoutSize(Container target) {
33 | synchronized (target.getTreeLock()) {
34 | Dimension dim = new Dimension(0, 0);
35 | int count = target.getComponentCount();
36 | for (int i = 0; i < count; i++) {
37 | Component m = target.getComponent(i);
38 | Dimension d = m.getPreferredSize();
39 | dim.width = Math.max(dim.width, d.width);
40 | dim.height += d.height + padding;
41 | }
42 | Insets insets = target.getInsets();
43 | dim.width += insets.left + insets.right + padding * 2;
44 | dim.height += insets.top + insets.bottom + padding * 2;
45 | return dim;
46 | }
47 | }
48 |
49 | @Override
50 | public Dimension minimumLayoutSize(Container parent) {
51 | return new Dimension(0, 0);
52 | }
53 |
54 | @Override
55 | public void layoutContainer(Container parent) {
56 | Insets insets = parent.getInsets();
57 | int maxWidth = parent.getWidth() - (insets.left + insets.right);
58 | int count = parent.getComponentCount();
59 | int height = 0;
60 | int gap = padding;
61 | for (int i = 0; i < count; i++) {
62 | Component component = parent.getComponent(i);
63 | if (component.isVisible()) {
64 | Dimension size = component.getPreferredSize();
65 | component.setBounds(gap, height, maxWidth - gap * 2, size.height);
66 | height += size.height + gap * 2;
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/com/apkfuns/androidsourceviewer/widget/PopListView.java:
--------------------------------------------------------------------------------
1 | package com.apkfuns.androidsourceviewer.widget;
2 |
3 | import com.apkfuns.androidsourceviewer.util.Utils;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.actionSystem.AnActionEvent;
6 | import com.intellij.openapi.actionSystem.DefaultActionGroup;
7 | import com.intellij.openapi.project.DumbAware;
8 | import com.intellij.openapi.project.Project;
9 | import com.intellij.openapi.ui.popup.JBPopupFactory;
10 | import com.intellij.openapi.ui.popup.ListPopup;
11 | import org.jetbrains.annotations.NotNull;
12 |
13 | /**
14 | * Created by pengwei on 2017/11/7.
15 | */
16 | public class PopListView {
17 |
18 | private AnActionEvent anActionEvent;
19 | private ListPopup listPopup;
20 | private JBPopupFactory.ActionSelectionAid aid;
21 |
22 | public PopListView(AnActionEvent anActionEvent) {
23 | this.anActionEvent = anActionEvent;
24 | aid = JBPopupFactory.ActionSelectionAid.NUMBERING;
25 | }
26 |
27 | /**
28 | * create ListPop
29 | *
30 | * @param title
31 | * @param data
32 | * @param listener
33 | */
34 | public void createList(String title, String[] data, OnItemClickListener listener) {
35 | DefaultActionGroup group = new DefaultActionGroup();
36 | if (data != null && data.length > 0) {
37 | for (int i = 0; i < data.length; i++) {
38 | if (!Utils.isEmpty(data[i])) {
39 | if (data[i].contains("-")) {
40 | group.add(new VersionListItemAction(i, data[i], listener));
41 | } else {
42 | group.addSeparator(data[i]);
43 | }
44 | }
45 | }
46 | }
47 | listPopup = JBPopupFactory.getInstance().createActionGroupPopup(title, group,
48 | anActionEvent.getDataContext(), aid, true, null, -1, null, "unknown");
49 | show();
50 | }
51 |
52 | public void dispose() {
53 | if (listPopup != null) {
54 | listPopup.dispose();
55 | }
56 | }
57 |
58 | private void show() {
59 | if (listPopup == null || anActionEvent == null) {
60 | return;
61 | }
62 | Project project = anActionEvent.getProject();
63 | if (project != null) {
64 | listPopup.showCenteredInCurrentWindow(project);
65 | } else {
66 | listPopup.showInBestPositionFor(anActionEvent.getDataContext());
67 | }
68 | }
69 |
70 | public interface OnItemClickListener {
71 | void OnItemClick(int position, String value);
72 | }
73 |
74 | /**
75 | * Android版本选择
76 | */
77 | private static class VersionListItemAction extends AnAction implements DumbAware {
78 |
79 | private int index;
80 | private String value;
81 | private OnItemClickListener clickListener;
82 |
83 | public VersionListItemAction(int index, String value, OnItemClickListener clickListener) {
84 | super(value);
85 | this.index = index;
86 | this.value = value;
87 | this.clickListener = clickListener;
88 | }
89 |
90 | @Override
91 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
92 | if (clickListener != null) {
93 | String versionName = this.value.substring(this.value.indexOf("-") + 1).trim();
94 | clickListener.OnItemClick(this.index, versionName);
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------