diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 059e414..bcdc9fa 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -11,7 +11,7 @@ android { androidResources.enable = false defaultConfig { - minSdk = 24 + minSdk = 26 consumerProguardFiles("proguard-rules.pro") } diff --git a/api/src/main/java/io/github/libxposed/api/XposedInterface.java b/api/src/main/java/io/github/libxposed/api/XposedInterface.java index 431eadd..5c4b956 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -10,8 +10,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; import java.lang.reflect.Method; import java.nio.ByteBuffer; @@ -24,27 +24,17 @@ @SuppressWarnings("unused") public interface XposedInterface { /** - * SDK API version. + * The framework has the capability to hook system_server and other system processes. */ - int API = 100; - - /** - * Indicates that the framework is running as root. - */ - int FRAMEWORK_PRIVILEGE_ROOT = 0; - /** - * Indicates that the framework is running in a container with a fake system_server. - */ - int FRAMEWORK_PRIVILEGE_CONTAINER = 1; + long CAP_SYSTEM = 1L; /** - * Indicates that the framework is running as a different app, which may have at most shell permission. + * The framework provides remote preferences and remote files support. */ - int FRAMEWORK_PRIVILEGE_APP = 2; + long CAP_REMOTE = 1L << 1; /** - * Indicates that the framework is embedded in the hooked app, - * which means {@link #getRemotePreferences} will be null and remote file is unsupported. + * The framework allows dynamically loaded code to use Xposed APIs. */ - int FRAMEWORK_PRIVILEGE_EMBEDDED = 3; + long CAP_RT_API_REFLECTION = 1L << 2; /** * The default hook priority. @@ -62,12 +52,12 @@ public interface XposedInterface { /** * Contextual interface for before invocation callbacks. */ - interface BeforeHookCallback { + interface BeforeHookCallback { /** - * Gets the method / constructor to be hooked. + * Gets the method / constructor being hooked. */ @NonNull - Member getMember(); + T getExecutable(); /** * Gets the {@code this} object, or {@code null} if the method is static. @@ -102,12 +92,12 @@ interface BeforeHookCallback { /** * Contextual interface for after invocation callbacks. */ - interface AfterHookCallback { + interface AfterHookCallback { /** - * Gets the method / constructor to be hooked. + * Gets the method / constructor being hooked. */ @NonNull - Member getMember(); + T getExecutable(); /** * Gets the {@code this} object, or {@code null} if the method is static. @@ -157,70 +147,90 @@ interface AfterHookCallback { } /** - * Interface for method / constructor hooking. Xposed modules should define their own hooker class - * and implement this interface. Normally, a hooker class corresponds to a method / constructor, but - * there could also be a single hooker class for all of them. By this way you can implement an interface - * like the old API. - * - *

- * Classes implementing this interface should should provide two public static methods named - * before and after for before invocation and after invocation respectively. - *

- * - *

- * The before invocation method should have the following signature:
- * Param {@code callback}: The {@link BeforeHookCallback} of the procedure call.
- * Return value: If you want to save contextual information of one procedure call between the before - * and after callback, it could be a self-defined class, otherwise it should be {@code void}. - *

- * - *

- * The after invocation method should have the following signature:
- * Param {@code callback}: The {@link AfterHookCallback} of the procedure call.
- * Param {@code context} (optional): The contextual object returned by the before invocation. - *

+ * Interface for method / constructor hooking. * *

Example usage:

* *
{@code
-     *   public class ExampleHooker implements Hooker {
+     *   public class ExampleHooker implements SimpleHooker {
      *
-     *       public static void before(@NonNull BeforeHookCallback callback) {
+     *       @Override
+     *       void before(@NonNull BeforeHookCallback callback) {
      *           // Pre-hooking logic goes here
      *       }
      *
-     *       public static void after(@NonNull AfterHookCallback callback) {
+     *       @Override
+     *       void after(@NonNull AfterHookCallback callback) {
      *           // Post-hooking logic goes here
      *       }
      *   }
      *
-     *   public class ExampleHookerWithContext implements Hooker {
+     *   public class ExampleHookerWithContext implements ContextualHooker {
      *
-     *       public static MyContext before(@NonNull BeforeHookCallback callback) {
+     *       @Override
+     *       MyContext before(@NonNull BeforeHookCallback callback) {
      *           // Pre-hooking logic goes here
      *           return new MyContext();
      *       }
      *
-     *       public static void after(@NonNull AfterHookCallback callback, MyContext context) {
+     *       @Override
+     *       void after(@NonNull AfterHookCallback callback, MyContext context) {
      *           // Post-hooking logic goes here
      *       }
      *   }
      * }
*/ - interface Hooker { + interface Hooker { + /** + * Returns the priority of the hook. Hooks with higher priority will be executed first. The default + * priority is {@link #PRIORITY_DEFAULT}. Make sure the value is consistent after the hooker is installed, + * otherwise the behavior is undefined. + * + * @return The priority of the hook + */ + default int getPriority() { + return PRIORITY_DEFAULT; + } + } + + /** + * A hooker without context. + */ + interface SimpleHooker extends Hooker { + + default void before(@NonNull BeforeHookCallback callback) { + } + + default void after(@NonNull AfterHookCallback callback) { + } } /** - * Interface for canceling a hook. + * A hooker with context. The context object is guaranteed to be the same between before and after + * invocation. + * + * @param The type of the context + */ + interface ContextualHooker extends Hooker { + default C before(@NonNull BeforeHookCallback callback) { + return null; + } + + default void after(@NonNull AfterHookCallback callback, @Nullable C context) { + } + } + + /** + * Handle for a hook. * * @param {@link Method} or {@link Constructor} */ - interface MethodUnhooker { + interface HookHandle { /** - * Gets the method or constructor being hooked. + * Gets the method / constructor being hooked. */ @NonNull - T getOrigin(); + T getExecutable(); /** * Cancels the hook. The behavior of calling this method multiple times is undefined. @@ -228,6 +238,55 @@ interface MethodUnhooker { void unhook(); } + /** + * Handle for a method hook. + */ + interface MethodHookHandle extends HookHandle { + /** + * Invoke the original method, but keeps all higher priority hooks. + * + * @param thisObject For non-static calls, the {@code this} pointer, otherwise {@code null} + * @param args The arguments used for the method call + * @return The result returned from the invoked method + * @see Method#invoke(Object, Object...) + */ + @Nullable + Object invokeOrigin(@Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + } + + /** + * Handle for a constructor hook. + * + * @param The type of the constructor + */ + interface CtorHookHandle extends HookHandle> { + /** + * Invoke the original constructor as a method, but keeps all higher priority hooks. + * + * @param thisObject The instance to be constructed + * @param args The arguments used for the construction + * @see Constructor#newInstance(Object...) + */ + void invokeOrigin(@NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + + /** + * Invoke the original constructor, but keeps all higher priority hooks. + * + * @param args The arguments used for the construction + * @return The instance created and initialized by the constructor + * @see Constructor#newInstance(Object...) + */ + @NonNull + T newInstanceOrigin(Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; + } + + /** + * Gets the Xposed API version of current implementation. + * + * @return API version + */ + int getApiVersion(); + /** * Gets the Xposed framework name of current implementation. * @@ -252,101 +311,56 @@ interface MethodUnhooker { long getFrameworkVersionCode(); /** - * Gets the Xposed framework privilege of current implementation. + * Gets the Xposed framework capabilities. + * Capabilities with prefix CAP_RT_ may change among launches. * - * @return Framework privilege + * @return Framework capabilities */ - int getFrameworkPrivilege(); + long getFrameworkCapabilities(); /** - * Hook a method with default priority. + * Hook a method. * - * @param origin The method to be hooked - * @param hooker The hooker class - * @return Unhooker for canceling the hook + * @param origin The method to be hooked + * @param hooker The hooker object + * @return Handle for the hook * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, * or hooker is invalid * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - MethodUnhooker hook(@NonNull Method origin, @NonNull Class hooker); + MethodHookHandle hook(@NonNull Method origin, @NonNull Hooker hooker); /** - * Hook the static initializer of a class with default priority. - *

- * Note: If the class is initialized, the hook will never be called. - *

+ * Hook a constructor. * - * @param origin The class to be hooked - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @throws IllegalArgumentException if class has no static initializer or hooker is invalid + * @param origin The constructor to be hooked + * @param hooker The hooker object + * @return Handle for the hook + * @throws IllegalArgumentException if origin is framework internal or {@link Constructor#newInstance}, + * or hooker is invalid * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - MethodUnhooker> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker); + CtorHookHandle hook(@NonNull Constructor origin, @NonNull Hooker> hooker); /** - * Hook the static initializer of a class with specified priority. + * Hook the static initializer of a class. *

* Note: If the class is initialized, the hook will never be called. *

* * @param origin The class to be hooked - * @param priority The hook priority - * @param hooker The hooker class - * @return Unhooker for canceling the hook + * @param hooker The hooker object + * @return Handle for the hook * @throws IllegalArgumentException if class has no static initializer or hooker is invalid * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - MethodUnhooker> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker); - - /** - * Hook a method with specified priority. - * - * @param origin The method to be hooked - * @param priority The hook priority - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, - * or hooker is invalid - * @throws HookFailedError if hook fails due to framework internal error - */ - @NonNull - MethodUnhooker hook(@NonNull Method origin, int priority, @NonNull Class hooker); - - /** - * Hook a constructor with default priority. - * - * @param The type of the constructor - * @param origin The constructor to be hooked - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, - * or hooker is invalid - * @throws HookFailedError if hook fails due to framework internal error - */ - @NonNull - MethodUnhooker> hook(@NonNull Constructor origin, @NonNull Class hooker); + MethodHookHandle hookClassInitializer(@NonNull Class origin, @NonNull Hooker hooker); /** - * Hook a constructor with specified priority. - * - * @param The type of the constructor - * @param origin The constructor to be hooked - * @param priority The hook priority - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, - * or hooker is invalid - * @throws HookFailedError if hook fails due to framework internal error - */ - @NonNull - MethodUnhooker> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker); - - /** - * Deoptimizes a method in case hooked callee is not called because of inline. + * Deoptimizes a method / constructor in case hooked callee is not called because of inline. * *

By deoptimizing the method, the method will back all callee without inlining. * For example, when a short hooked method B is invoked by method A, the callback to B is not invoked @@ -358,24 +372,14 @@ interface MethodUnhooker { * the deoptimized callers are all you need. Otherwise, it would be better to change the hook point or * to deoptimize the whole app manually (by simply reinstalling the app without uninstall).

* - * @param method The method to deoptimize - * @return Indicate whether the deoptimizing succeed or not - */ - boolean deoptimize(@NonNull Method method); - - /** - * Deoptimizes a constructor in case hooked callee is not called because of inline. - * - * @param The type of the constructor - * @param constructor The constructor to deoptimize + * @param executable The method / constructor to deoptimize * @return Indicate whether the deoptimizing succeed or not - * @see #deoptimize(Method) */ - boolean deoptimize(@NonNull Constructor constructor); + boolean deoptimize(@NonNull Executable executable); /** - * Basically the same as {@link Method#invoke(Object, Object...)}, but calls the original method - * as it was before the interception by Xposed. + * Basically the same as {@link Method#invoke(Object, Object...)}, but skips all Xposed hooks. + * If you do not want to skip higher priority hooks, use {@link MethodHookHandle#invokeOrigin(Object, Object...)} instead. * * @param method The method to be called * @param thisObject For non-static calls, the {@code this} pointer, otherwise {@code null} @@ -387,8 +391,8 @@ interface MethodUnhooker { Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** - * Basically the same as {@link Constructor#newInstance(Object...)}, but calls the original constructor - * as it was before the interception by Xposed. + * Invoke the constructor as a method, but skips all Xposed hooks. + * If you do not want to skip higher priority hooks, use {@link CtorHookHandle#invokeOrigin(Object, Object...)} instead. * * @param constructor The constructor to create and initialize a new instance * @param thisObject The instance to be constructed @@ -398,6 +402,19 @@ interface MethodUnhooker { */ void invokeOrigin(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + /** + * Basically the same as {@link Constructor#newInstance(Object...)}, but skips all Xposed hooks. + * If you do not want to skip higher priority hooks, use {@link CtorHookHandle#newInstanceOrigin(Object...)} instead. + * + * @param The type of the constructor + * @param constructor The constructor to create and initialize a new instance + * @param args The arguments used for the construction + * @return The instance created and initialized by the constructor + * @see Constructor#newInstance(Object...) + */ + @NonNull + T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; + /** * Invokes a special (non-virtual) method on a given object instance, similar to the functionality of * {@code CallNonVirtualMethod} in JNI, which invokes an instance (nonstatic) method on a Java @@ -430,19 +447,6 @@ interface MethodUnhooker { */ void invokeSpecial(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; - /** - * Basically the same as {@link Constructor#newInstance(Object...)}, but calls the original constructor - * as it was before the interception by Xposed. - * - * @param The type of the constructor - * @param constructor The constructor to create and initialize a new instance - * @param args The arguments used for the construction - * @return The instance created and initialized by the constructor - * @see Constructor#newInstance(Object...) - */ - @NonNull - T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; - /** * Creates a new instance of the given subclass, but initialize it with a parent constructor. This could * leave the object in an invalid state, where the subclass constructor are not called and the fields @@ -471,26 +475,6 @@ interface MethodUnhooker { */ void log(int priority, @Nullable String tag, @NonNull String msg, @Nullable Throwable tr); - /** - * Writes a message to the Xposed log. - * @deprecated Use {@link #log(int, String, String, Throwable)} instead. - * This method is kept for compatibility with old hooker classes and will be removed in first release version. - * - * @param message The log message - */ - @Deprecated - void log(@NonNull String message); - - /** - * Writes a message with a stack trace to the Xposed log. - * @deprecated Use {@link #log(int, String, String, Throwable)} instead. - * - * @param message The log message - * @param throwable The Throwable object for the stack trace - */ - @Deprecated - void log(@NonNull String message, @NonNull Throwable throwable); - /** * Parse a dex file in memory. * diff --git a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java index feb8cea..e3377c5 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -10,6 +10,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; @@ -21,156 +22,164 @@ */ public class XposedInterfaceWrapper implements XposedInterface { - private final XposedInterface mBase; - - XposedInterfaceWrapper(@NonNull XposedInterface base) { + private XposedInterface mBase; + + /** + * Attaches the framework interface to the module. Modules should never call this method. + * + * @param base The framework interface + */ + @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"); + } + } + + @Override + public int getApiVersion() { + ensureAttached(); + return mBase.getApiVersion(); + } + @NonNull @Override public final String getFrameworkName() { + ensureAttached(); return mBase.getFrameworkName(); } @NonNull @Override public final String getFrameworkVersion() { + ensureAttached(); return mBase.getFrameworkVersion(); } @Override public final long getFrameworkVersionCode() { + ensureAttached(); return mBase.getFrameworkVersionCode(); } @Override - public final int getFrameworkPrivilege() { - return mBase.getFrameworkPrivilege(); + public final long getFrameworkCapabilities() { + ensureAttached(); + return mBase.getFrameworkCapabilities(); } @NonNull @Override - public final MethodUnhooker hook(@NonNull Method origin, @NonNull Class hooker) { + public final MethodHookHandle hook(@NonNull Method origin, @NonNull Hooker hooker) { + ensureAttached(); return mBase.hook(origin, hooker); } @NonNull @Override - public MethodUnhooker> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker) { - return mBase.hookClassInitializer(origin, hooker); - } - - @NonNull - @Override - public MethodUnhooker> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { - return mBase.hookClassInitializer(origin, priority, hooker); - } - - @NonNull - @Override - public final MethodUnhooker hook(@NonNull Method origin, int priority, @NonNull Class hooker) { - return mBase.hook(origin, priority, hooker); - } - - @NonNull - @Override - public final MethodUnhooker> hook(@NonNull Constructor origin, @NonNull Class hooker) { + public final CtorHookHandle hook(@NonNull Constructor origin, @NonNull Hooker> hooker) { + ensureAttached(); return mBase.hook(origin, hooker); } @NonNull @Override - public final MethodUnhooker> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { - return mBase.hook(origin, priority, hooker); - } - - @Override - public final boolean deoptimize(@NonNull Method method) { - return mBase.deoptimize(method); + public final MethodHookHandle hookClassInitializer(@NonNull Class origin, @NonNull Hooker hooker) { + ensureAttached(); + return mBase.hookClassInitializer(origin, hooker); } @Override - public final boolean deoptimize(@NonNull Constructor constructor) { - return mBase.deoptimize(constructor); + public final boolean deoptimize(@NonNull Executable executable) { + ensureAttached(); + return mBase.deoptimize(executable); } @Nullable @Override public final Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + ensureAttached(); return mBase.invokeOrigin(method, thisObject, args); } @Override - public void invokeOrigin(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + public final void invokeOrigin(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + ensureAttached(); mBase.invokeOrigin(constructor, thisObject, args); } + @NonNull + @Override + public final T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { + ensureAttached(); + return mBase.newInstanceOrigin(constructor, args); + } + @Nullable @Override public final Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + ensureAttached(); return mBase.invokeSpecial(method, thisObject, args); } @Override - public void invokeSpecial(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + public final void invokeSpecial(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + ensureAttached(); mBase.invokeSpecial(constructor, thisObject, args); } - @NonNull - @Override - public final T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { - return mBase.newInstanceOrigin(constructor, args); - } - @NonNull @Override public final U newInstanceSpecial(@NonNull Constructor constructor, @NonNull Class subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { + ensureAttached(); return mBase.newInstanceSpecial(constructor, subClass, args); } @Override public final void log(int priority, @Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { + ensureAttached(); mBase.log(priority, tag, msg, tr); } - @Override - public final void log(@NonNull String message) { - mBase.log(message); - } - - @Override - public final void log(@NonNull String message, @NonNull Throwable throwable) { - mBase.log(message, throwable); - } - @Nullable @Override public final DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException { + ensureAttached(); return mBase.parseDex(dexData, includeAnnotations); } @NonNull @Override - public SharedPreferences getRemotePreferences(@NonNull String name) { + public final SharedPreferences getRemotePreferences(@NonNull String name) { + ensureAttached(); return mBase.getRemotePreferences(name); } @NonNull @Override - public ApplicationInfo getApplicationInfo() { + public final ApplicationInfo getApplicationInfo() { + ensureAttached(); return mBase.getApplicationInfo(); } @NonNull @Override - public String[] listRemoteFiles() { + public final String[] listRemoteFiles() { + ensureAttached(); return mBase.listRemoteFiles(); } @NonNull @Override - public ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException { + public final ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException { + ensureAttached(); return mBase.openRemoteFile(name); } } diff --git a/api/src/main/java/io/github/libxposed/api/XposedModule.java b/api/src/main/java/io/github/libxposed/api/XposedModule.java index b2e1a03..475a49c 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModule.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModule.java @@ -1,21 +1,10 @@ package io.github.libxposed.api; -import androidx.annotation.NonNull; - /** * Super class which all Xposed module entry classes should extend.
- * Entry classes will be instantiated exactly once for each process. + * Entry classes will be instantiated exactly once for each process. Modules should not do initialization + * work before {@link #onModuleLoaded(ModuleLoadedParam)} is called. */ @SuppressWarnings("unused") public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface { - /** - * Instantiates a new Xposed module.
- * When the module is loaded into the target process, the constructor will be called. - * - * @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 - */ - public XposedModule(@NonNull XposedInterface base, @NonNull ModuleLoadedParam param) { - super(base); - } } diff --git a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java index 1cb548c..941f5d1 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java @@ -89,6 +89,16 @@ interface PackageLoadedParam { boolean isFirstPackage(); } + /** + * Gets notified when the module is loaded into the target process.
+ * This callback is guaranteed to be called exactly once for a process before + * {@link android.app.AppComponentFactory} is created. + * + * @param param Information about the process in which the module is loaded + */ + default void onModuleLoaded(@NonNull ModuleLoadedParam param) { + } + /** * Gets notified when a package is loaded into the app process.
* This callback could be invoked multiple times for the same process on each package.