API: Compatiablity for API 101 over API 100

This commit is contained in:
WeiguangTWK 2026-04-02 20:26:52 +08:00
parent b896dbcda3
commit 745afd390c
4 changed files with 438 additions and 10 deletions

View File

@ -10,10 +10,12 @@ import androidx.annotation.Nullable;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import io.github.libxposed.api.errors.HookFailedError; import io.github.libxposed.api.errors.HookFailedError;
import io.github.libxposed.api.utils.DexParser; import io.github.libxposed.api.utils.DexParser;
@ -24,9 +26,28 @@ import io.github.libxposed.api.utils.DexParser;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public interface XposedInterface { public interface XposedInterface {
/** /**
* SDK API version. * The API version of this library.
*/ */
int API = 100; int LIB_API = 101;
/**
* @deprecated Use {@link #LIB_API}. Kept for API100 compatibility.
*/
@Deprecated
int API = LIB_API;
/**
* The framework has capability to hook system_server.
*/
long PROP_CAP_SYSTEM = 1L;
/**
* The framework provides remote preferences and remote files support.
*/
long PROP_CAP_REMOTE = 1L << 1;
/**
* The framework enables runtime Xposed API protection.
*/
long PROP_RT_API_PROTECTION = 1L << 2;
/** /**
* Indicates that the framework is running as root. * Indicates that the framework is running as root.
@ -208,6 +229,16 @@ public interface XposedInterface {
* }</pre> * }</pre>
*/ */
interface Hooker { interface Hooker {
@Nullable
default Object intercept(@NonNull Chain chain) throws Throwable {
return chain.proceed();
}
}
enum ExceptionMode {
DEFAULT,
PASSTHROUGH,
PROTECTIVE
} }
/** /**
@ -228,6 +259,221 @@ public interface XposedInterface {
void unhook(); void unhook();
} }
interface HookHandle {
@NonNull
Executable getExecutable();
void unhook();
}
/**
* Chain context for the API 101 style interceptor.
*/
interface Chain {
@NonNull
Executable getExecutable();
@Nullable
Object getThisObject();
@NonNull
Object[] getArgs();
@SuppressWarnings("unchecked")
@Nullable
default <T> T getArg(int index) {
return (T) getArgs()[index];
}
default void setArg(int index, @Nullable Object value) {
getArgs()[index] = value;
}
@Nullable
Object proceed() throws InvocationTargetException, IllegalArgumentException, IllegalAccessException;
}
/**
* API 101 interceptor.
*/
interface Interceptor {
@Nullable
Object intercept(@NonNull Chain chain) throws Throwable;
}
/**
* API 101 hook builder.
*/
interface HookBuilder {
@NonNull
default HookBuilder setPriority(int priority) {
return this;
}
@NonNull
default HookBuilder setExceptionMode(@NonNull ExceptionMode mode) {
return this;
}
@NonNull
HookHandle intercept(@NonNull Hooker hooker);
@NonNull
default HookHandle intercept(@NonNull Interceptor interceptor) {
return intercept(new Hooker() {
@SuppressWarnings("unused")
public Object intercept(@NonNull Chain chain) throws Throwable {
return interceptor.intercept(chain);
}
});
}
}
final class LegacyHookRegistry {
private LegacyHookRegistry() {
}
static final ConcurrentHashMap<Executable, HookEntry> ENTRIES = new ConcurrentHashMap<>();
}
final class HookEntry {
final XposedInterface base;
final Hooker hooker;
final Method interceptMethod;
final ExceptionMode exceptionMode;
HookEntry(@NonNull XposedInterface base, @NonNull Hooker hooker, @NonNull Method interceptMethod, @NonNull ExceptionMode exceptionMode) {
this.base = base;
this.hooker = hooker;
this.interceptMethod = interceptMethod;
this.exceptionMode = exceptionMode;
}
}
final class CompatHookBuilder implements HookBuilder {
private final XposedInterface base;
private final Executable origin;
private int priority = PRIORITY_DEFAULT;
private ExceptionMode exceptionMode = ExceptionMode.DEFAULT;
CompatHookBuilder(@NonNull XposedInterface base, @NonNull Executable origin) {
this.base = base;
this.origin = origin;
}
@NonNull
@Override
public HookBuilder setPriority(int priority) {
this.priority = priority;
return this;
}
@NonNull
@Override
public HookBuilder setExceptionMode(@NonNull ExceptionMode mode) {
this.exceptionMode = mode;
return this;
}
@NonNull
@Override
public HookHandle intercept(@NonNull Hooker hooker) {
final Method method = CompatHooker.findInterceptMethod(hooker.getClass());
LegacyHookRegistry.ENTRIES.put(origin, new HookEntry(base, hooker, method, exceptionMode));
final MethodUnhooker<? extends Executable> raw;
if (origin instanceof Method) {
raw = base.hook((Method) origin, priority, CompatHooker.class);
} else if (origin instanceof Constructor<?>) {
@SuppressWarnings({"rawtypes", "unchecked"})
MethodUnhooker<? extends Executable> cast = (MethodUnhooker) base.hook((Constructor) origin, priority, CompatHooker.class);
raw = cast;
} else {
LegacyHookRegistry.ENTRIES.remove(origin);
throw new IllegalArgumentException("Unsupported executable type: " + origin.getClass().getName());
}
return new HookHandle() {
@NonNull
@Override
public Executable getExecutable() {
return origin;
}
@Override
public void unhook() {
LegacyHookRegistry.ENTRIES.remove(origin);
raw.unhook();
}
};
}
}
final class CompatHooker implements Hooker {
private CompatHooker() {
}
static Method findInterceptMethod(@NonNull Class<?> cls) {
for (Method method : cls.getMethods()) {
if (!method.getName().equals("intercept") || method.getParameterCount() != 1) {
continue;
}
if (method.getParameterTypes()[0] == Chain.class) {
method.setAccessible(true);
return method;
}
}
throw new IllegalArgumentException("Hooker must declare public intercept(Chain): " + cls.getName());
}
public static void before(@NonNull BeforeHookCallback callback) {
var member = callback.getMember();
if (!(member instanceof Executable)) {
return;
}
var executable = (Executable) member;
var entry = LegacyHookRegistry.ENTRIES.get(executable);
if (entry == null) {
return;
}
var chain = new Chain() {
@NonNull
@Override
public Executable getExecutable() {
return executable;
}
@Nullable
@Override
public Object getThisObject() {
return callback.getThisObject();
}
@NonNull
@Override
public Object[] getArgs() {
return callback.getArgs();
}
@Nullable
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Object proceed() throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
if (executable instanceof Method method) {
return entry.base.invokeOrigin(method, callback.getThisObject(), callback.getArgs());
}
entry.base.invokeOrigin((Constructor) executable, callback.getThisObject(), callback.getArgs());
return callback.getThisObject();
}
};
try {
callback.returnAndSkip(entry.interceptMethod.invoke(entry.hooker, chain));
} catch (InvocationTargetException ite) {
callback.throwAndSkip(ite.getCause() != null ? ite.getCause() : ite);
} catch (Throwable t) {
callback.throwAndSkip(t);
}
}
}
/** /**
* Gets the Xposed framework name of current implementation. * Gets the Xposed framework name of current implementation.
* *
@ -236,6 +482,13 @@ public interface XposedInterface {
@NonNull @NonNull
String getFrameworkName(); String getFrameworkName();
/**
* Gets runtime API version.
*/
default int getApiVersion() {
return LIB_API;
}
/** /**
* Gets the Xposed framework version of current implementation. * Gets the Xposed framework version of current implementation.
* *
@ -251,6 +504,21 @@ public interface XposedInterface {
*/ */
long getFrameworkVersionCode(); long getFrameworkVersionCode();
/**
* Gets the Xposed framework properties.
*/
default long getFrameworkProperties() {
long prop = 0L;
if (getFrameworkPrivilege() == FRAMEWORK_PRIVILEGE_ROOT ||
getFrameworkPrivilege() == FRAMEWORK_PRIVILEGE_CONTAINER) {
prop |= PROP_CAP_SYSTEM;
}
if (getFrameworkPrivilege() != FRAMEWORK_PRIVILEGE_EMBEDDED) {
prop |= PROP_CAP_REMOTE;
}
return prop;
}
/** /**
* Gets the Xposed framework privilege of current implementation. * Gets the Xposed framework privilege of current implementation.
* *
@ -345,6 +613,16 @@ public interface XposedInterface {
@NonNull @NonNull
<T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker); <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker);
@NonNull
default HookBuilder hook(@NonNull Executable origin) {
return new CompatHookBuilder(this, origin);
}
@NonNull
default HookBuilder hook(@NonNull Executable origin, int priority) {
return new CompatHookBuilder(this, origin).setPriority(priority);
}
/** /**
* Deoptimizes a method in case hooked callee is not called because of inline. * Deoptimizes a method in case hooked callee is not called because of inline.
* *
@ -468,6 +746,14 @@ public interface XposedInterface {
*/ */
void log(@NonNull String message); void log(@NonNull String message);
/**
* Writes a message to the Xposed log with explicit priority and optional tag.
*/
default void log(int priority, @Nullable String tag, @NonNull String msg) {
String prefix = tag == null || tag.trim().isEmpty() ? "" : ("[" + tag + "] ");
log(prefix + msg);
}
/** /**
* Writes a message with a stack trace to the Xposed log. * Writes a message with a stack trace to the Xposed log.
* *
@ -476,6 +762,18 @@ public interface XposedInterface {
*/ */
void log(@NonNull String message, @NonNull Throwable throwable); void log(@NonNull String message, @NonNull Throwable throwable);
/**
* Writes a message with stack trace to the Xposed log with explicit priority and optional tag.
*/
default void log(int priority, @Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
String prefix = tag == null || tag.trim().isEmpty() ? "" : ("[" + tag + "] ");
if (tr == null) {
log(prefix + msg);
} else {
log(prefix + msg, tr);
}
}
/** /**
* Parse a dex file in memory. * Parse a dex file in memory.
* *
@ -491,8 +789,17 @@ public interface XposedInterface {
* Gets the application info of the module. * Gets the application info of the module.
*/ */
@NonNull @NonNull
@Deprecated
ApplicationInfo getApplicationInfo(); ApplicationInfo getApplicationInfo();
/**
* Gets the application info of the module.
*/
@NonNull
default ApplicationInfo getModuleApplicationInfo() {
return getApplicationInfo();
}
/** /**
* Gets remote preferences stored in Xposed framework. Note that those are read-only in hooked apps. * Gets remote preferences stored in Xposed framework. Note that those are read-only in hooked apps.
* *

View File

@ -10,6 +10,7 @@ import androidx.annotation.Nullable;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor; 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.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -21,151 +22,210 @@ import io.github.libxposed.api.utils.DexParser;
*/ */
public class XposedInterfaceWrapper implements XposedInterface { public class XposedInterfaceWrapper implements XposedInterface {
private final XposedInterface mBase; private volatile XposedInterface mBase;
public XposedInterfaceWrapper() {
}
XposedInterfaceWrapper(@NonNull XposedInterface base) { XposedInterfaceWrapper(@NonNull XposedInterface base) {
mBase = base; mBase = base;
} }
/**
* Attaches the framework interface to the module. Modules should never call this method directly.
*/
@SuppressWarnings("unused")
public final void attachFramework(@NonNull XposedInterface base) {
if (mBase != null) {
throw new IllegalStateException("Framework already attached");
}
mBase = base;
}
private void ensureAttached() {
if (mBase == null) {
throw new IllegalStateException("Framework not attached");
}
}
@NonNull @NonNull
@Override @Override
public final String getFrameworkName() { public final String getFrameworkName() {
ensureAttached();
return mBase.getFrameworkName(); return mBase.getFrameworkName();
} }
@NonNull @NonNull
@Override @Override
public final String getFrameworkVersion() { public final String getFrameworkVersion() {
ensureAttached();
return mBase.getFrameworkVersion(); return mBase.getFrameworkVersion();
} }
@Override @Override
public final long getFrameworkVersionCode() { public final long getFrameworkVersionCode() {
ensureAttached();
return mBase.getFrameworkVersionCode(); return mBase.getFrameworkVersionCode();
} }
@Override @Override
public final int getFrameworkPrivilege() { public final int getFrameworkPrivilege() {
ensureAttached();
return mBase.getFrameworkPrivilege(); return mBase.getFrameworkPrivilege();
} }
@NonNull @NonNull
@Override @Override
public final MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker) { public final MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hook(origin, hooker); return mBase.hook(origin, hooker);
} }
@NonNull
@Override
public final HookBuilder hook(@NonNull Executable origin) {
ensureAttached();
return mBase.hook(origin);
}
@NonNull
@Override
public final HookBuilder hook(@NonNull Executable origin, int priority) {
ensureAttached();
return mBase.hook(origin, priority);
}
@NonNull @NonNull
@Override @Override
public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, @NonNull Class<? extends Hooker> hooker) { public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hookClassInitializer(origin, hooker); return mBase.hookClassInitializer(origin, hooker);
} }
@NonNull @NonNull
@Override @Override
public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) { public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hookClassInitializer(origin, priority, hooker); return mBase.hookClassInitializer(origin, priority, hooker);
} }
@NonNull @NonNull
@Override @Override
public final MethodUnhooker<Method> hook(@NonNull Method origin, int priority, @NonNull Class<? extends Hooker> hooker) { public final MethodUnhooker<Method> hook(@NonNull Method origin, int priority, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hook(origin, priority, hooker); return mBase.hook(origin, priority, hooker);
} }
@NonNull @NonNull
@Override @Override
public final <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull Class<? extends Hooker> hooker) { public final <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hook(origin, hooker); return mBase.hook(origin, hooker);
} }
@NonNull @NonNull
@Override @Override
public final <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) { public final <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hook(origin, priority, hooker); return mBase.hook(origin, priority, hooker);
} }
@Override @Override
public final boolean deoptimize(@NonNull Method method) { public final boolean deoptimize(@NonNull Method method) {
ensureAttached();
return mBase.deoptimize(method); return mBase.deoptimize(method);
} }
@Override @Override
public final <T> boolean deoptimize(@NonNull Constructor<T> constructor) { public final <T> boolean deoptimize(@NonNull Constructor<T> constructor) {
ensureAttached();
return mBase.deoptimize(constructor); return mBase.deoptimize(constructor);
} }
@Nullable @Nullable
@Override @Override
public final Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { public final Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
ensureAttached();
return mBase.invokeOrigin(method, thisObject, args); return mBase.invokeOrigin(method, thisObject, args);
} }
@Override @Override
public <T> void invokeOrigin(@NonNull Constructor<T> constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { public <T> void invokeOrigin(@NonNull Constructor<T> constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
ensureAttached();
mBase.invokeOrigin(constructor, thisObject, args); mBase.invokeOrigin(constructor, thisObject, args);
} }
@Nullable @Nullable
@Override @Override
public final Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { public final Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
ensureAttached();
return mBase.invokeSpecial(method, thisObject, args); return mBase.invokeSpecial(method, thisObject, args);
} }
@Override @Override
public <T> void invokeSpecial(@NonNull Constructor<T> constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { public <T> void invokeSpecial(@NonNull Constructor<T> constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
ensureAttached();
mBase.invokeSpecial(constructor, thisObject, args); mBase.invokeSpecial(constructor, thisObject, args);
} }
@NonNull @NonNull
@Override @Override
public final <T> T newInstanceOrigin(@NonNull Constructor<T> constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { public final <T> T newInstanceOrigin(@NonNull Constructor<T> constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException {
ensureAttached();
return mBase.newInstanceOrigin(constructor, args); return mBase.newInstanceOrigin(constructor, args);
} }
@NonNull @NonNull
@Override @Override
public final <T, U> U newInstanceSpecial(@NonNull Constructor<T> constructor, @NonNull Class<U> subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { public final <T, U> U newInstanceSpecial(@NonNull Constructor<T> constructor, @NonNull Class<U> subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException {
ensureAttached();
return mBase.newInstanceSpecial(constructor, subClass, args); return mBase.newInstanceSpecial(constructor, subClass, args);
} }
@Override @Override
public final void log(@NonNull String message) { public final void log(@NonNull String message) {
ensureAttached();
mBase.log(message); mBase.log(message);
} }
@Override @Override
public final void log(@NonNull String message, @NonNull Throwable throwable) { public final void log(@NonNull String message, @NonNull Throwable throwable) {
ensureAttached();
mBase.log(message, throwable); mBase.log(message, throwable);
} }
@Nullable @Nullable
@Override @Override
public final DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException { public final DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException {
ensureAttached();
return mBase.parseDex(dexData, includeAnnotations); return mBase.parseDex(dexData, includeAnnotations);
} }
@NonNull @NonNull
@Override @Override
public SharedPreferences getRemotePreferences(@NonNull String name) { public SharedPreferences getRemotePreferences(@NonNull String name) {
ensureAttached();
return mBase.getRemotePreferences(name); return mBase.getRemotePreferences(name);
} }
@NonNull @NonNull
@Override @Override
public ApplicationInfo getApplicationInfo() { public ApplicationInfo getApplicationInfo() {
ensureAttached();
return mBase.getApplicationInfo(); return mBase.getApplicationInfo();
} }
@NonNull @NonNull
@Override @Override
public String[] listRemoteFiles() { public String[] listRemoteFiles() {
ensureAttached();
return mBase.listRemoteFiles(); return mBase.listRemoteFiles();
} }
@NonNull @NonNull
@Override @Override
public ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException { public ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException {
ensureAttached();
return mBase.openRemoteFile(name); return mBase.openRemoteFile(name);
} }
} }

View File

@ -4,10 +4,19 @@ import androidx.annotation.NonNull;
/** /**
* Super class which all Xposed module entry classes should extend.<br/> * Super class which all Xposed module entry classes should extend.<br/>
* Entry classes will be instantiated exactly once for each process. * Entry classes will be instantiated exactly once for each process. Modules should avoid
* initialization work before {@link #onModuleLoaded(ModuleLoadedParam)} is called.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface { public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface {
/**
* New API-style constructor. The framework will attach itself via
* {@link #attachFramework(XposedInterface)} and then dispatch lifecycle callbacks.
*/
public XposedModule() {
super();
}
/** /**
* Instantiates a new Xposed module.<br/> * Instantiates a new Xposed module.<br/>
* When the module is loaded into the target process, the constructor will be called. * When the module is loaded into the target process, the constructor will be called.
@ -15,7 +24,10 @@ public abstract class XposedModule extends XposedInterfaceWrapper implements Xpo
* @param base The implementation interface provided by the framework, should not be used by the module * @param base The implementation interface provided by the framework, should not be used by the module
* @param param Information about the process in which the module is loaded * @param param Information about the process in which the module is loaded
*/ */
@Deprecated
public XposedModule(@NonNull XposedInterface base, @NonNull ModuleLoadedParam param) { public XposedModule(@NonNull XposedInterface base, @NonNull ModuleLoadedParam param) {
super(base); this();
attachFramework(base);
onModuleLoaded(param);
} }
} }

View File

@ -1,5 +1,6 @@
package io.github.libxposed.api; package io.github.libxposed.api;
import android.app.AppComponentFactory;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.os.Build; import android.os.Build;
@ -34,7 +35,7 @@ public interface XposedModuleInterface {
/** /**
* Wraps information about system server. * Wraps information about system server.
*/ */
interface SystemServerLoadedParam { interface SystemServerStartingParam {
/** /**
* Gets the class loader of system server. * Gets the class loader of system server.
* *
@ -44,6 +45,13 @@ public interface XposedModuleInterface {
ClassLoader getClassLoader(); ClassLoader getClassLoader();
} }
/**
* @deprecated Kept for API100 compatibility.
*/
@Deprecated
interface SystemServerLoadedParam extends SystemServerStartingParam {
}
/** /**
* Wraps information about the package being loaded. * Wraps information about the package being loaded.
*/ */
@ -73,6 +81,27 @@ public interface XposedModuleInterface {
@NonNull @NonNull
ClassLoader getDefaultClassLoader(); ClassLoader getDefaultClassLoader();
/**
* Gets information about whether is this package the first and main package of the app process.
*
* @return {@code true} if this is the first package.
*/
boolean isFirstPackage();
/**
* @deprecated Kept for API100 compatibility. Use {@link PackageReadyParam#getClassLoader()}.
*/
@Deprecated
@NonNull
default ClassLoader getClassLoader() {
return getDefaultClassLoader();
}
}
/**
* Wraps information about the package whose class loader is ready.
*/
interface PackageReadyParam extends PackageLoadedParam {
/** /**
* Gets the class loader of the package being loaded. * Gets the class loader of the package being loaded.
* *
@ -82,11 +111,17 @@ public interface XposedModuleInterface {
ClassLoader getClassLoader(); ClassLoader getClassLoader();
/** /**
* Gets information about whether is this package the first and main package of the app process. * Gets the app component factory of the package being loaded.
*
* @return {@code true} if this is the first package.
*/ */
boolean isFirstPackage(); @RequiresApi(Build.VERSION_CODES.P)
@NonNull
AppComponentFactory getAppComponentFactory();
}
/**
* Gets notified when the module is loaded into the target process.
*/
default void onModuleLoaded(@NonNull ModuleLoadedParam param) {
} }
/** /**
@ -104,5 +139,19 @@ public interface XposedModuleInterface {
* @param param Information about system server * @param param Information about system server
*/ */
default void onSystemServerLoaded(@NonNull SystemServerLoadedParam param) { default void onSystemServerLoaded(@NonNull SystemServerLoadedParam param) {
onSystemServerStarting(param);
}
/**
* Gets notified when custom {@link android.app.AppComponentFactory} has created the final
* class loader.
*/
default void onPackageReady(@NonNull PackageReadyParam param) {
}
/**
* Gets notified when system server is ready to start critical services.
*/
default void onSystemServerStarting(@NonNull SystemServerStartingParam param) {
} }
} }