Treewide: Add more API 101 behaviour

This commit is contained in:
WeiguangTWK 2026-04-02 20:31:28 +08:00
parent 62ea99fa35
commit 43ffcee9c8
6 changed files with 191 additions and 17 deletions

View File

@ -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 {

View File

@ -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 = "<unknown>";
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() {

View File

@ -268,7 +268,7 @@ public class LSPManagerService extends ILSPManagerService.Stub {
@Override
public int getXposedApiVersion() {
return IXposedService.API;
return IXposedService.LIB_API;
}
@Override

View File

@ -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<String> getScope() throws RemoteException {
ensureModule();

@ -1 +1 @@
Subproject commit 496b76fa3e5af87958ebef97bd160319e05da79b
Subproject commit ec7cb26aa215b20b2ab1baf7c7b5a91a021a9016

@ -1 +1 @@
Subproject commit b896dbcda3fa1550d04d43d962923318ed5a61a8
Subproject commit 745afd390c7b87d9cd31efd246a7bc12cc190ac0