diff --git a/api/build.gradle.kts b/api/build.gradle.kts index fc05e88..8896e9f 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -10,7 +10,7 @@ android { buildToolsVersion = "35.0.0" 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 3c4ac86..a2bc870 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; @@ -62,12 +62,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 +102,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. @@ -185,23 +185,23 @@ interface AfterHookCallback { *
{@code
      *   public class ExampleHooker implements Hooker {
      *
-     *       public static void before(@NonNull BeforeHookCallback callback) {
+     *       public static void before(@NonNull BeforeHookCallback callback) {
      *           // Pre-hooking logic goes here
      *       }
      *
-     *       public static void after(@NonNull AfterHookCallback callback) {
+     *       public static void after(@NonNull AfterHookCallback callback) {
      *           // Post-hooking logic goes here
      *       }
      *   }
      *
      *   public class ExampleHookerWithContext implements Hooker {
      *
-     *       public static MyContext before(@NonNull BeforeHookCallback callback) {
+     *       public static MyContext before(@NonNull BeforeHookCallback callback) {
      *           // Pre-hooking logic goes here
      *           return new MyContext();
      *       }
      *
-     *       public static void after(@NonNull AfterHookCallback callback, MyContext context) {
+     *       public static void after(@NonNull AfterHookCallback callback, MyContext context) {
      *           // Post-hooking logic goes here
      *       }
      *   }
@@ -211,16 +211,16 @@ interface Hooker {
     }
 
     /**
-     * Interface for canceling a hook.
+     * 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.
@@ -259,32 +259,18 @@ interface MethodUnhooker {
     int getFrameworkPrivilege();
 
     /**
-     * Hook a method with default priority.
+     * Hook a method / constructor with specified priority.
      *
-     * @param origin The method to be hooked
-     * @param hooker The hooker class
-     * @return Unhooker for canceling the hook
+     * @param origin   The method / constructor to be hooked
+     * @param priority The hook priority
+     * @param hooker   The hooker class
+     * @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);
-
-    /**
-     * Hook the static initializer of a class with default priority.
-     * 

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

- * - * @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 - * @throws HookFailedError if hook fails due to framework internal error - */ - @NonNull - MethodUnhooker> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker); + HookHandle hook(@NonNull T origin, int priority, @NonNull Class hooker); /** * Hook the static initializer of a class with specified priority. @@ -295,58 +281,15 @@ interface MethodUnhooker { * @param origin The class to be hooked * @param priority The hook priority * @param hooker The hooker class - * @return Unhooker for canceling the hook + * @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); - - /** - * 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); + HookHandle> hookClassInitializer(@NonNull Class 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 +301,13 @@ 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 + * @param executable The method to deoptimize * @return Indicate whether the deoptimizing succeed or not */ - boolean deoptimize(@NonNull Method method); + boolean deoptimize(@NonNull Executable executable); /** - * 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 - * @return Indicate whether the deoptimizing succeed or not - * @see #deoptimize(Method) - */ - boolean deoptimize(@NonNull Constructor constructor); - - /** - * 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. * * @param method The method to be called * @param thisObject For non-static calls, the {@code this} pointer, otherwise {@code null} @@ -387,8 +319,7 @@ 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. * * @param constructor The constructor to create and initialize a new instance * @param thisObject The instance to be constructed @@ -398,6 +329,18 @@ 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. + * + * @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 +373,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 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 425596f..2441b75 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,9 +22,15 @@ */ public class XposedInterfaceWrapper implements XposedInterface { - private final XposedInterface mBase; + private XposedInterface mBase; - XposedInterfaceWrapper(@NonNull XposedInterface base) { + /** + * 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) { mBase = base; } @@ -51,48 +58,19 @@ public final int getFrameworkPrivilege() { @NonNull @Override - public final MethodUnhooker hook(@NonNull Method origin, @NonNull Class hooker) { - 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) { + public final HookHandle hook(@NonNull T origin, int priority, @NonNull Class hooker) { return mBase.hook(origin, priority, hooker); } @NonNull @Override - public final MethodUnhooker> hook(@NonNull Constructor origin, @NonNull Class hooker) { - 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 HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { + return mBase.hookClassInitializer(origin, priority, hooker); } @Override - public final boolean deoptimize(@NonNull Constructor constructor) { - return mBase.deoptimize(constructor); + public final boolean deoptimize(@NonNull Executable executable) { + return mBase.deoptimize(executable); } @Nullable @@ -106,6 +84,12 @@ public void invokeOrigin(@NonNull Constructor constructor, @NonNull T thi mBase.invokeOrigin(constructor, thisObject, args); } + @NonNull + @Override + public final T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { + return mBase.newInstanceOrigin(constructor, args); + } + @Nullable @Override public final Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { @@ -117,12 +101,6 @@ public void invokeSpecial(@NonNull Constructor constructor, @NonNull T th 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 { 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..ef51b45 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java @@ -1,5 +1,6 @@ package io.github.libxposed.api; +import android.app.AppComponentFactory; import android.content.pm.ApplicationInfo; import android.os.Build; @@ -89,6 +90,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 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. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60cbcd5..2254578 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] -annotation = "1.8.0" -kotlin = "2.0.0" -lint = "31.5.1" -agp = "8.5.1" +annotation = "1.9.0" +kotlin = "2.0.21" +lint = "31.7.1" +agp = "8.7.1" [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }