diff --git a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCreateCLHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCreateCLHooker.java index b5c1bec0..3b71d392 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCreateCLHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCreateCLHooker.java @@ -160,6 +160,31 @@ public class LoadedApkCreateCLHooker implements XposedInterface.Hooker { return isFirstPackage; } }); + + ClassLoader defaultClassLoaderForCompat = classLoader; + if (defaultClassLoaderField != null) { + try { + var defaultCl = (ClassLoader) defaultClassLoaderField.get(loadedApk); + if (defaultCl != null) { + defaultClassLoaderForCompat = defaultCl; + } + } catch (Throwable ignored) { + } + } + Object appComponentFactory = null; + try { + appComponentFactory = XposedHelpers.getObjectField(loadedApk, "mAppComponentFactory"); + } catch (Throwable ignored) { + } + LSPosedContext.callOnPackageReady( + loadedApk.getPackageName(), + loadedApk.getApplicationInfo(), + isFirstPackage, + defaultClassLoaderForCompat, + classLoader, + appComponentFactory + ); + Hookers.logD("callOnPackageReady via LoadedApkCreateCLHooker: " + loadedApk.getPackageName()); } catch (Throwable t) { Hookers.logE("error when hooking LoadedApk#createClassLoader", t); } finally { diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java index a4f64d14..c2711bb3 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; @@ -75,6 +76,27 @@ public class LSPosedContext implements XposedInterface { } } + public static void callOnPackageReady( + String packageName, + ApplicationInfo applicationInfo, + boolean isFirstPackage, + ClassLoader defaultClassLoader, + ClassLoader classLoader, + Object appComponentFactory + ) { + var lifecycle = new CompatLifecycleData( + packageName, + applicationInfo, + isFirstPackage, + defaultClassLoader, + classLoader, + appComponentFactory + ); + for (XposedModule module : modules) { + invokeCompatLifecycle(module, "onPackageReady", lifecycle); + } + } + public static void callOnSystemServerLoaded(XposedModuleInterface.SystemServerLoadedParam param) { for (XposedModule module : modules) { try { @@ -82,6 +104,18 @@ public class LSPosedContext implements XposedInterface { } catch (Throwable t) { Log.e(TAG, "Error when calling onSystemServerLoaded of " + module.getApplicationInfo().packageName, t); } + invokeCompatLifecycle( + module, + "onSystemServerStarting", + new CompatLifecycleData( + null, + null, + false, + param.getClassLoader(), + null, + null + ) + ); } } @@ -112,19 +146,7 @@ public class LSPosedContext implements XposedInterface { continue; } try { - var moduleEntry = moduleClass.getConstructor(XposedInterface.class, XposedModuleInterface.ModuleLoadedParam.class); - var moduleContext = (XposedModule) moduleEntry.newInstance(ctx, new XposedModuleInterface.ModuleLoadedParam() { - @Override - public boolean isSystemServer() { - return isSystemServer; - } - - @NonNull - @Override - public String getProcessName() { - return processName; - } - }); + var moduleContext = instantiateModuleCompat(moduleClass, ctx); modules.add(moduleContext); } catch (Throwable e) { Log.e(TAG, " Failed to load class " + moduleClass, e); @@ -139,6 +161,127 @@ public class LSPosedContext implements XposedInterface { return true; } + private static XposedModule instantiateModuleCompat(Class moduleClass, LSPosedContext context) throws Throwable { + // New API-style path: no-arg constructor + attachFramework + onModuleLoaded callback. + try { + var ctor = moduleClass.getDeclaredConstructor(); + ctor.setAccessible(true); + var instance = ctor.newInstance(); + if (instance instanceof XposedModule) { + var typed = (XposedModule) instance; + typed.attachFramework(context); + typed.onModuleLoaded(new XposedModuleInterface.ModuleLoadedParam() { + @Override + public boolean isSystemServer() { + return isSystemServer; + } + + @NonNull + @Override + public String getProcessName() { + return processName; + } + }); + return (XposedModule) instance; + } + } catch (NoSuchMethodException ignored) { + } + + // Old API-style path: constructor takes (XposedInterface, ModuleLoadedParam). + var moduleEntry = moduleClass.getConstructor(XposedInterface.class, XposedModuleInterface.ModuleLoadedParam.class); + return (XposedModule) moduleEntry.newInstance(context, new XposedModuleInterface.ModuleLoadedParam() { + @Override + public boolean isSystemServer() { + return isSystemServer; + } + + @NonNull + @Override + public String getProcessName() { + return processName; + } + }); + } + + private static final class CompatLifecycleData { + final String packageName; + final ApplicationInfo applicationInfo; + final boolean isFirstPackage; + final ClassLoader defaultClassLoader; + final ClassLoader classLoader; + final Object appComponentFactory; + + CompatLifecycleData( + String packageName, + ApplicationInfo applicationInfo, + boolean isFirstPackage, + ClassLoader defaultClassLoader, + ClassLoader classLoader, + Object appComponentFactory + ) { + this.packageName = packageName; + this.applicationInfo = applicationInfo; + this.isFirstPackage = isFirstPackage; + this.defaultClassLoader = defaultClassLoader; + this.classLoader = classLoader; + this.appComponentFactory = appComponentFactory; + } + } + + private static void invokeCompatLifecycle(XposedModule module, String methodName, CompatLifecycleData data) { + try { + for (Method method : module.getClass().getMethods()) { + if (!method.getName().equals(methodName) || method.getParameterCount() != 1) { + continue; + } + var paramType = method.getParameterTypes()[0]; + var proxy = Proxy.newProxyInstance( + paramType.getClassLoader(), + new Class[]{paramType}, + (proxyObj, invokedMethod, args) -> { + String invokedName = invokedMethod.getName(); + return switch (invokedName) { + case "getPackageName" -> data.packageName; + case "getApplicationInfo" -> data.applicationInfo; + case "isFirstPackage" -> data.isFirstPackage; + case "getDefaultClassLoader" -> data.defaultClassLoader; + case "getClassLoader" -> data.classLoader; + case "getAppComponentFactory" -> data.appComponentFactory; + case "getClassLoaderForSystemServer", "getSystemServerClassLoader" -> data.defaultClassLoader; + // Keep Object-contract methods stable for proxy users. + case "toString" -> "CompatLifecycleProxy(" + methodName + "," + data.packageName + ")"; + case "hashCode" -> System.identityHashCode(proxyObj); + case "equals" -> proxyObj == (args == null || args.length == 0 ? null : args[0]); + default -> null; + }; + } + ); + method.invoke(module, proxy); + return; + } + } catch (Throwable t) { + String modulePkg = ""; + try { + if (module.getApplicationInfo() != null && module.getApplicationInfo().packageName != null) { + modulePkg = module.getApplicationInfo().packageName; + } + } catch (Throwable ignored) { + } + Throwable root = t; + if (t instanceof InvocationTargetException && ((InvocationTargetException) t).getTargetException() != null) { + root = ((InvocationTargetException) t).getTargetException(); + } else if (t.getCause() != null) { + root = t.getCause(); + } + String brief = "LSP_INVOKE_ERR method=" + methodName + + " module=" + modulePkg + + " type=" + root.getClass().getName() + + " msg=" + String.valueOf(root.getMessage()); + android.util.Log.e(TAG, brief); + Log.e(TAG, brief, root); + } + } + @NonNull @Override public String getFrameworkName() { diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPManagerService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPManagerService.java index 46a3cd54..ba377bfc 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPManagerService.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPManagerService.java @@ -268,7 +268,7 @@ public class LSPManagerService extends ILSPManagerService.Stub { @Override public int getXposedApiVersion() { - return IXposedService.API; + return IXposedService.LIB_API; } @Override diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPModuleService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPModuleService.java index 6e1a3ed4..3ea98463 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPModuleService.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPModuleService.java @@ -125,7 +125,7 @@ public class LSPModuleService extends IXposedService.Stub { @Override public int getAPIVersion() throws RemoteException { ensureModule(); - return API; + return IXposedService.LIB_API; } @Override @@ -152,6 +152,12 @@ public class LSPModuleService extends IXposedService.Stub { return IXposedService.FRAMEWORK_PRIVILEGE_ROOT; } + @Override + public long getFrameworkProperties() throws RemoteException { + ensureModule(); + return IXposedService.PROP_CAP_SYSTEM | IXposedService.PROP_CAP_REMOTE; + } + @Override public List getScope() throws RemoteException { ensureModule(); diff --git a/services/libxposed b/services/libxposed index 496b76fa..ec7cb26a 160000 --- a/services/libxposed +++ b/services/libxposed @@ -1 +1 @@ -Subproject commit 496b76fa3e5af87958ebef97bd160319e05da79b +Subproject commit ec7cb26aa215b20b2ab1baf7c7b5a91a021a9016 diff --git a/xposed/libxposed b/xposed/libxposed index b896dbcd..745afd39 160000 --- a/xposed/libxposed +++ b/xposed/libxposed @@ -1 +1 @@ -Subproject commit b896dbcda3fa1550d04d43d962923318ed5a61a8 +Subproject commit 745afd390c7b87d9cd31efd246a7bc12cc190ac0