diff --git a/app/src/main/java/io/github/a13e300/tools/StethoxAppInterceptor.java b/app/src/main/java/io/github/a13e300/tools/StethoxAppInterceptor.java index b2043df..c037eff 100644 --- a/app/src/main/java/io/github/a13e300/tools/StethoxAppInterceptor.java +++ b/app/src/main/java/io/github/a13e300/tools/StethoxAppInterceptor.java @@ -109,6 +109,7 @@ private synchronized void initializeStetho(Context context) throws InterruptedEx try { scope.defineProperty("activities", null, Utils.class.getDeclaredMethod("getActivities", ScriptableObject.class), null, ScriptableObject.READONLY); scope.defineProperty("current", null, Utils.class.getDeclaredMethod("getCurrentActivity", ScriptableObject.class), null, ScriptableObject.READONLY); + scope.defineProperty("fragments", null, Utils.class.getDeclaredMethod("getFragments", ScriptableObject.class), null, ScriptableObject.READONLY); ScriptableObject.defineClass(scope, HookFunction.class); ScriptableObject.defineClass(scope, UnhookFunction.class); ScriptableObject.defineClass(scope, HookParam.class); diff --git a/app/src/main/java/io/github/a13e300/tools/Utils.java b/app/src/main/java/io/github/a13e300/tools/Utils.java index dccaeab..d382844 100644 --- a/app/src/main/java/io/github/a13e300/tools/Utils.java +++ b/app/src/main/java/io/github/a13e300/tools/Utils.java @@ -6,9 +6,14 @@ import org.mozilla.javascript.Context; import org.mozilla.javascript.ScriptableObject; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import de.robv.android.xposed.XposedHelpers; +import io.github.a13e300.tools.node.Node; public class Utils { public static ActivityThread getActivityThread() { @@ -31,11 +36,74 @@ public static Activity[] getActivities(ScriptableObject ignore) { public static Activity getCurrentActivity(ScriptableObject ignore) { var activities = (Map) XposedHelpers.getObjectField(getActivityThread(), "mActivities"); var acr = activities.values().stream().filter(v -> - !XposedHelpers.getBooleanField(v, "paused") + !XposedHelpers.getBooleanField(v, "paused") ).findFirst(); return acr.map(o -> (Activity) XposedHelpers.getObjectField(o, "activity")).orElse(null); } + public static Object getSupportFragmentManager(ScriptableObject ignore) { + var currentActivity = getCurrentActivity(ignore); + if (currentActivity == null) return null; + + var clazz = XposedHelpers.findClass("androidx.fragment.app.FragmentActivity", currentActivity.getClassLoader()); + Method method = XposedHelpers.findMethodExactIfExists(clazz, "getSupportFragmentManager"); + try { + return method.invoke(currentActivity); + } catch (InvocationTargetException | IllegalAccessException e) { + return null; + } + } + + public static ArrayList> getFragments(ScriptableObject ignore) { + Object manager = getSupportFragmentManager(ignore); + if (manager == null) return null; + + var rootNodes = new ArrayList>(); + var nodeStack = new ArrayList>>(); + nodeStack.add(rootNodes); + + var fragmentMgrStack = new ArrayList<>(); + fragmentMgrStack.add(manager); + + var loader = manager.getClass().getClassLoader(); + var fragmentMgrClazz = XposedHelpers.findClass("androidx.fragment.app.FragmentManager", loader); + var fragmentClazz = XposedHelpers.findClass("androidx.fragment.app.Fragment", loader); + + while (!fragmentMgrStack.isEmpty()) { + Object currentManager = fragmentMgrStack.remove(fragmentMgrStack.size() - 1); + List> currentNode = nodeStack.remove(nodeStack.size() - 1); + // Object currentManager = fragmentMgrStack.remove(0); + // List> currentNode = nodeStack.remove(0); + + try { + Method method = XposedHelpers.findMethodExactIfExists(fragmentMgrClazz, "getFragments"); + var fragments = (List) method.invoke(currentManager); + + for (int i = 0; i < fragments.size(); i++) { + Object fragment = fragments.get(i); + + var childNode = new Node<>(); + childNode.index = i; + childNode.value = fragment; + childNode.children = new ArrayList<>(); + currentNode.add(childNode); + + Method childMethod = XposedHelpers.findMethodExactIfExists(fragmentClazz, "getChildFragmentManager"); + Object childManager = childMethod.invoke(fragment); + if (childManager == null) continue; + + fragmentMgrStack.add(childManager); + nodeStack.add(childNode.children); + } + + } catch (InvocationTargetException | IllegalAccessException e) { + // Ignore: the exception and continue with the next fragment + } + } + + return rootNodes; + } + public static String getStackTrace(boolean hide) { var sb = new StringBuilder(); var st = Thread.currentThread().getStackTrace(); diff --git a/app/src/main/java/io/github/a13e300/tools/node/Node.java b/app/src/main/java/io/github/a13e300/tools/node/Node.java new file mode 100644 index 0000000..7b6b8f0 --- /dev/null +++ b/app/src/main/java/io/github/a13e300/tools/node/Node.java @@ -0,0 +1,27 @@ +package io.github.a13e300.tools.node; + +import androidx.annotation.NonNull; + +import java.util.List; + +public class Node { + public int index; + public T value; + public List> children; + + public String getValueName() { + return value == null ? "" : value.getClass().getName(); + } + + @NonNull + @Override + public String toString() { + // json format + return "{" + + "\"index\":" + index + + ",\"name\":\"" + getValueName() + "\"" + + ",\"value\":\"" + value + "\"" + + ",\"children\":" + children + + "}"; + } +}