diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 5886cfda1..a30b76b3c 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -79,6 +79,7 @@ import ch.qos.logback.classic.Level; import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; +import jadx.api.JavaClass; import jadx.api.JavaNode; import jadx.api.ResourceFile; import jadx.api.plugins.events.IJadxEvents; @@ -133,6 +134,7 @@ import jadx.gui.ui.treenodes.SummaryNode; import jadx.gui.update.JadxUpdate; import jadx.gui.update.JadxUpdate.IUpdateCallback; import jadx.gui.update.data.Release; +import jadx.gui.utils.AndroidManifestParser; import jadx.gui.utils.CacheObject; import jadx.gui.utils.FontUtils; import jadx.gui.utils.ILoadListener; @@ -167,6 +169,7 @@ public class MainWindow extends JFrame { private static final ImageIcon ICON_SEARCH = UiUtils.openSvgIcon("ui/find"); private static final ImageIcon ICON_FIND = UiUtils.openSvgIcon("ui/ejbFinderMethod"); private static final ImageIcon ICON_COMMENT_SEARCH = UiUtils.openSvgIcon("ui/usagesFinder"); + private static final ImageIcon ICON_MAIN_ACTIVITY = UiUtils.openSvgIcon("ui/home"); private static final ImageIcon ICON_BACK = UiUtils.openSvgIcon("ui/left"); private static final ImageIcon ICON_FORWARD = UiUtils.openSvgIcon("ui/right"); private static final ImageIcon ICON_QUARK = UiUtils.openSvgIcon("ui/quark"); @@ -1027,6 +1030,33 @@ public class MainWindow extends JFrame { commentSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_SEMICOLON, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)); + Action gotoMainActivityAction = new AbstractAction(NLS.str("menu.goto_main_activity"), ICON_MAIN_ACTIVITY) { + @Override + public void actionPerformed(ActionEvent ev) { + AndroidManifestParser parser = new AndroidManifestParser(getWrapper().getResources()); + if (!parser.isResourceFound()) { + JOptionPane.showMessageDialog(MainWindow.this, + NLS.str("error_dialog.not_found", "AndroidManifest.xml"), + NLS.str("error_dialog.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + try { + JavaClass mainActivityClass = parser.getMainActivity(getWrapper()); + tabbedPane.codeJump(getCacheObject().getNodeCache().makeFrom(mainActivityClass)); + } catch (Exception e) { + LOG.error("Main activity not found", e); + JOptionPane.showMessageDialog(MainWindow.this, + NLS.str("error_dialog.not_found", "Main Activity"), + NLS.str("error_dialog.title"), + JOptionPane.ERROR_MESSAGE); + } + } + }; + gotoMainActivityAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.goto_main_activity")); + gotoMainActivityAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_M, + UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)); + ActionHandler decompileAllAction = new ActionHandler(ev -> requestFullDecompilation()); decompileAllAction.setNameAndDesc(NLS.str("menu.decompile_all")); decompileAllAction.setIcon(ICON_DECOMPILE_ALL); @@ -1135,6 +1165,7 @@ public class MainWindow extends JFrame { nav.add(textSearchAction); nav.add(clsSearchAction); nav.add(commentSearchAction); + nav.add(gotoMainActivityAction); nav.addSeparator(); nav.add(backAction); nav.add(forwardAction); @@ -1199,6 +1230,7 @@ public class MainWindow extends JFrame { toolbar.add(textSearchAction); toolbar.add(clsSearchAction); toolbar.add(commentSearchAction); + toolbar.add(gotoMainActivityAction); toolbar.addSeparator(); toolbar.add(backAction); toolbar.add(forwardAction); @@ -1220,6 +1252,7 @@ public class MainWindow extends JFrame { textSearchAction.setEnabled(loaded); clsSearchAction.setEnabled(loaded); commentSearchAction.setEnabled(loaded); + gotoMainActivityAction.setEnabled(loaded); backAction.setEnabled(loaded); forwardAction.setEnabled(loaded); syncAction.setEnabled(loaded); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/AndroidManifestParser.java b/jadx-gui/src/main/java/jadx/gui/utils/AndroidManifestParser.java new file mode 100644 index 000000000..122830136 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/AndroidManifestParser.java @@ -0,0 +1,139 @@ +package jadx.gui.utils; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import io.reactivex.annotations.Nullable; + +import jadx.api.JavaClass; +import jadx.api.ResourceFile; +import jadx.api.ResourceType; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.xmlgen.XmlSecurity; +import jadx.gui.JadxWrapper; + +public class AndroidManifestParser { + private final Document mXmlDocument; + + public AndroidManifestParser(List resourceFiles) { + mXmlDocument = parseAndroidManifest(getAndroidManifest(resourceFiles)); + } + + public boolean isResourceFound() { + return mXmlDocument != null; + } + + public JavaClass getMainActivity(JadxWrapper decompiler) { + final String mainActivityName = getMainActivityName(); + if (mainActivityName == null) { + throw new JadxRuntimeException("Failed to get main activity name from manifest"); + } + JavaClass javaClass = decompiler.searchJavaClassByOrigClassName(mainActivityName); + if (javaClass == null) { + throw new JadxRuntimeException("Failed to find main activity class: " + mainActivityName); + } + return javaClass; + } + + private String getMainActivityName() { + String mainActivityName = getMainActivityNameThroughActivityTag(); + if (mainActivityName == null) { + mainActivityName = getMainActivityNameThroughActivityAliasTag(); + } + return mainActivityName; + } + + private String getMainActivityNameThroughActivityAliasTag() { + NodeList activityAliasNodes = mXmlDocument.getElementsByTagName("activity-alias"); + for (int i = 0; i < activityAliasNodes.getLength(); i++) { + Element activityElement = (Element) activityAliasNodes.item(i); + if (isMainActivityElement(activityElement)) { + return activityElement.getAttribute("android:targetActivity"); + } + } + return null; + } + + private String getMainActivityNameThroughActivityTag() { + NodeList activityNodes = mXmlDocument.getElementsByTagName("activity"); + for (int i = 0; i < activityNodes.getLength(); i++) { + Element activityElement = (Element) activityNodes.item(i); + if (isMainActivityElement(activityElement)) { + return activityElement.getAttribute("android:name"); + } + } + return null; + } + + private boolean isMainActivityElement(Element element) { + NodeList intentFilterNodes = element.getElementsByTagName("intent-filter"); + for (int j = 0; j < intentFilterNodes.getLength(); j++) { + Element intentFilterElement = (Element) intentFilterNodes.item(j); + NodeList actionNodes = intentFilterElement.getElementsByTagName("action"); + NodeList categoryNodes = intentFilterElement.getElementsByTagName("category"); + + boolean isMainAction = false; + boolean isLauncherCategory = false; + + for (int k = 0; k < actionNodes.getLength(); k++) { + Element actionElement = (Element) actionNodes.item(k); + String actionName = actionElement.getAttribute("android:name"); + if ("android.intent.action.MAIN".equals(actionName)) { + isMainAction = true; + break; + } + } + + for (int k = 0; k < categoryNodes.getLength(); k++) { + Element categoryElement = (Element) categoryNodes.item(k); + String categoryName = categoryElement.getAttribute("android:name"); + if ("android.intent.category.LAUNCHER".equals(categoryName)) { + isLauncherCategory = true; + break; + } + } + + if (isMainAction && isLauncherCategory) { + return true; + } + } + return false; + } + + @Nullable + public static ResourceFile getAndroidManifest(List resources) { + // TODO: taken from ExportGradleTask.java: also use this function there ? + return resources.stream() + .filter(resourceFile -> resourceFile.getType() == ResourceType.MANIFEST) + .findFirst() + .orElse(null); + } + + public static Document parseAndroidManifest(ResourceFile androidManifest) { + if (androidManifest == null) { + return null; + } + + // TODO: taken from ExportGradleProject.java: also use this function there ? + Document androidManifestDocument; + try { + String xmlContent = androidManifest.loadContent().getText().getCodeStr(); + DocumentBuilder builder = XmlSecurity.getSecureDbf().newDocumentBuilder(); + androidManifestDocument = builder.parse(new InputSource(new StringReader(xmlContent))); + androidManifestDocument.getDocumentElement().normalize(); + } catch (ParserConfigurationException | SAXException | IOException ex) { + throw new RuntimeException(ex); + } + return androidManifestDocument; + } +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index ae682a864..fbe0218f9 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -14,6 +14,7 @@ menu.navigation=Navigation menu.text_search=Textsuche menu.class_search=Klassen-Suche menu.comment_search=Kommentar suchen +#menu.goto_main_activity= menu.tools=Tools #menu.plugins=Plugins #menu.decompile_all=Decompile all classes @@ -61,6 +62,7 @@ progress.decompile=Dekompilieren progress.canceling=Breche ab error_dialog.title=Fehler +#error_dialog.not_found= search.previous=Zurück search.next=Weiter diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 9b957904c..37aeee703 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -14,6 +14,7 @@ menu.navigation=Navigation menu.text_search=Text search menu.class_search=Class search menu.comment_search=Comment search +menu.goto_main_activity=Go to main Activity menu.tools=Tools menu.plugins=Plugins menu.decompile_all=Decompile all classes @@ -61,6 +62,7 @@ progress.decompile=Decompiling progress.canceling=Canceling error_dialog.title=Error +error_dialog.not_found=%s not found search.previous=Previous search.next=Next diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index b83f5ff65..ecf4252aa 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -14,6 +14,7 @@ menu.navigation=Navegación menu.text_search=Buscar texto menu.class_search=Buscar clase #menu.comment_search=Comment search +#menu.goto_main_activity= menu.tools=Herramientas #menu.plugins=Plugins #menu.decompile_all=Decompile all classes @@ -61,6 +62,7 @@ progress.decompile=Decompiling #progress.canceling=Canceling #error_dialog.title= +#error_dialog.not_found= search.previous=Anterior search.next=Siguiente diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 4023e946d..270cf5766 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -14,6 +14,7 @@ menu.navigation=네비게이션 menu.text_search=텍스트 검색 menu.class_search=클래스 검색 menu.comment_search=주석 검색 +#menu.goto_main_activity= menu.tools=도구 #menu.plugins=Plugins #menu.decompile_all=Decompile all classes @@ -61,6 +62,7 @@ progress.decompile=디컴파일 중 progress.canceling=취소 중 error_dialog.title=오류 +#error_dialog.not_found= search.previous=이전 search.next=다음 diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index 30232464e..185a12fdf 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -14,6 +14,7 @@ menu.navigation=Navegação menu.text_search=Buscar por texto menu.class_search=Buscar por classe menu.comment_search=Busca por comentário +#menu.goto_main_activity= menu.tools=Ferramentas #menu.plugins=Plugins #menu.decompile_all=Decompile all classes @@ -61,6 +62,7 @@ progress.decompile=Descompilando progress.canceling=Cancelando error_dialog.title=Erro +#error_dialog.not_found= search.previous=Anterior search.next=Próximo diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index d59b5e04f..534f5dc6f 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -14,6 +14,7 @@ menu.navigation=Навигация menu.text_search=Поиск строк menu.class_search=Поиск классов menu.comment_search=Поиск комментариев +#menu.goto_main_activity= menu.tools=Инструменты #menu.plugins=Plugins #menu.decompile_all=Decompile all classes @@ -61,6 +62,7 @@ progress.decompile=Декомпиляция progress.canceling=Прерывание error_dialog.title=Ошибка +#error_dialog.not_found= search.previous=Назад search.next=Вперед diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index d60aa1b50..bad4e3133 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -14,6 +14,7 @@ menu.navigation=导航 menu.text_search=文本搜索 menu.class_search=类名搜索 menu.comment_search=注释搜索 +#menu.goto_main_activity= menu.tools=工具 menu.plugins=插件 menu.decompile_all=反编译所有类 @@ -61,6 +62,7 @@ progress.decompile=反编译中 progress.canceling=正在取消 error_dialog.title=错误 +#error_dialog.not_found= search.previous=上一个 search.next=下一个 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index 3c847157a..ad1f305ae 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -14,6 +14,7 @@ menu.navigation=瀏覽 menu.text_search=文字搜尋 menu.class_search=類別搜尋 menu.comment_search=註解搜尋 +#menu.goto_main_activity= menu.tools=工具 menu.plugins=外掛程式 menu.decompile_all=反編譯所有類別 @@ -61,6 +62,7 @@ progress.decompile=正在反編譯 progress.canceling=正在取消 error_dialog.title=錯誤 +#error_dialog.not_found= search.previous=上一個 search.next=下一個 diff --git a/jadx-gui/src/main/resources/icons/ui/home.svg b/jadx-gui/src/main/resources/icons/ui/home.svg new file mode 100644 index 000000000..cbc5e7840 --- /dev/null +++ b/jadx-gui/src/main/resources/icons/ui/home.svg @@ -0,0 +1 @@ +