├── .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 | ![](https://img.shields.io/badge/AndroidSourceViewer-1.2.1-blue.svg) 4 | 5 | Android Studio 在线查看 Android 和 Java 指定版本源码插件 6 | 7 | ## Features 8 | * 支持查看 Android / Java 任意版本源码 9 | * 支持对比 Android / Java 任意两个版本源码差异 10 | * 支持 Android 官网文档查看和方法定位 11 | * 支持 Native 方法源码查看 12 | 13 | ## Screenshot 14 | ![](./screenshot/ss1.png)
15 | ![](./screenshot/ss2.png)
16 | ![](./screenshot/ss3.png)
17 | ![](./screenshot/ss4.gif)
18 | ![](./screenshot/ss5.gif)
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 | ![](./screenshot/AndroidSourceViewer讨论群群二维码.png) 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 |
    3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
    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 | --------------------------------------------------------------------------------