feat(gui): add 'go to main activity' action (#1423)(PR #1965)

* implemented feature: 'go to main activity'
* fix code formatting (added braces to if statements)
* search main activity by original class name, handle exceptions

---------

Co-authored-by: Skylot <skylot@gmail.com>
This commit is contained in:
Mino
2023-07-29 21:03:25 +01:00
committed by GitHub
parent ef0c09ca5b
commit d9692a4f5f
11 changed files with 189 additions and 0 deletions
@@ -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);
@@ -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<ResourceFile> 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<ResourceFile> 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;
}
}
@@ -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
@@ -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
@@ -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
@@ -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=다음
@@ -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
@@ -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=Вперед
@@ -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=下一个
@@ -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=下一個
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#6E6E6E" width="16" height="16" viewBox="0 0 512 512" id="_x30_1" version="1.1" xml:space="preserve"><path d="M500.284,227.716l-216-216c-15.621-15.621-40.948-15.621-56.568,0h0l-216,216c-15.621,15.621-15.621,40.948,0,56.568 C23.69,296.258,41.363,299.042,56,292.659V452c0,33.137,26.863,60,60,60h280c33.137,0,60-26.863,60-60V292.659 c14.637,6.383,32.31,3.599,44.284-8.375h0C515.905,268.663,515.905,243.337,500.284,227.716z M326,432c0,11.046-8.954,20-20,20H206 c-11.046,0-20-8.954-20-20V312c0-11.046,8.954-20,20-20h100c11.046,0,20,8.954,20,20V432z"/></svg>

After

Width:  |  Height:  |  Size: 645 B