From 128ead6aab8861c73ae739719af669f373023e7a Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 28 Oct 2024 15:58:54 +0100 Subject: [PATCH 01/37] Update deps --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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" } From 7d83e02b3b739dc9178df0930d3f7cf1b197e6de Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 28 Oct 2024 16:04:49 +0100 Subject: [PATCH 02/37] Rename MethodUnhooker to HookHandle, add invoke method --- .../github/libxposed/api/XposedInterface.java | 49 +++++++++++-------- .../libxposed/api/XposedInterfaceWrapper.java | 12 ++--- 2 files changed, 35 insertions(+), 26 deletions(-) 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..062a935 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -211,17 +211,29 @@ 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. */ @NonNull T getOrigin(); + /** + * Similar to {@link Method#invoke(Object, Object...)}, but skips Xposed hooks with lower priority. + * + * @param method The method or constructor to be called + * @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 invoke(@NonNull T method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + /** * Cancels the hook. The behavior of calling this method multiple times is undefined. */ @@ -263,13 +275,13 @@ interface MethodUnhooker { * * @param origin The method to be hooked * @param hooker The hooker class - * @return Unhooker for canceling the hook + * @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); + HookHandle hook(@NonNull Method origin, @NonNull Class hooker); /** * Hook the static initializer of a class with default priority. @@ -279,12 +291,12 @@ interface MethodUnhooker { * * @param origin The class to be hooked * @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, @NonNull Class hooker); + HookHandle> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker); /** * Hook the static initializer of a class with specified priority. @@ -295,12 +307,12 @@ 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); + HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker); /** * Hook a method with specified priority. @@ -308,13 +320,13 @@ interface MethodUnhooker { * @param origin The method 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 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); + HookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker); /** * Hook a constructor with default priority. @@ -322,13 +334,13 @@ interface MethodUnhooker { * @param The type of the constructor * @param origin The constructor to be hooked * @param hooker The hooker class - * @return Unhooker for canceling the hook + * @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 Constructor origin, @NonNull Class hooker); + HookHandle> hook(@NonNull Constructor origin, @NonNull Class hooker); /** * Hook a constructor with specified priority. @@ -337,13 +349,13 @@ interface MethodUnhooker { * @param origin The constructor 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 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> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker); /** * Deoptimizes a method in case hooked callee is not called because of inline. @@ -374,8 +386,7 @@ interface MethodUnhooker { 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 +398,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. + * Basically the same as {@link Constructor#newInstance(Object...)}, but skips all Xposed hooks. * * @param constructor The constructor to create and initialize a new instance * @param thisObject The instance to be constructed @@ -431,8 +441,7 @@ 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. + * 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 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..630024a 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -51,37 +51,37 @@ public final int getFrameworkPrivilege() { @NonNull @Override - public final MethodUnhooker hook(@NonNull Method origin, @NonNull Class hooker) { + public final HookHandle hook(@NonNull Method origin, @NonNull Class hooker) { return mBase.hook(origin, hooker); } @NonNull @Override - public MethodUnhooker> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker) { + public HookHandle> 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) { + public HookHandle> 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 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 HookHandle> 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) { + public final HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { return mBase.hook(origin, priority, hooker); } From 854af941469a69067929920311f1c9049af5a408 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 28 Oct 2024 16:07:55 +0100 Subject: [PATCH 03/37] Reorder methods --- .../github/libxposed/api/XposedInterface.java | 86 +++++++++---------- .../libxposed/api/XposedInterfaceWrapper.java | 30 +++---- 2 files changed, 58 insertions(+), 58 deletions(-) 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 062a935..a846f7b 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -283,37 +283,6 @@ interface HookHandle { @NonNull HookHandle 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 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 - HookHandle> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker); - - /** - * Hook the static initializer of a class with specified priority. - *

- * 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 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 - HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker); - /** * Hook a method with specified priority. * @@ -357,6 +326,37 @@ interface HookHandle { @NonNull HookHandle> hook(@NonNull Constructor origin, int priority, @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 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 + HookHandle> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker); + + /** + * Hook the static initializer of a class with specified priority. + *

+ * 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 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 + HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker); + /** * Deoptimizes a method in case hooked callee is not called because of inline. * @@ -408,6 +408,18 @@ interface HookHandle { */ 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 @@ -440,18 +452,6 @@ interface HookHandle { */ void invokeSpecial(@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; - /** * 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 630024a..8a19c1a 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -57,32 +57,32 @@ public final HookHandle hook(@NonNull Method origin, @NonNull Class HookHandle> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker) { - return mBase.hookClassInitializer(origin, hooker); + public final HookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker) { + return mBase.hook(origin, priority, hooker); } @NonNull @Override - public HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { - return mBase.hookClassInitializer(origin, priority, hooker); + public final HookHandle> hook(@NonNull Constructor origin, @NonNull Class hooker) { + return mBase.hook(origin, hooker); } @NonNull @Override - public final HookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker) { + public final HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { return mBase.hook(origin, priority, hooker); } @NonNull @Override - public final HookHandle> hook(@NonNull Constructor origin, @NonNull Class hooker) { - return mBase.hook(origin, hooker); + public HookHandle> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker) { + return mBase.hookClassInitializer(origin, hooker); } @NonNull @Override - public final HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { - return mBase.hook(origin, priority, hooker); + public HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { + return mBase.hookClassInitializer(origin, priority, hooker); } @Override @@ -106,6 +106,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 +123,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 { From a69f70789b56c782d488a0328729016d734c1945 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 28 Oct 2024 16:50:36 +0100 Subject: [PATCH 04/37] Rename `getOrigin` to `getMember` --- .../io/github/libxposed/api/XposedInterface.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 a846f7b..d8cb8dd 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -64,7 +64,7 @@ public interface XposedInterface { */ interface BeforeHookCallback { /** - * Gets the method / constructor to be hooked. + * Gets the method / constructor being hooked. */ @NonNull Member getMember(); @@ -104,7 +104,7 @@ interface BeforeHookCallback { */ interface AfterHookCallback { /** - * Gets the method / constructor to be hooked. + * Gets the method / constructor being hooked. */ @NonNull Member getMember(); @@ -217,22 +217,22 @@ interface Hooker { */ interface HookHandle { /** - * Gets the method or constructor being hooked. + * Gets the method / constructor being hooked. */ @NonNull - T getOrigin(); + T getMember(); /** * Similar to {@link Method#invoke(Object, Object...)}, but skips Xposed hooks with lower priority. * - * @param method The method or constructor to be called + * @param member The method / constructor to be called * @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 invoke(@NonNull T method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + Object invoke(@NonNull T member, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** * Cancels the hook. The behavior of calling this method multiple times is undefined. From 6767f01b2412a81d47fbc07e1d026ff0602ee4ac Mon Sep 17 00:00:00 2001 From: Nullptr Date: Tue, 29 Oct 2024 10:16:53 +0100 Subject: [PATCH 05/37] Set minSdk = 26 and refine interface --- api/build.gradle.kts | 2 +- .../github/libxposed/api/XposedInterface.java | 85 ++++--------------- .../libxposed/api/XposedInterfaceWrapper.java | 26 ++---- 3 files changed, 23 insertions(+), 90 deletions(-) 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 d8cb8dd..ef6713b 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 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 being hooked. */ @NonNull - Member getMember(); + T getExecutable(); /** * Gets the {@code this} object, or {@code null} if the method is static. @@ -215,24 +215,12 @@ interface Hooker { * * @param {@link Method} or {@link Constructor} */ - interface HookHandle { + interface HookHandle { /** * Gets the method / constructor being hooked. */ @NonNull - T getMember(); - - /** - * Similar to {@link Method#invoke(Object, Object...)}, but skips Xposed hooks with lower priority. - * - * @param member The method / constructor to be called - * @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 invoke(@NonNull T member, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + T getExecutable(); /** * Cancels the hook. The behavior of calling this method multiple times is undefined. @@ -271,9 +259,9 @@ interface HookHandle { int getFrameworkPrivilege(); /** - * Hook a method with default priority. + * Hook a method / constructor with default priority. * - * @param origin The method to be hooked + * @param origin The method / constructor to be hooked * @param hooker The hooker class * @return Handle for the hook * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, @@ -281,12 +269,12 @@ interface HookHandle { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle hook(@NonNull Method origin, @NonNull Class hooker); + HookHandle hook(@NonNull T origin, @NonNull Class hooker); /** - * Hook a method with specified priority. + * Hook a method / constructor with specified priority. * - * @param origin The method to be hooked + * @param origin The method / constructor to be hooked * @param priority The hook priority * @param hooker The hooker class * @return Handle for the hook @@ -295,36 +283,7 @@ interface HookHandle { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle 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 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 - HookHandle> 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 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 - HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker); + HookHandle hook(@NonNull T origin, int priority, @NonNull Class hooker); /** * Hook the static initializer of a class with default priority. @@ -358,7 +317,7 @@ interface HookHandle { 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 @@ -370,20 +329,10 @@ interface HookHandle { * 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 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 skips all Xposed hooks. @@ -398,7 +347,7 @@ interface HookHandle { Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** - * Basically the same as {@link Constructor#newInstance(Object...)}, but skips all Xposed hooks. + * 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 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 8a19c1a..e3099a2 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; @@ -51,25 +52,13 @@ public final int getFrameworkPrivilege() { @NonNull @Override - public final HookHandle hook(@NonNull Method origin, @NonNull Class hooker) { + public final HookHandle hook(@NonNull T origin, @NonNull Class hooker) { return mBase.hook(origin, hooker); } @NonNull @Override - public final HookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker) { - return mBase.hook(origin, priority, hooker); - } - - @NonNull - @Override - public final HookHandle> hook(@NonNull Constructor origin, @NonNull Class hooker) { - return mBase.hook(origin, hooker); - } - - @NonNull - @Override - public final HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { + public final HookHandle hook(@NonNull T origin, int priority, @NonNull Class hooker) { return mBase.hook(origin, priority, hooker); } @@ -86,13 +75,8 @@ public HookHandle> hookClassInitializer(@NonNull Class ori } @Override - public final boolean deoptimize(@NonNull Method method) { - return mBase.deoptimize(method); - } - - @Override - public final boolean deoptimize(@NonNull Constructor constructor) { - return mBase.deoptimize(constructor); + public final boolean deoptimize(@NonNull Executable executable) { + return mBase.deoptimize(executable); } @Nullable From bea4b68d5f7c1396561c9d011edca647ddd5859e Mon Sep 17 00:00:00 2001 From: Nullptr Date: Tue, 29 Oct 2024 10:27:48 +0100 Subject: [PATCH 06/37] Remove default priority hook --- .../github/libxposed/api/XposedInterface.java | 28 ------------------- .../libxposed/api/XposedInterfaceWrapper.java | 12 -------- 2 files changed, 40 deletions(-) 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 ef6713b..12bdb69 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -258,19 +258,6 @@ interface HookHandle { */ int getFrameworkPrivilege(); - /** - * Hook a method / constructor with default priority. - * - * @param origin The method / constructor to be hooked - * @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 - HookHandle hook(@NonNull T origin, @NonNull Class hooker); - /** * Hook a method / constructor with specified priority. * @@ -285,21 +272,6 @@ interface HookHandle { @NonNull HookHandle hook(@NonNull T origin, int priority, @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 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 - HookHandle> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker); - /** * Hook the static initializer of a class with specified priority. *

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 e3099a2..169a8d8 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -50,24 +50,12 @@ public final int getFrameworkPrivilege() { return mBase.getFrameworkPrivilege(); } - @NonNull - @Override - public final HookHandle hook(@NonNull T origin, @NonNull Class hooker) { - return mBase.hook(origin, hooker); - } - @NonNull @Override public final HookHandle hook(@NonNull T origin, int priority, @NonNull Class hooker) { return mBase.hook(origin, priority, hooker); } - @NonNull - @Override - public HookHandle> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker) { - return mBase.hookClassInitializer(origin, hooker); - } - @NonNull @Override public HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { From 83cdd40c8bc4daf10e666d8340420d558192a864 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Tue, 29 Oct 2024 10:33:46 +0100 Subject: [PATCH 07/37] Add type in example --- .../java/io/github/libxposed/api/XposedInterface.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 12bdb69..a2bc870 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -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
      *       }
      *   }

From 00865a09218d075f5a78dc94707f8983ebfddd07 Mon Sep 17 00:00:00 2001
From: Nullptr 
Date: Fri, 1 Nov 2024 15:50:50 +0100
Subject: [PATCH 08/37] Move onModuleLoaded out of constructor

---
 .../libxposed/api/XposedInterfaceWrapper.java     | 12 +++++++++---
 .../io/github/libxposed/api/XposedModule.java     | 15 ++-------------
 .../libxposed/api/XposedModuleInterface.java      | 11 +++++++++++
 3 files changed, 22 insertions(+), 16 deletions(-)

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 169a8d8..2441b75 100644
--- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java
+++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java
@@ -22,9 +22,15 @@
  */
 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) {
         mBase = base;
     }
 
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. From 656f5ca268cba4fa6bd2c2cac2de52b32baf5d1c Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 20 Feb 2026 21:32:48 +0100 Subject: [PATCH 09/37] Refine HookHandle --- .../github/libxposed/api/XposedInterface.java | 66 +++++++++++++++++-- .../libxposed/api/XposedInterfaceWrapper.java | 10 ++- 2 files changed, 70 insertions(+), 6 deletions(-) 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 e250e95..1daafc6 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -228,6 +228,47 @@ interface HookHandle { 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(@Nullable Object 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...) + */ + T newInstanceOrigin(Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; + } + /** * Gets the Xposed framework name of current implementation. * @@ -259,9 +300,9 @@ interface HookHandle { int getFrameworkPrivilege(); /** - * Hook a method / constructor with specified priority. + * Hook a method with specified priority. * - * @param origin The method / constructor to be hooked + * @param origin The method to be hooked * @param priority The hook priority * @param hooker The hooker class * @return Handle for the hook @@ -270,7 +311,21 @@ interface HookHandle { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle hook(@NonNull T origin, int priority, @NonNull Class hooker); + MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker); + + /** + * Hook a constructor with specified priority. + * + * @param origin The constructor to be hooked + * @param priority The hook priority + * @param hooker The hooker class + * @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 + CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Class hooker); /** * Hook the static initializer of a class with specified priority. @@ -286,7 +341,7 @@ interface HookHandle { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker); + MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker); /** * Deoptimizes a method / constructor in case hooked callee is not called because of inline. @@ -308,6 +363,7 @@ interface HookHandle { /** * 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} @@ -320,6 +376,7 @@ interface HookHandle { /** * 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 @@ -331,6 +388,7 @@ interface HookHandle { /** * 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 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 ac5caba..90431ef 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -58,13 +58,19 @@ public final int getFrameworkPrivilege() { @NonNull @Override - public final HookHandle hook(@NonNull T origin, int priority, @NonNull Class hooker) { + public MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker) { return mBase.hook(origin, priority, hooker); } @NonNull @Override - public HookHandle> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { + public CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { + return mBase.hook(origin, priority, hooker); + } + + @NonNull + @Override + public MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { return mBase.hookClassInitializer(origin, priority, hooker); } From 7ce14b8895bb14dcb9b4b5d0da882c8fc92d974c Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 20 Feb 2026 21:33:16 +0100 Subject: [PATCH 10/37] RFC: Remove deprecated api --- .../github/libxposed/api/XposedInterface.java | 20 ------------------- .../libxposed/api/XposedInterfaceWrapper.java | 10 ---------- 2 files changed, 30 deletions(-) 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 1daafc6..1d56e4f 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -459,26 +459,6 @@ interface CtorHookHandle extends HookHandle> { */ 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 90431ef..92383f4 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -118,16 +118,6 @@ public final void log(int priority, @Nullable String tag, @NonNull String msg, @ 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 { From 4f6cc32a0e1bca25fdd5bcd67c0a5aecbb2870e6 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 20 Feb 2026 21:49:29 +0100 Subject: [PATCH 11/37] RFC: Make wrapper methods final --- .../libxposed/api/XposedInterfaceWrapper.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 92383f4..01928fd 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -58,19 +58,19 @@ public final int getFrameworkPrivilege() { @NonNull @Override - public MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker) { + public final MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker) { return mBase.hook(origin, priority, hooker); } @NonNull @Override - public CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { + public final CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { return mBase.hook(origin, priority, hooker); } @NonNull @Override - public MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { + public final MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { return mBase.hookClassInitializer(origin, priority, hooker); } @@ -86,7 +86,7 @@ public final Object invokeOrigin(@NonNull Method method, @Nullable Object thisOb } @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 { mBase.invokeOrigin(constructor, thisObject, args); } @@ -103,7 +103,7 @@ public final Object invokeSpecial(@NonNull Method method, @NonNull Object thisOb } @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 { mBase.invokeSpecial(constructor, thisObject, args); } @@ -126,25 +126,25 @@ public final DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnno @NonNull @Override - public SharedPreferences getRemotePreferences(@NonNull String name) { + public final SharedPreferences getRemotePreferences(@NonNull String name) { return mBase.getRemotePreferences(name); } @NonNull @Override - public ApplicationInfo getApplicationInfo() { + public final ApplicationInfo getApplicationInfo() { return mBase.getApplicationInfo(); } @NonNull @Override - public String[] listRemoteFiles() { + public final String[] listRemoteFiles() { return mBase.listRemoteFiles(); } @NonNull @Override - public ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException { + public final ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException { return mBase.openRemoteFile(name); } } From bc0bd04faefe0ad871b07facadb41a907b329dba Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 20 Feb 2026 21:54:05 +0100 Subject: [PATCH 12/37] Update api/src/main/java/io/github/libxposed/api/XposedInterface.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- api/src/main/java/io/github/libxposed/api/XposedInterface.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1d56e4f..e9cfccc 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -257,7 +257,7 @@ interface CtorHookHandle extends HookHandle> { * @param args The arguments used for the construction * @see Constructor#newInstance(Object...) */ - void invokeOrigin(@Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + void invokeOrigin(@NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** * Invoke the original constructor, but keeps all higher priority hooks. From 4bcbd4bd42863dd2548e2c5210dfa1e4f8f23ccc Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 20 Feb 2026 22:16:21 +0100 Subject: [PATCH 13/37] RFC: Copilot suggestions --- .../github/libxposed/api/XposedInterface.java | 3 +- .../libxposed/api/XposedInterfaceWrapper.java | 29 +++++++++++++++++++ .../libxposed/api/XposedModuleInterface.java | 3 +- 3 files changed, 32 insertions(+), 3 deletions(-) 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 e9cfccc..4344102 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -266,6 +266,7 @@ interface CtorHookHandle extends HookHandle> { * @return The instance created and initialized by the constructor * @see Constructor#newInstance(Object...) */ + @NonNull T newInstanceOrigin(Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; } @@ -356,7 +357,7 @@ interface CtorHookHandle extends HookHandle> { * 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 executable The method to deoptimize + * @param executable The method / constructor to deoptimize * @return Indicate whether the deoptimizing succeed or not */ boolean deoptimize(@NonNull Executable executable); 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 01928fd..e14c33b 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -31,120 +31,149 @@ public class XposedInterfaceWrapper implements XposedInterface { */ @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 @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() { + ensureAttached(); return mBase.getFrameworkPrivilege(); } @NonNull @Override public final MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker) { + ensureAttached(); return mBase.hook(origin, priority, hooker); } @NonNull @Override public final CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { + ensureAttached(); return mBase.hook(origin, priority, hooker); } @NonNull @Override public final MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { + ensureAttached(); return mBase.hookClassInitializer(origin, priority, hooker); } @Override 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 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 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 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); } @Nullable @Override public final DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException { + ensureAttached(); return mBase.parseDex(dexData, includeAnnotations); } @NonNull @Override public final SharedPreferences getRemotePreferences(@NonNull String name) { + ensureAttached(); return mBase.getRemotePreferences(name); } @NonNull @Override public final ApplicationInfo getApplicationInfo() { + ensureAttached(); return mBase.getApplicationInfo(); } @NonNull @Override public final String[] listRemoteFiles() { + ensureAttached(); return mBase.listRemoteFiles(); } @NonNull @Override 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/XposedModuleInterface.java b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java index ef51b45..941f5d1 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java @@ -1,6 +1,5 @@ package io.github.libxposed.api; -import android.app.AppComponentFactory; import android.content.pm.ApplicationInfo; import android.os.Build; @@ -93,7 +92,7 @@ interface PackageLoadedParam { /** * 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. + * {@link android.app.AppComponentFactory} is created. * * @param param Information about the process in which the module is loaded */ From aa8751b98e4c3c2da97f85b7175eb2dd201bdde8 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 20 Feb 2026 23:30:12 +0100 Subject: [PATCH 14/37] RFC: Replace framework privilege with capabilities --- .../github/libxposed/api/XposedInterface.java | 24 ++++++++----------- .../libxposed/api/XposedInterfaceWrapper.java | 4 ++-- 2 files changed, 12 insertions(+), 16 deletions(-) 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 4344102..5a55fe4 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -29,22 +29,17 @@ public interface XposedInterface { int API = 100; /** - * Indicates that the framework is running as root. + * The framework has the capability to hook system_server and other system processes. */ - int FRAMEWORK_PRIVILEGE_ROOT = 0; + long CAP_SYSTEM = 1; /** - * Indicates that the framework is running in a container with a fake system_server. + * The framework provides remote preferences and remote files support. */ - int FRAMEWORK_PRIVILEGE_CONTAINER = 1; + long CAP_REMOTE = 1 << 1; /** - * Indicates that the framework is running as a different app, which may have at most shell permission. + * The framework allows dynamically loaded code to use Xposed APIs. */ - int FRAMEWORK_PRIVILEGE_APP = 2; - /** - * Indicates that the framework is embedded in the hooked app, - * which means {@link #getRemotePreferences} will be null and remote file is unsupported. - */ - int FRAMEWORK_PRIVILEGE_EMBEDDED = 3; + long CAP_RT_API_REFLECTION = 1 << 2; /** * The default hook priority. @@ -294,11 +289,12 @@ interface CtorHookHandle extends HookHandle> { 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 specified priority. 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 e14c33b..9820fea 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -64,9 +64,9 @@ public final long getFrameworkVersionCode() { } @Override - public final int getFrameworkPrivilege() { + public final long getFrameworkCapabilities() { ensureAttached(); - return mBase.getFrameworkPrivilege(); + return mBase.getFrameworkCapabilities(); } @NonNull From ad48420018841b0369d567f892507c11dcf60e3d Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sat, 21 Feb 2026 07:50:04 +0100 Subject: [PATCH 15/37] RFC: Abandon the use of hooker class. Use callback object instead. The side effects of individual hooker class are too severe while introducing minimum advantage. Because dsl and lambda becomes impossible, every module would implement its own hook dispatchers, making cooperation impossible and destroy hook dump readability. --- .../github/libxposed/api/XposedInterface.java | 80 +++++++++++-------- .../libxposed/api/XposedInterfaceWrapper.java | 6 +- 2 files changed, 48 insertions(+), 38 deletions(-) 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 5a55fe4..aeab3c2 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -152,57 +152,67 @@ 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 VoidHooker {
      *
-     *       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 { + } + + /** + * A hooker without context. + */ + interface VoidHooker extends Hooker { + + default void before(@NonNull BeforeHookCallback callback) { + } + + default void after(@NonNull AfterHookCallback callback) { + } + } + + /** + * 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) { + } } /** @@ -301,28 +311,28 @@ interface CtorHookHandle extends HookHandle> { * * @param origin The method to be hooked * @param priority The hook priority - * @param hooker The hooker class + * @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 - MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker); + MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Hooker hooker); /** * Hook a constructor with specified priority. * * @param origin The constructor to be hooked * @param priority The hook priority - * @param hooker The hooker class + * @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 - CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Class hooker); + CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Hooker> hooker); /** * Hook the static initializer of a class with specified priority. @@ -332,13 +342,13 @@ interface CtorHookHandle extends HookHandle> { * * @param origin The class to be hooked * @param priority The hook priority - * @param hooker The hooker class + * @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 - MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker); + MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Hooker hooker); /** * Deoptimizes a method / constructor in case hooked callee is not called because of inline. 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 9820fea..44b6834 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -71,21 +71,21 @@ public final long getFrameworkCapabilities() { @NonNull @Override - public final MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Class hooker) { + public final MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Hooker hooker) { ensureAttached(); return mBase.hook(origin, priority, hooker); } @NonNull @Override - public final CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { + public final CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Hooker> hooker) { ensureAttached(); return mBase.hook(origin, priority, hooker); } @NonNull @Override - public final MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { + public final MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Hooker hooker) { ensureAttached(); return mBase.hookClassInitializer(origin, priority, hooker); } From 2b92a76ac3c3b5b2ac7055c55fef3cd646e850fa Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sat, 21 Feb 2026 09:52:54 +0100 Subject: [PATCH 16/37] RFC: Add getApiVersion --- .../io/github/libxposed/api/XposedInterface.java | 12 +++++++----- .../github/libxposed/api/XposedInterfaceWrapper.java | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) 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 aeab3c2..fdfff98 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -23,11 +23,6 @@ */ @SuppressWarnings("unused") public interface XposedInterface { - /** - * SDK API version. - */ - int API = 100; - /** * The framework has the capability to hook system_server and other system processes. */ @@ -275,6 +270,13 @@ interface CtorHookHandle extends HookHandle> { 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. * 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 44b6834..867ec9d 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -43,6 +43,12 @@ private void ensureAttached() { } } + @Override + public int getApiVersion() { + ensureAttached(); + return mBase.getApiVersion(); + } + @NonNull @Override public final String getFrameworkName() { From 44e46c0aae151649ef88316969acff2b823b57ab Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sat, 21 Feb 2026 12:51:53 +0100 Subject: [PATCH 17/37] RFC: Simplify hookClassInitializer --- api/src/main/java/io/github/libxposed/api/XposedInterface.java | 2 +- .../java/io/github/libxposed/api/XposedInterfaceWrapper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 fdfff98..788a673 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -350,7 +350,7 @@ interface CtorHookHandle extends HookHandle> { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Hooker hooker); + MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Hooker hooker); /** * Deoptimizes a method / constructor in case hooked callee is not called because of inline. 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 867ec9d..d08d8d5 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -91,7 +91,7 @@ public final CtorHookHandle hook(@NonNull Constructor origin, int prio @NonNull @Override - public final MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Hooker hooker) { + public final MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Hooker hooker) { ensureAttached(); return mBase.hookClassInitializer(origin, priority, hooker); } From 570343d127f5d5509a3a845d63ec5aa842bd6bf5 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sun, 22 Feb 2026 08:09:09 +0100 Subject: [PATCH 18/37] RFC: Move priority into hooker, simplify hook interface --- .../github/libxposed/api/XposedInterface.java | 19 +++++++++++++------ .../libxposed/api/XposedInterfaceWrapper.java | 12 ++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) 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 788a673..98be0a8 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -181,6 +181,16 @@ interface AfterHookCallback { * }
*/ 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; + } } /** @@ -312,7 +322,6 @@ interface CtorHookHandle extends HookHandle> { * Hook a method with specified priority. * * @param origin The method to be hooked - * @param priority The hook priority * @param hooker The hooker object * @return Handle for the hook * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, @@ -320,13 +329,12 @@ interface CtorHookHandle extends HookHandle> { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Hooker hooker); + MethodHookHandle hook(@NonNull Method origin, @NonNull Hooker hooker); /** * Hook a constructor with specified priority. * * @param origin The constructor to be hooked - * @param priority The hook priority * @param hooker The hooker object * @return Handle for the hook * @throws IllegalArgumentException if origin is framework internal or {@link Constructor#newInstance}, @@ -334,7 +342,7 @@ interface CtorHookHandle extends HookHandle> { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Hooker> hooker); + CtorHookHandle hook(@NonNull Constructor origin, @NonNull Hooker> hooker); /** * Hook the static initializer of a class with specified priority. @@ -343,14 +351,13 @@ interface CtorHookHandle extends HookHandle> { *

* * @param origin The class to be hooked - * @param priority The hook priority * @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 - MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Hooker hooker); + MethodHookHandle hookClassInitializer(@NonNull Class origin, @NonNull Hooker hooker); /** * Deoptimizes a method / constructor in case hooked callee is not called because of inline. 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 d08d8d5..e3377c5 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -77,23 +77,23 @@ public final long getFrameworkCapabilities() { @NonNull @Override - public final MethodHookHandle hook(@NonNull Method origin, int priority, @NonNull Hooker hooker) { + public final MethodHookHandle hook(@NonNull Method origin, @NonNull Hooker hooker) { ensureAttached(); - return mBase.hook(origin, priority, hooker); + return mBase.hook(origin, hooker); } @NonNull @Override - public final CtorHookHandle hook(@NonNull Constructor origin, int priority, @NonNull Hooker> hooker) { + public final CtorHookHandle hook(@NonNull Constructor origin, @NonNull Hooker> hooker) { ensureAttached(); - return mBase.hook(origin, priority, hooker); + return mBase.hook(origin, hooker); } @NonNull @Override - public final MethodHookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull Hooker hooker) { + public final MethodHookHandle hookClassInitializer(@NonNull Class origin, @NonNull Hooker hooker) { ensureAttached(); - return mBase.hookClassInitializer(origin, priority, hooker); + return mBase.hookClassInitializer(origin, hooker); } @Override From c1612ac05d48c2fe69a16ad1622d48995fc04bf7 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sun, 22 Feb 2026 08:26:31 +0100 Subject: [PATCH 19/37] RFC: Refine docs --- .../main/java/io/github/libxposed/api/XposedInterface.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 98be0a8..5b15def 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -319,7 +319,7 @@ interface CtorHookHandle extends HookHandle> { long getFrameworkCapabilities(); /** - * Hook a method with specified priority. + * Hook a method. * * @param origin The method to be hooked * @param hooker The hooker object @@ -332,7 +332,7 @@ interface CtorHookHandle extends HookHandle> { MethodHookHandle hook(@NonNull Method origin, @NonNull Hooker hooker); /** - * Hook a constructor with specified priority. + * Hook a constructor. * * @param origin The constructor to be hooked * @param hooker The hooker object @@ -345,7 +345,7 @@ interface CtorHookHandle extends HookHandle> { 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. *

From 6386db99828effe8130ca43a8eb901a5f63b8460 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sun, 22 Feb 2026 08:41:25 +0100 Subject: [PATCH 20/37] RFC: Fix wrong type --- .../main/java/io/github/libxposed/api/XposedInterface.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5b15def..92fd43b 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -198,10 +198,10 @@ default int getPriority() { */ interface VoidHooker extends Hooker { - default void before(@NonNull BeforeHookCallback callback) { + default void before(@NonNull BeforeHookCallback callback) { } - default void after(@NonNull AfterHookCallback callback) { + default void after(@NonNull AfterHookCallback callback) { } } From bc2b440ba1b8b80a66b785d8a93bb6ea8d2956ec Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sun, 22 Feb 2026 09:11:05 +0100 Subject: [PATCH 21/37] RFC: Rename VoidHooker -> SimpleHooker to prevent misunderstanding --- .../main/java/io/github/libxposed/api/XposedInterface.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 92fd43b..2a8de8c 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -152,7 +152,7 @@ interface AfterHookCallback { *

Example usage:

* *
{@code
-     *   public class ExampleHooker implements VoidHooker {
+     *   public class ExampleHooker implements SimpleHooker {
      *
      *       @Override
      *       void before(@NonNull BeforeHookCallback callback) {
@@ -196,7 +196,7 @@ default int getPriority() {
     /**
      * A hooker without context.
      */
-    interface VoidHooker extends Hooker {
+    interface SimpleHooker extends Hooker {
 
         default void before(@NonNull BeforeHookCallback callback) {
         }

From 3e7973a9894dbca94ee9b20560651882e783a314 Mon Sep 17 00:00:00 2001
From: Nullptr 
Date: Sun, 22 Feb 2026 09:43:36 +0100
Subject: [PATCH 22/37] RFC: Correct long value

---
 .../main/java/io/github/libxposed/api/XposedInterface.java  | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

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 2a8de8c..5c4b956 100644
--- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java
+++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java
@@ -26,15 +26,15 @@ public interface XposedInterface {
     /**
      * The framework has the capability to hook system_server and other system processes.
      */
-    long CAP_SYSTEM = 1;
+    long CAP_SYSTEM = 1L;
     /**
      * The framework provides remote preferences and remote files support.
      */
-    long CAP_REMOTE = 1 << 1;
+    long CAP_REMOTE = 1L << 1;
     /**
      * The framework allows dynamically loaded code to use Xposed APIs.
      */
-    long CAP_RT_API_REFLECTION = 1 << 2;
+    long CAP_RT_API_REFLECTION = 1L << 2;
 
     /**
      * The default hook priority.

From b5696c83f59d65d8a326daf91aa95b043b3e5172 Mon Sep 17 00:00:00 2001
From: Nullptr 
Date: Tue, 24 Feb 2026 11:05:35 +0100
Subject: [PATCH 23/37] RFC: Rename CAP_RT_API_REFLECTION ->
 CAP_RT_DYNAMIC_CODE_API_ACCESS

---
 api/src/main/java/io/github/libxposed/api/XposedInterface.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 5c4b956..f787b15 100644
--- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java
+++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java
@@ -34,7 +34,7 @@ public interface XposedInterface {
     /**
      * The framework allows dynamically loaded code to use Xposed APIs.
      */
-    long CAP_RT_API_REFLECTION = 1L << 2;
+    long CAP_RT_DYNAMIC_CODE_API_ACCESS = 1L << 2;
 
     /**
      * The default hook priority.

From e4067226ebfca766bdfd4dd3f4adcb9492a9d845 Mon Sep 17 00:00:00 2001
From: Nullptr 
Date: Tue, 24 Feb 2026 11:07:34 +0100
Subject: [PATCH 24/37] RFC: Make getApiVersion final

---
 .../java/io/github/libxposed/api/XposedInterfaceWrapper.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 e3377c5..40eb168 100644
--- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java
+++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java
@@ -44,7 +44,7 @@ private void ensureAttached() {
     }
 
     @Override
-    public int getApiVersion() {
+    public final int getApiVersion() {
         ensureAttached();
         return mBase.getApiVersion();
     }

From 3794e4c0e290f526c8fa5bd8974f6fe303fd3464 Mon Sep 17 00:00:00 2001
From: Nullptr 
Date: Tue, 24 Feb 2026 12:59:39 +0100
Subject: [PATCH 25/37] RFC: Add log without tr

---
 .../github/libxposed/api/XposedInterface.java | 23 ++++++++++++++-----
 .../libxposed/api/XposedInterfaceWrapper.java |  6 +++++
 2 files changed, 23 insertions(+), 6 deletions(-)

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 f787b15..6ba05c9 100644
--- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java
+++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java
@@ -321,8 +321,8 @@ interface CtorHookHandle extends HookHandle> {
     /**
      * Hook a method.
      *
-     * @param origin   The method to be hooked
-     * @param hooker   The hooker object
+     * @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
@@ -334,8 +334,8 @@ interface CtorHookHandle extends HookHandle> {
     /**
      * Hook a constructor.
      *
-     * @param origin   The constructor to be hooked
-     * @param hooker   The hooker object
+     * @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
@@ -350,8 +350,8 @@ interface CtorHookHandle extends HookHandle> {
      * Note: If the class is initialized, the hook will never be called.
      * 

* - * @param origin The class to be hooked - * @param hooker The hooker object + * @param origin The class to be hooked + * @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 @@ -465,6 +465,17 @@ interface CtorHookHandle extends HookHandle> { @NonNull U newInstanceSpecial(@NonNull Constructor constructor, @NonNull Class subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; + /** + * Writes a message to the Xposed log. + * + * @param priority The log priority, see {@link android.util.Log} + * @param tag The log tag + * @param msg The log message + */ + default void log(int priority, @Nullable String tag, @NonNull String msg) { + log(priority, tag, msg, null); + } + /** * Writes a message to the Xposed log. * 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 40eb168..e1e3033 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -142,6 +142,12 @@ public final U newInstanceSpecial(@NonNull Constructor constructor, @N return mBase.newInstanceSpecial(constructor, subClass, args); } + @Override + public final void log(int priority, @Nullable String tag, @NonNull String msg) { + ensureAttached(); + mBase.log(priority, tag, msg, null); + } + @Override public final void log(int priority, @Nullable String tag, @NonNull String msg, @Nullable Throwable tr) { ensureAttached(); From 28a4d3cafadcd19a78cbaafed47139d55b98ddc4 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Tue, 24 Feb 2026 20:31:35 +0100 Subject: [PATCH 26/37] RFC: Abandon AOP style interface. Use OkHttp style interceptor for method hooks --- .../github/libxposed/api/XposedInterface.java | 452 +++++++++--------- .../libxposed/api/XposedInterfaceWrapper.java | 44 +- 2 files changed, 242 insertions(+), 254 deletions(-) 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 6ba05c9..b743300 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -41,58 +41,114 @@ public interface XposedInterface { */ int PRIORITY_DEFAULT = 50; /** - * Execute the hook callback late. + * Execute at the end of the interception chain. */ int PRIORITY_LOWEST = -10000; /** - * Execute the hook callback early. + * Execute at the beginning of the interception chain. */ int PRIORITY_HIGHEST = 10000; /** - * Contextual interface for before invocation callbacks. + * Invoker for a method or constructor. + * + * @param {@link Method} or {@link Constructor} + */ + interface Invoker { + } + + /** + * Invoker for a method. */ - interface BeforeHookCallback { + interface MethodInvoker extends Invoker { /** - * Gets the method / constructor being hooked. + * Invoke the method interception chain starting from the invoker's priority. + * + * @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...) */ - @NonNull - T getExecutable(); + @Nullable + Object invoke(@Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** - * Gets the {@code this} object, or {@code null} if the method is static. + * 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 + * object. This method is useful when you need to call a specific method on an object, bypassing any + * overridden methods in subclasses and directly invoking the method defined in the specified class. + * + *

This method is useful when you need to call {@code super.xxx()} in a hooked constructor.

+ * + * @param thisObject The {@code this} pointer + * @param args The arguments used for the method call + * @return The result returned from the invoked method + * @see Method#invoke(Object, Object...) */ @Nullable - Object getThisObject(); + Object invokeSpecial(@NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + } + /** + * Invoker for a constructor. + * + * @param The type of the constructor + */ + interface CtorInvoker extends Invoker> { /** - * Gets the arguments passed to the method / constructor. You can modify the arguments. + * Invoke the constructor interception chain as a method starting from the invoker's priority. + * + * @param thisObject The instance to be constructed + * @param args The arguments used for the construction + * @see Constructor#newInstance(Object...) + */ + void invoke(@NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + + /** + * Invoke the constructor starting from the invoker's priority. + * + * @param args The arguments used for the construction + * @return The instance created and initialized by the constructor + * @see Constructor#newInstance(Object...) */ @NonNull - Object[] getArgs(); + T newInstance(Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; /** - * Sets the return value of the method and skip the invocation. If the procedure is a constructor, - * the {@code result} param will be ignored. - * Note that the after invocation callback will still be called. + * 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 + * object. This method is useful when you need to call a specific method on an object, bypassing any + * overridden methods in subclasses and directly invoking the method defined in the specified class. + * + *

This method is useful when you need to call {@code super.xxx()} in a hooked constructor.

* - * @param result The return value + * @param thisObject The instance to be constructed + * @param args The arguments used for the construction + * @see Constructor#newInstance(Object...) */ - void returnAndSkip(@Nullable Object result); + void invokeSpecial(@NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** - * Throw an exception from the method / constructor and skip the invocation. - * Note that the after invocation callback will still be called. + * 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 + * of the subclass are not initialized. + * + *

This method is useful when you need to initialize some fields in the subclass by yourself.

* - * @param throwable The exception to be thrown + * @param The type of the subclass + * @param subClass The subclass to create a new instance + * @param args The arguments used for the construction + * @return The instance of subclass initialized by the constructor + * @see Constructor#newInstance(Object...) */ - void throwAndSkip(@Nullable Throwable throwable); + @NonNull + U newInstanceSpecial(@NonNull Class subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; } /** - * Contextual interface for after invocation callbacks. + * Interceptor chain for a method or constructor. */ - interface AfterHookCallback { + interface Chain { /** * Gets the method / constructor being hooked. */ @@ -100,184 +156,147 @@ interface AfterHookCallback { T getExecutable(); /** - * Gets the {@code this} object, or {@code null} if the method is static. + * Gets the arguments used for the method call or construction. + */ + @NonNull + Object[] getArgs(); + } + + /** + * Interceptor chain for a method. + */ + interface MethodChain extends Chain { + /** + * Gets the {@code this} pointer for the method call, or {@code null} for static calls. */ @Nullable Object getThisObject(); /** - * Gets all arguments passed to the method / constructor. + * Proceeds to the next interceptor in the chain with the same arguments and {@code this} pointer. + * + * @return The result returned from next interceptor or the original method if current + * interceptor is the last one in the chain + * @throws Throwable if any interceptor or the original method throws an exception */ - @NonNull - Object[] getArgs(); + @Nullable + Object proceed() throws Throwable; /** - * Gets the return value of the method or the before invocation callback. If the procedure is a - * constructor, a void method or an exception was thrown, the return value will be {@code null}. + * Proceeds to the next interceptor in the chain with the given arguments and the same {@code this} pointer. + * + * @param args The arguments used for the method call + * @return The result returned from next interceptor or the original method if current + * interceptor is the last one in the chain + * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable - Object getResult(); + Object proceed(Object... args) throws Throwable; /** - * Gets the exception thrown by the method / constructor or the before invocation callback. If the - * procedure call was successful, the return value will be {@code null}. + * Proceeds to the next interceptor in the chain with the given arguments and {@code this} pointer. + * + * @param thisObject The {@code this} pointer for the method call, or {@code null} for static calls + * @param args The arguments used for the method call + * @return The result returned from next interceptor or the original method if current + * interceptor is the last one in the chain + * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable - Throwable getThrowable(); + Object proceedWith(Object thisObject, Object... args) throws Throwable; + } + /** + * Interceptor chain for a constructor. + */ + interface CtorChain extends Chain> { /** - * Gets whether the invocation was skipped by the before invocation callback. + * Gets the instance being constructed. Note that the instance may be not fully initialized when + * the chain is called. */ - boolean isSkipped(); + @NonNull + Object getThisObject(); /** - * Sets the return value of the method and skip the invocation. If the procedure is a constructor, - * the {@code result} param will be ignored. + * Proceeds to the next interceptor in the chain with the same arguments and {@code this} pointer. * - * @param result The return value + * @throws Throwable if any interceptor or the original constructor throws an exception */ - void setResult(@Nullable Object result); + void proceed() throws Throwable; /** - * Sets the exception thrown by the method / constructor. + * Proceeds to the next interceptor in the chain with the given arguments and the same {@code this} pointer. * - * @param throwable The exception to be thrown. + * @param args The arguments used for the construction + * @throws Throwable if any interceptor or the original constructor throws an exception */ - void setThrowable(@Nullable Throwable throwable); - } + void proceed(Object... args) throws Throwable; - /** - * Interface for method / constructor hooking. - * - *

Example usage:

- * - *
{@code
-     *   public class ExampleHooker implements SimpleHooker {
-     *
-     *       @Override
-     *       void before(@NonNull BeforeHookCallback callback) {
-     *           // Pre-hooking logic goes here
-     *       }
-     *
-     *       @Override
-     *       void after(@NonNull AfterHookCallback callback) {
-     *           // Post-hooking logic goes here
-     *       }
-     *   }
-     *
-     *   public class ExampleHookerWithContext implements ContextualHooker {
-     *
-     *       @Override
-     *       MyContext before(@NonNull BeforeHookCallback callback) {
-     *           // Pre-hooking logic goes here
-     *           return new MyContext();
-     *       }
-     *
-     *       @Override
-     *       void after(@NonNull AfterHookCallback callback, MyContext context) {
-     *           // Post-hooking logic goes here
-     *       }
-     *   }
-     * }
- */ - 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. + * Proceeds to the next interceptor in the chain with the given arguments and {@code this} pointer. * - * @return The priority of the hook + * @param thisObject The instance being constructed + * @param args The arguments used for the construction + * @throws Throwable if any interceptor or the original constructor throws an exception */ - default int getPriority() { - return PRIORITY_DEFAULT; - } + void proceedWith(Object thisObject, Object... args) throws Throwable; } /** - * A hooker without context. - */ - interface SimpleHooker extends Hooker { - - default void before(@NonNull BeforeHookCallback callback) { - } - - default void after(@NonNull AfterHookCallback callback) { - } - } - - /** - * A hooker with context. The context object is guaranteed to be the same between before and after - * invocation. + * Hooker for a method or constructor. * - * @param The type of the context + * @param {@link Method} or {@link Constructor} */ - interface ContextualHooker extends Hooker { - default C before(@NonNull BeforeHookCallback callback) { - return null; - } - - default void after(@NonNull AfterHookCallback callback, @Nullable C context) { - } + interface Hooker { } /** - * Handle for a hook. - * - * @param {@link Method} or {@link Constructor} + * Hooker for a method. */ - interface HookHandle { + interface MethodHooker extends Hooker { /** - * Gets the method / constructor being hooked. - */ - @NonNull - T getExecutable(); - - /** - * Cancels the hook. The behavior of calling this method multiple times is undefined. + * Intercepts a method call. + * + * @param chain The interceptor chain for the method call + * @return The result to be returned from the interceptor. If the hooker does not want to + * change the result, it should call {@code chain.proceed()} and return its result. + * @throws Throwable Throw any exception from the interceptor. The exception will + * propagate to the caller if not caught by any interceptor. */ - void unhook(); + @Nullable + Object intercept(@NonNull MethodChain chain) throws Throwable; } /** - * Handle for a method hook. + * Hooker for a constructor. */ - interface MethodHookHandle extends HookHandle { + interface CtorHooker extends Hooker> { /** - * Invoke the original method, but keeps all higher priority hooks. + * Intercepts a constructor call. * - * @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...) + * @param chain The interceptor chain for the constructor call + * @throws Throwable Throw any exception from the interceptor. The exception will + * propagate to the caller if not caught by any interceptor. */ - @Nullable - Object invokeOrigin(@Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + void intercept(@NonNull CtorChain chain) throws Throwable; } /** - * Handle for a constructor hook. + * Handle for a hook. * - * @param The type of the constructor + * @param {@link Method} or {@link Constructor} */ - interface CtorHookHandle extends HookHandle> { + interface 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...) + * Gets the method / constructor being hooked. */ - void invokeOrigin(@NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + @NonNull + T getExecutable(); /** - * 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...) + * Cancels the hook. The behavior of calling this method multiple times is undefined. */ - @NonNull - T newInstanceOrigin(Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; + void unhook(); } /** @@ -319,7 +338,7 @@ interface CtorHookHandle extends HookHandle> { long getFrameworkCapabilities(); /** - * Hook a method. + * Hook a method with default priority. * * @param origin The method to be hooked * @param hooker The hooker object @@ -329,10 +348,24 @@ interface CtorHookHandle extends HookHandle> { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - MethodHookHandle hook(@NonNull Method origin, @NonNull Hooker hooker); + HookHandle hook(@NonNull Method origin, @NonNull MethodHooker hooker); + + /** + * Hook a method with the given priority. + * + * @param origin The method to be hooked + * @param priority The priority of the hook + * @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 + HookHandle hook(@NonNull Method origin, int priority, @NonNull MethodHooker hooker); /** - * Hook a constructor. + * Hook a constructor with default priority. * * @param origin The constructor to be hooked * @param hooker The hooker object @@ -342,7 +375,21 @@ interface CtorHookHandle extends HookHandle> { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - CtorHookHandle hook(@NonNull Constructor origin, @NonNull Hooker> hooker); + HookHandle> hook(@NonNull Constructor origin, @NonNull CtorHooker hooker); + + /** + * Hook a constructor with the given priority. + * + * @param origin The constructor to be hooked + * @param priority The priority of the hook + * @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 + HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull CtorHooker hooker); /** * Hook the static initializer of a class. @@ -357,7 +404,23 @@ interface CtorHookHandle extends HookHandle> { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - MethodHookHandle hookClassInitializer(@NonNull Class origin, @NonNull Hooker hooker); + HookHandle hookClassInitializer(@NonNull Class origin, @NonNull MethodHooker hooker); + + /** + * Hook the static initializer of a class with the given priority. + *

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

+ * + * @param origin The class to be hooked + * @param priority The priority of 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 + HookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull MethodHooker hooker); /** * Deoptimizes a method / constructor in case hooked callee is not called because of inline. @@ -378,92 +441,25 @@ interface CtorHookHandle extends HookHandle> { boolean deoptimize(@NonNull Executable executable); /** - * 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} - * @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(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; - - /** - * 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. + * Get a method invoker for the given method and priority. * - * @param constructor The constructor to create and initialize a new instance - * @param thisObject The instance to be constructed - * @param args The arguments used for the construction - * @param The type of the instance - * @see Constructor#newInstance(Object...) - */ - 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...) + * @param method The method to get the invoker for + * @param priority The priority of the invoker, or null for the original method without any hooks. + * @return The method invoker */ @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 - * object. This method is useful when you need to call a specific method on an object, bypassing any - * overridden methods in subclasses and directly invoking the method defined in the specified class. - * - *

This method is useful when you need to call {@code super.xxx()} in a hooked constructor.

- * - * @param method The method to be called - * @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 invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + MethodInvoker getInvoker(@NonNull Method method, @Nullable Integer priority); /** - * 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 - * object. This method is useful when you need to call a specific method on an object, bypassing any - * overridden methods in subclasses and directly invoking the method defined in the specified class. + * Get a constructor invoker for the given constructor and priority. * - *

This method is useful when you need to call {@code super.xxx()} in a hooked constructor.

- * - * @param constructor The constructor to create and initialize a new instance - * @param thisObject The instance to be constructed - * @param args The arguments used for the construction - * @see Constructor#newInstance(Object...) - */ - void invokeSpecial(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; - - /** - * 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 - * of the subclass are not initialized. - * - *

This method is useful when you need to initialize some fields in the subclass by yourself.

- * - * @param The type of the parent constructor - * @param The type of the subclass - * @param constructor The parent constructor to initialize a new instance - * @param subClass The subclass to create a new instance - * @param args The arguments used for the construction - * @return The instance of subclass initialized by the constructor - * @see Constructor#newInstance(Object...) + * @param constructor The constructor to get the invoker for + * @param priority The priority of the invoker, or null for the original constructor without any hooks. + * @param The type of the constructor + * @return The constructor invoker */ @NonNull - U newInstanceSpecial(@NonNull Constructor constructor, @NonNull Class subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; + CtorInvoker getInvoker(@NonNull Constructor constructor, @Nullable Integer priority); /** * Writes a message to the Xposed log. @@ -472,9 +468,7 @@ interface CtorHookHandle extends HookHandle> { * @param tag The log tag * @param msg The log message */ - default void log(int priority, @Nullable String tag, @NonNull String msg) { - log(priority, tag, msg, null); - } + void log(int priority, @Nullable String tag, @NonNull String msg); /** * Writes a message to the Xposed log. 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 e1e3033..de7500c 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -11,7 +11,6 @@ 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; @@ -77,69 +76,64 @@ public final long getFrameworkCapabilities() { @NonNull @Override - public final MethodHookHandle hook(@NonNull Method origin, @NonNull Hooker hooker) { + public final HookHandle hook(@NonNull Method origin, @NonNull MethodHooker hooker) { ensureAttached(); return mBase.hook(origin, hooker); } @NonNull @Override - public final CtorHookHandle hook(@NonNull Constructor origin, @NonNull Hooker> hooker) { + public final HookHandle> hook(@NonNull Constructor origin, @NonNull CtorHooker hooker) { ensureAttached(); return mBase.hook(origin, hooker); } @NonNull @Override - public final MethodHookHandle hookClassInitializer(@NonNull Class origin, @NonNull Hooker hooker) { + public HookHandle hook(@NonNull Method origin, int priority, @NonNull MethodHooker hooker) { ensureAttached(); - return mBase.hookClassInitializer(origin, hooker); + return mBase.hook(origin, priority, hooker); } + @NonNull @Override - public final boolean deoptimize(@NonNull Executable executable) { + public final HookHandle hookClassInitializer(@NonNull Class origin, @NonNull MethodHooker hooker) { ensureAttached(); - return mBase.deoptimize(executable); + return mBase.hookClassInitializer(origin, hooker); } - @Nullable + @NonNull @Override - public final Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + public HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull CtorHooker hooker) { ensureAttached(); - return mBase.invokeOrigin(method, thisObject, args); + return mBase.hook(origin, priority, hooker); } @Override - public final void invokeOrigin(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + public final boolean deoptimize(@NonNull Executable executable) { ensureAttached(); - mBase.invokeOrigin(constructor, thisObject, args); + return mBase.deoptimize(executable); } @NonNull @Override - public final T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { + public HookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull MethodHooker hooker) { 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); + return mBase.hookClassInitializer(origin, priority, hooker); } + @NonNull @Override - public final void invokeSpecial(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + public MethodInvoker getInvoker(@NonNull Method method, @Nullable Integer priority) { ensureAttached(); - mBase.invokeSpecial(constructor, thisObject, args); + return mBase.getInvoker(method, priority); } @NonNull @Override - public final U newInstanceSpecial(@NonNull Constructor constructor, @NonNull Class subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { + public CtorInvoker getInvoker(@NonNull Constructor constructor, @Nullable Integer priority) { ensureAttached(); - return mBase.newInstanceSpecial(constructor, subClass, args); + return mBase.getInvoker(constructor, priority); } @Override From 21d7244c3a3443fb00ce7880f309efb6dcf9fa3c Mon Sep 17 00:00:00 2001 From: Nullptr Date: Wed, 25 Feb 2026 20:08:02 +0100 Subject: [PATCH 27/37] RFC: Use unmodifiable list for getArgs, and make proceed more convenient --- .../github/libxposed/api/XposedInterface.java | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) 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 b743300..603fc5a 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -14,6 +14,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; +import java.util.List; import io.github.libxposed.api.errors.HookFailedError; import io.github.libxposed.api.utils.DexParser; @@ -156,10 +157,23 @@ interface Chain { T getExecutable(); /** - * Gets the arguments used for the method call or construction. + * Gets the arguments. The returned list is immutable. If you want to change the arguments, you + * should call {@code proceed(Object...)} or {@code proceedWith(Object, Object...)} with the new + * arguments. */ @NonNull - Object[] getArgs(); + List getArgs(); + + /** + * Gets the argument at the given index. + * + * @param index The argument index + * @return The argument at the given index + * @throws IndexOutOfBoundsException if index is out of bounds + * @throws ClassCastException if the argument cannot be cast to the expected type + */ + @Nullable + U getArg(int index) throws IndexOutOfBoundsException, ClassCastException; } /** @@ -193,8 +207,21 @@ interface MethodChain extends Chain { @Nullable Object proceed(Object... args) throws Throwable; + /** + * Proceeds to the next interceptor in the chain with the same arguments and given {@code this} pointer. + * Static method interceptors should not call this. + * + * @param thisObject The {@code this} pointer for the method call, or {@code null} for static calls + * @return The result returned from next interceptor or the original method if current + * interceptor is the last one in the chain + * @throws Throwable if any interceptor or the original method throws an exception + */ + @Nullable + Object proceedWith(@NonNull Object thisObject) throws Throwable; + /** * Proceeds to the next interceptor in the chain with the given arguments and {@code this} pointer. + * Static method interceptors should not call this. * * @param thisObject The {@code this} pointer for the method call, or {@code null} for static calls * @param args The arguments used for the method call @@ -203,7 +230,7 @@ interface MethodChain extends Chain { * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable - Object proceedWith(Object thisObject, Object... args) throws Throwable; + Object proceedWith(@NonNull Object thisObject, Object... args) throws Throwable; } /** @@ -232,6 +259,14 @@ interface CtorChain extends Chain> { */ void proceed(Object... args) throws Throwable; + /** + * Proceeds to the next interceptor in the chain with the same arguments and given {@code this} pointer. + * + * @param thisObject The instance being constructed + * @throws Throwable if any interceptor or the original constructor throws an exception + */ + void proceedWith(@NonNull Object thisObject) throws Throwable; + /** * Proceeds to the next interceptor in the chain with the given arguments and {@code this} pointer. * @@ -239,7 +274,7 @@ interface CtorChain extends Chain> { * @param args The arguments used for the construction * @throws Throwable if any interceptor or the original constructor throws an exception */ - void proceedWith(Object thisObject, Object... args) throws Throwable; + void proceedWith(@NonNull Object thisObject, Object... args) throws Throwable; } /** From 6c228e099e31a99ca6f22667fa8bcf1d4d0b48f9 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Wed, 25 Feb 2026 21:39:37 +0100 Subject: [PATCH 28/37] RFC: Refine Invoker --- api/build.gradle.kts | 6 +- .../github/libxposed/api/XposedInterface.java | 64 ++++++++++++++++--- .../libxposed/api/XposedInterfaceWrapper.java | 22 +++++-- 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index bcdc9fa..bcd003b 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -7,7 +7,7 @@ plugins { android { namespace = "io.github.libxposed.api" compileSdk = 36 - buildToolsVersion = "35.0.0" + buildToolsVersion = "36.1.0" androidResources.enable = false defaultConfig { @@ -20,8 +20,8 @@ android { } compileOptions { - targetCompatibility = JavaVersion.VERSION_1_8 - sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_21 } publishing { 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 603fc5a..93e4903 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -44,11 +44,11 @@ public interface XposedInterface { /** * Execute at the end of the interception chain. */ - int PRIORITY_LOWEST = -10000; + int PRIORITY_LOWEST = Integer.MIN_VALUE; /** * Execute at the beginning of the interception chain. */ - int PRIORITY_HIGHEST = 10000; + int PRIORITY_HIGHEST = Integer.MAX_VALUE; /** * Invoker for a method or constructor. @@ -56,6 +56,31 @@ public interface XposedInterface { * @param {@link Method} or {@link Constructor} */ interface Invoker { + sealed interface Type permits Type.Origin, Type.Chain { + /** + * Invoking the original executable, skipping all the hooks + */ + Origin ORIGIN = new Origin(); + + /** + * Invoking the original executable, skipping all the hooks + */ + record Origin() implements Type { + } + + /** + * Invoking the executable starting from the middle of the hook chain, skipping all the + * hooks with priority higher than the given value. + * + * @param maxPriority + */ + record Chain(int maxPriority) implements Type { + /** + * Invoking the executable with full hook chain. + */ + public static final Chain FULL = new Chain(PRIORITY_HIGHEST); + } + } } /** @@ -476,25 +501,46 @@ interface HookHandle { boolean deoptimize(@NonNull Executable executable); /** - * Get a method invoker for the given method and priority. + * Get a method invoker for the given method with full hook chain. * - * @param method The method to get the invoker for - * @param priority The priority of the invoker, or null for the original method without any hooks. + * @param method The method to get the invoker for * @return The method invoker */ @NonNull - MethodInvoker getInvoker(@NonNull Method method, @Nullable Integer priority); + MethodInvoker getInvoker(@NonNull Method method); /** - * Get a constructor invoker for the given constructor and priority. + * Get a method invoker for the given method and type. + * + * @param method The method to get the invoker for + * @param type The type of the invoker, can be used to invoke the original method or to invoke + * the method starting from a specific priority in the hook chain + * @return The method invoker + */ + @NonNull + MethodInvoker getInvoker(@NonNull Method method, @NonNull Invoker.Type type); + + /** + * Get a constructor invoker for the given constructor with full hook chain. + * + * @param constructor The constructor to get the invoker for + * @param The type of the constructor + * @return The constructor invoker + */ + @NonNull + CtorInvoker getInvoker(@NonNull Constructor constructor); + + /** + * Get a constructor invoker for the given constructor and type. * * @param constructor The constructor to get the invoker for - * @param priority The priority of the invoker, or null for the original constructor without any hooks. + * @param type The type of the invoker, can be used to invoke the original method or to invoke + * the method starting from a specific priority in the hook chain * @param The type of the constructor * @return The constructor invoker */ @NonNull - CtorInvoker getInvoker(@NonNull Constructor constructor, @Nullable Integer priority); + CtorInvoker getInvoker(@NonNull Constructor constructor, @NonNull Invoker.Type type); /** * Writes a message to the Xposed log. 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 de7500c..954e5e7 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -124,16 +124,30 @@ public HookHandle hookClassInitializer(@NonNull Class origin, int pri @NonNull @Override - public MethodInvoker getInvoker(@NonNull Method method, @Nullable Integer priority) { + public MethodInvoker getInvoker(@NonNull Method method) { ensureAttached(); - return mBase.getInvoker(method, priority); + return mBase.getInvoker(method); } @NonNull @Override - public CtorInvoker getInvoker(@NonNull Constructor constructor, @Nullable Integer priority) { + public MethodInvoker getInvoker(@NonNull Method method, @NonNull Invoker.Type type) { ensureAttached(); - return mBase.getInvoker(constructor, priority); + return mBase.getInvoker(method, type); + } + + @NonNull + @Override + public CtorInvoker getInvoker(@NonNull Constructor constructor) { + ensureAttached(); + return mBase.getInvoker(constructor); + } + + @NonNull + @Override + public CtorInvoker getInvoker(@NonNull Constructor constructor, @NonNull Invoker.Type type) { + ensureAttached(); + return mBase.getInvoker(constructor, type); } @Override From 5efd3980a91baff0f5dbd5ae666fe5dfe0621751 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Thu, 26 Feb 2026 02:09:56 +0100 Subject: [PATCH 29/37] RFC: Standardization code and docs --- .../github/libxposed/api/XposedInterface.java | 17 +++++------ .../libxposed/api/XposedInterfaceWrapper.java | 28 +++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) 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 93e4903..286da72 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -215,7 +215,7 @@ interface MethodChain extends Chain { * Proceeds to the next interceptor in the chain with the same arguments and {@code this} pointer. * * @return The result returned from next interceptor or the original method if current - * interceptor is the last one in the chain + * interceptor is the last one in the chain. For void methods, always returns {@code null}. * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable @@ -226,7 +226,7 @@ interface MethodChain extends Chain { * * @param args The arguments used for the method call * @return The result returned from next interceptor or the original method if current - * interceptor is the last one in the chain + * interceptor is the last one in the chain. For void methods, always returns {@code null}. * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable @@ -236,9 +236,9 @@ interface MethodChain extends Chain { * Proceeds to the next interceptor in the chain with the same arguments and given {@code this} pointer. * Static method interceptors should not call this. * - * @param thisObject The {@code this} pointer for the method call, or {@code null} for static calls + * @param thisObject The {@code this} pointer for the method call * @return The result returned from next interceptor or the original method if current - * interceptor is the last one in the chain + * interceptor is the last one in the chain. For void methods, always returns {@code null}. * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable @@ -248,10 +248,10 @@ interface MethodChain extends Chain { * Proceeds to the next interceptor in the chain with the given arguments and {@code this} pointer. * Static method interceptors should not call this. * - * @param thisObject The {@code this} pointer for the method call, or {@code null} for static calls + * @param thisObject The {@code this} pointer for the method call * @param args The arguments used for the method call * @return The result returned from next interceptor or the original method if current - * interceptor is the last one in the chain + * interceptor is the last one in the chain. For void methods, always returns {@code null}. * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable @@ -267,7 +267,7 @@ interface CtorChain extends Chain> { * the chain is called. */ @NonNull - Object getThisObject(); + T getThisObject(); /** * Proceeds to the next interceptor in the chain with the same arguments and {@code this} pointer. @@ -320,6 +320,7 @@ interface MethodHooker extends Hooker { * @param chain The interceptor chain for the method call * @return The result to be returned from the interceptor. If the hooker does not want to * change the result, it should call {@code chain.proceed()} and return its result. + *

For void methods, the return value is ignored by the framework.

* @throws Throwable Throw any exception from the interceptor. The exception will * propagate to the caller if not caught by any interceptor. */ @@ -354,7 +355,7 @@ interface HookHandle { T getExecutable(); /** - * Cancels the hook. The behavior of calling this method multiple times is undefined. + * Cancels the hook. This method is idempotent. It is safe to call this method multiple times. */ void unhook(); } 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 954e5e7..8cc35e5 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -81,6 +81,13 @@ public final HookHandle hook(@NonNull Method origin, @NonNull MethodHook return mBase.hook(origin, hooker); } + @NonNull + @Override + public final HookHandle hook(@NonNull Method origin, int priority, @NonNull MethodHooker hooker) { + ensureAttached(); + return mBase.hook(origin, priority, hooker); + } + @NonNull @Override public final HookHandle> hook(@NonNull Constructor origin, @NonNull CtorHooker hooker) { @@ -90,7 +97,7 @@ public final HookHandle> hook(@NonNull Constructor origin, @NonNull @Override - public HookHandle hook(@NonNull Method origin, int priority, @NonNull MethodHooker hooker) { + public final HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull CtorHooker hooker) { ensureAttached(); return mBase.hook(origin, priority, hooker); } @@ -104,9 +111,9 @@ public final HookHandle hookClassInitializer(@NonNull Class origin, @ @NonNull @Override - public HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull CtorHooker hooker) { + public final HookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull MethodHooker hooker) { ensureAttached(); - return mBase.hook(origin, priority, hooker); + return mBase.hookClassInitializer(origin, priority, hooker); } @Override @@ -117,35 +124,28 @@ public final boolean deoptimize(@NonNull Executable executable) { @NonNull @Override - public HookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull MethodHooker hooker) { - ensureAttached(); - return mBase.hookClassInitializer(origin, priority, hooker); - } - - @NonNull - @Override - public MethodInvoker getInvoker(@NonNull Method method) { + public final MethodInvoker getInvoker(@NonNull Method method) { ensureAttached(); return mBase.getInvoker(method); } @NonNull @Override - public MethodInvoker getInvoker(@NonNull Method method, @NonNull Invoker.Type type) { + public final MethodInvoker getInvoker(@NonNull Method method, @NonNull Invoker.Type type) { ensureAttached(); return mBase.getInvoker(method, type); } @NonNull @Override - public CtorInvoker getInvoker(@NonNull Constructor constructor) { + public final CtorInvoker getInvoker(@NonNull Constructor constructor) { ensureAttached(); return mBase.getInvoker(constructor); } @NonNull @Override - public CtorInvoker getInvoker(@NonNull Constructor constructor, @NonNull Invoker.Type type) { + public final CtorInvoker getInvoker(@NonNull Constructor constructor, @NonNull Invoker.Type type) { ensureAttached(); return mBase.getInvoker(constructor, type); } From fdbbc0619db8b54338141ac8fc5e109b692106b1 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Thu, 26 Feb 2026 10:14:27 +0100 Subject: [PATCH 30/37] RFC: Use builder mode for hooks, simplifying interface --- api/build.gradle.kts | 1 + .../github/libxposed/api/XposedInterface.java | 176 ++++++++++-------- .../libxposed/api/XposedInterfaceWrapper.java | 35 +--- .../libxposed/api/XposedModuleInterface.java | 8 +- 4 files changed, 114 insertions(+), 106 deletions(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index bcd003b..17d46d6 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -93,5 +93,6 @@ signing { dependencies { compileOnly(libs.annotation) + compileOnly(libs.kotlin.stdlib) lintPublish(project(":checks")) } 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 286da72..5ef60c2 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -58,21 +58,21 @@ public interface XposedInterface { interface Invoker { sealed interface Type permits Type.Origin, Type.Chain { /** - * Invoking the original executable, skipping all the hooks + * A convenience constant for {@link Origin}. */ Origin ORIGIN = new Origin(); /** - * Invoking the original executable, skipping all the hooks + * Invokes the original executable, skipping all hooks. */ record Origin() implements Type { } /** - * Invoking the executable starting from the middle of the hook chain, skipping all the + * Invokes the executable starting from the middle of the hook chain, skipping all * hooks with priority higher than the given value. * - * @param maxPriority + * @param maxPriority The maximum priority of hooks to include in the chain */ record Chain(int maxPriority) implements Type { /** @@ -88,7 +88,7 @@ record Chain(int maxPriority) implements Type { */ interface MethodInvoker extends Invoker { /** - * Invoke the method interception chain starting from the invoker's priority. + * Invokes the method through the hook chain determined by the invoker's type. * * @param thisObject For non-static calls, the {@code this} pointer, otherwise {@code null} * @param args The arguments used for the method call @@ -122,7 +122,8 @@ interface MethodInvoker extends Invoker { */ interface CtorInvoker extends Invoker> { /** - * Invoke the constructor interception chain as a method starting from the invoker's priority. + * Invokes the constructor as a method on an existing instance through the hook chain + * determined by the invoker's type. * * @param thisObject The instance to be constructed * @param args The arguments used for the construction @@ -131,7 +132,7 @@ interface CtorInvoker extends Invoker> { void invoke(@NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** - * Invoke the constructor starting from the invoker's priority. + * Creates a new instance through the hook chain determined by the invoker's type. * * @param args The arguments used for the construction * @return The instance created and initialized by the constructor @@ -155,8 +156,8 @@ interface CtorInvoker extends Invoker> { void invokeSpecial(@NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** - * 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 + * Creates a new instance of the given subclass, but initializes it with a parent constructor. This could + * leave the object in an invalid state, where the subclass constructor is not called and the fields * of the subclass are not initialized. * *

This method is useful when you need to initialize some fields in the subclass by yourself.

@@ -328,6 +329,21 @@ interface MethodHooker extends Hooker { Object intercept(@NonNull MethodChain chain) throws Throwable; } + /** + * Utility hooker for a void method. Used in Java lambda where unit return type is not supported. + */ + @kotlin.Deprecated(message = "Hidden from Kotlin", level = kotlin.DeprecationLevel.HIDDEN) + interface VoidMethodHooker extends Hooker { + /** + * Intercepts a method call. + * + * @param chain The interceptor chain for the method call + * @throws Throwable Throw any exception from the interceptor. The exception will + * propagate to the caller if not caught by any interceptor. + */ + void intercept(@NonNull MethodChain chain) throws Throwable; + } + /** * Hooker for a constructor. */ @@ -360,6 +376,73 @@ interface HookHandle { void unhook(); } + /** + * Builder for configuring a hook. + * + * @param The concrete builder type for chaining + * @param {@link Method} or {@link Constructor} + */ + interface HookBuilder, U extends Executable> { + /** + * Sets the priority of the hook. Hooks with higher priority will be called before hooks with lower + * priority. The default priority is {@link XposedInterface#PRIORITY_DEFAULT}. + * + * @param priority The priority of the hook + * @return The builder itself for chaining + */ + T setPriority(int priority); + } + + /** + * Builder for a method hook. + */ + interface MethodHookBuilder extends HookBuilder { + /** + * Sets the hooker for the method and builds the hook. + * + * @param hooker The hooker object + * @return The 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 + HookHandle intercept(@NonNull MethodHooker hooker); + + /** + * Sets a void hooker for the method and builds the hook. This is a utility method for Java + * lambda where unit return type is not supported. + * + * @param hooker The hooker object + * @return The 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 + */ + @kotlin.Deprecated(message = "Hidden from Kotlin", level = kotlin.DeprecationLevel.HIDDEN) + @NonNull + HookHandle intercept(@NonNull VoidMethodHooker hooker); + } + + /** + * Builder for a constructor hook. + * + * @param The type of the constructor + */ + interface CtorHookBuilder extends HookBuilder, Constructor> { + /** + * Sets the hooker for the constructor and builds the hook. + * + * @param hooker The hooker object + * @return The 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 + HookHandle> intercept(@NonNull CtorHooker hooker); + } + /** * Gets the Xposed API version of current implementation. * @@ -399,58 +482,22 @@ interface HookHandle { long getFrameworkCapabilities(); /** - * Hook a method with default priority. + * Hook a method. * * @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 - HookHandle hook(@NonNull Method origin, @NonNull MethodHooker hooker); - - /** - * Hook a method with the given priority. - * - * @param origin The method to be hooked - * @param priority The priority of the hook - * @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 + * @return The builder for the hook */ @NonNull - HookHandle hook(@NonNull Method origin, int priority, @NonNull MethodHooker hooker); + MethodHookBuilder hook(@NonNull Method origin); /** - * Hook a constructor with default priority. + * Hook a constructor. * * @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 - HookHandle> hook(@NonNull Constructor origin, @NonNull CtorHooker hooker); - - /** - * Hook a constructor with the given priority. - * - * @param origin The constructor to be hooked - * @param priority The priority of the hook - * @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 + * @return The builder for the hook */ @NonNull - HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull CtorHooker hooker); + CtorHookBuilder hook(@NonNull Constructor origin); /** * Hook the static initializer of a class. @@ -459,39 +506,20 @@ interface HookHandle { *

* * @param origin The class to be hooked - * @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 - HookHandle hookClassInitializer(@NonNull Class origin, @NonNull MethodHooker hooker); - - /** - * Hook the static initializer of a class with the given priority. - *

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

- * - * @param origin The class to be hooked - * @param priority The priority of 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 + * @return The builder for the hook */ @NonNull - HookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull MethodHooker hooker); + MethodHookBuilder hookClassInitializer(@NonNull Class origin); /** * 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. + *

By deoptimizing the method, the runtime will fall back to calling all callees without inlining. * For example, when a short hooked method B is invoked by method A, the callback to B is not invoked * after hooking, which may mean A has inlined B inside its method body. To force A to call the hooked B, * you can deoptimize A and then your hook can take effect.

* - *

Generally, you need to find all the callers of your hooked callee and that can be hardly achieve + *

Generally, you need to find all the callers of your hooked callee, and that can hardly be achieved * (but you can still search all callers by using {@link DexParser}). Use this method if you are sure * 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).

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 8cc35e5..aec48aa 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -17,7 +17,7 @@ import io.github.libxposed.api.utils.DexParser; /** - * Wrap of {@link XposedInterface} used by the modules for the purpose of shielding framework implementation details. + * Wrapper of {@link XposedInterface} used by modules to shield framework implementation details. */ public class XposedInterfaceWrapper implements XposedInterface { @@ -76,44 +76,23 @@ public final long getFrameworkCapabilities() { @NonNull @Override - public final HookHandle hook(@NonNull Method origin, @NonNull MethodHooker hooker) { + public final MethodHookBuilder hook(@NonNull Method origin) { ensureAttached(); - return mBase.hook(origin, hooker); + return mBase.hook(origin); } @NonNull @Override - public final HookHandle hook(@NonNull Method origin, int priority, @NonNull MethodHooker hooker) { + public final CtorHookBuilder hook(@NonNull Constructor origin) { ensureAttached(); - return mBase.hook(origin, priority, hooker); + return mBase.hook(origin); } @NonNull @Override - public final HookHandle> hook(@NonNull Constructor origin, @NonNull CtorHooker hooker) { + public final MethodHookBuilder hookClassInitializer(@NonNull Class origin) { ensureAttached(); - return mBase.hook(origin, hooker); - } - - @NonNull - @Override - public final HookHandle> hook(@NonNull Constructor origin, int priority, @NonNull CtorHooker hooker) { - ensureAttached(); - return mBase.hook(origin, priority, hooker); - } - - @NonNull - @Override - public final HookHandle hookClassInitializer(@NonNull Class origin, @NonNull MethodHooker hooker) { - ensureAttached(); - return mBase.hookClassInitializer(origin, hooker); - } - - @NonNull - @Override - public final HookHandle hookClassInitializer(@NonNull Class origin, int priority, @NonNull MethodHooker hooker) { - ensureAttached(); - return mBase.hookClassInitializer(origin, priority, hooker); + return mBase.hookClassInitializer(origin); } @Override 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 941f5d1..3989cb8 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java @@ -16,9 +16,9 @@ public interface XposedModuleInterface { */ interface ModuleLoadedParam { /** - * Gets information about whether the module is running in system server. + * Returns whether the current process is system server. * - * @return {@code true} if the module is running in system server + * @return {@code true} if the current process is system server */ boolean isSystemServer(); @@ -67,7 +67,7 @@ interface PackageLoadedParam { /** * Gets default class loader. * - * @return the default class loader + * @return The default class loader */ @RequiresApi(Build.VERSION_CODES.Q) @NonNull @@ -82,7 +82,7 @@ interface PackageLoadedParam { ClassLoader getClassLoader(); /** - * Gets information about whether is this package the first and main package of the app process. + * Returns whether this is the first and main package loaded in the app process. * * @return {@code true} if this is the first package. */ From 8f7d25910048edb6a43e2cce4af565119882a27b Mon Sep 17 00:00:00 2001 From: Nullptr Date: Thu, 26 Feb 2026 14:51:44 +0100 Subject: [PATCH 31/37] RFC: Simplify Invoker interface --- .../github/libxposed/api/XposedInterface.java | 41 ++++++------------- .../libxposed/api/XposedInterfaceWrapper.java | 14 ------- 2 files changed, 13 insertions(+), 42 deletions(-) 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 5ef60c2..5ac7923 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -55,7 +55,10 @@ public interface XposedInterface { * * @param {@link Method} or {@link Constructor} */ - interface Invoker { + interface Invoker, U extends Executable> { + /** + * Type of the invoker, which determines the hook chain to be invoked + */ sealed interface Type permits Type.Origin, Type.Chain { /** * A convenience constant for {@link Origin}. @@ -81,12 +84,17 @@ record Chain(int maxPriority) implements Type { public static final Chain FULL = new Chain(PRIORITY_HIGHEST); } } + + /** + * Sets the type of the invoker, which determines the hook chain to be invoked + */ + T setType(@NonNull Type type); } /** * Invoker for a method. */ - interface MethodInvoker extends Invoker { + interface MethodInvoker extends Invoker { /** * Invokes the method through the hook chain determined by the invoker's type. * @@ -120,7 +128,7 @@ interface MethodInvoker extends Invoker { * * @param The type of the constructor */ - interface CtorInvoker extends Invoker> { + interface CtorInvoker extends Invoker, Constructor> { /** * Invokes the constructor as a method on an existing instance through the hook chain * determined by the invoker's type. @@ -530,7 +538,7 @@ interface CtorHookBuilder extends HookBuilder, Constructor boolean deoptimize(@NonNull Executable executable); /** - * Get a method invoker for the given method with full hook chain. + * Get a method invoker for the given method. * * @param method The method to get the invoker for * @return The method invoker @@ -539,18 +547,7 @@ interface CtorHookBuilder extends HookBuilder, Constructor MethodInvoker getInvoker(@NonNull Method method); /** - * Get a method invoker for the given method and type. - * - * @param method The method to get the invoker for - * @param type The type of the invoker, can be used to invoke the original method or to invoke - * the method starting from a specific priority in the hook chain - * @return The method invoker - */ - @NonNull - MethodInvoker getInvoker(@NonNull Method method, @NonNull Invoker.Type type); - - /** - * Get a constructor invoker for the given constructor with full hook chain. + * Get a constructor invoker for the given constructor. * * @param constructor The constructor to get the invoker for * @param The type of the constructor @@ -559,18 +556,6 @@ interface CtorHookBuilder extends HookBuilder, Constructor @NonNull CtorInvoker getInvoker(@NonNull Constructor constructor); - /** - * Get a constructor invoker for the given constructor and type. - * - * @param constructor The constructor to get the invoker for - * @param type The type of the invoker, can be used to invoke the original method or to invoke - * the method starting from a specific priority in the hook chain - * @param The type of the constructor - * @return The constructor invoker - */ - @NonNull - CtorInvoker getInvoker(@NonNull Constructor constructor, @NonNull Invoker.Type type); - /** * Writes a message to the Xposed log. * 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 aec48aa..877c820 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -108,13 +108,6 @@ public final MethodInvoker getInvoker(@NonNull Method method) { return mBase.getInvoker(method); } - @NonNull - @Override - public final MethodInvoker getInvoker(@NonNull Method method, @NonNull Invoker.Type type) { - ensureAttached(); - return mBase.getInvoker(method, type); - } - @NonNull @Override public final CtorInvoker getInvoker(@NonNull Constructor constructor) { @@ -122,13 +115,6 @@ public final CtorInvoker getInvoker(@NonNull Constructor constructor) return mBase.getInvoker(constructor); } - @NonNull - @Override - public final CtorInvoker getInvoker(@NonNull Constructor constructor, @NonNull Invoker.Type type) { - ensureAttached(); - return mBase.getInvoker(constructor, type); - } - @Override public final void log(int priority, @Nullable String tag, @NonNull String msg) { ensureAttached(); From 73ee713b4da5084721032141a7dc590774e660e1 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 27 Feb 2026 11:57:57 +0100 Subject: [PATCH 32/37] RFC: Update docs --- .../main/java/io/github/libxposed/api/XposedInterface.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 5ac7923..dc830c8 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -538,7 +538,8 @@ interface CtorHookBuilder extends HookBuilder, Constructor boolean deoptimize(@NonNull Executable executable); /** - * Get a method invoker for the given method. + * Get a method invoker for the given method. The default type of the invoker is + * {@link Invoker.Type.Chain#FULL}. * * @param method The method to get the invoker for * @return The method invoker @@ -547,7 +548,8 @@ interface CtorHookBuilder extends HookBuilder, Constructor MethodInvoker getInvoker(@NonNull Method method); /** - * Get a constructor invoker for the given constructor. + * Get a constructor invoker for the given constructor. The default type of the invoker is + * {@link Invoker.Type.Chain#FULL}. * * @param constructor The constructor to get the invoker for * @param The type of the constructor From b38c8c81078b2f21880771ac127b7f191da2423f Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 27 Feb 2026 12:00:04 +0100 Subject: [PATCH 33/37] RFC: Separate onPackageLoaded and onPackageReady, enabling control of AppComponentFactory logic --- .../libxposed/api/XposedModuleInterface.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) 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 3989cb8..af75cb4 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java @@ -74,25 +74,26 @@ interface PackageLoadedParam { ClassLoader getDefaultClassLoader(); /** - * Gets the class loader of the package being loaded. + * Returns whether this is the first and main package loaded in the app process. * - * @return The class loader. + * @return {@code true} if this is the first package. */ - @NonNull - ClassLoader getClassLoader(); + boolean isFirstPackage(); + } + interface PackageReadyParam extends PackageLoadedParam { /** - * Returns whether this is the first and main package loaded in the app process. + * Gets the class loader of the package being loaded. * - * @return {@code true} if this is the first package. + * @return The class loader. */ - boolean isFirstPackage(); + @NonNull + ClassLoader getClassLoader(); } /** * 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. + * This callback is guaranteed to be called exactly once for a process. * * @param param Information about the process in which the module is loaded */ @@ -100,14 +101,26 @@ default void onModuleLoaded(@NonNull ModuleLoadedParam param) { } /** - * Gets notified when a package is loaded into the app process.
+ * Gets notified when a package is loaded into the app process. This is the time when the default + * classloader is ready but before the instantiation of custom {@link android.app.AppComponentFactory}.
* This callback could be invoked multiple times for the same process on each package. * * @param param Information about the package being loaded */ + @RequiresApi(Build.VERSION_CODES.Q) default void onPackageLoaded(@NonNull PackageLoadedParam param) { } + /** + * Gets notified when custom {@link android.app.AppComponentFactory} has instantiated the app + * classloader and is ready to create {@link android.app.Activity} and {@link android.app.Service}.
+ * This callback could be invoked multiple times for the same process on each package. + * + * @param param Information about the package being loaded + */ + default void onPackageReady(@NonNull PackageReadyParam param) { + } + /** * Gets notified when the system server is loaded. * From f589dc12b2ce64acf5aa78f2b20683bd0c0f3759 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 27 Feb 2026 12:23:21 +0100 Subject: [PATCH 34/37] RFC: Add getAppComponentFactory for PackageReadyParam --- .../libxposed/api/XposedModuleInterface.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) 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 af75cb4..ac6b4cf 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; @@ -49,7 +50,7 @@ interface SystemServerLoadedParam { */ interface PackageLoadedParam { /** - * Gets the package name of the package being loaded. + * Gets the package name of the current package. * * @return The package name. */ @@ -57,7 +58,7 @@ interface PackageLoadedParam { String getPackageName(); /** - * Gets the {@link ApplicationInfo} of the package being loaded. + * Gets the {@link ApplicationInfo} of the current package. * * @return The ApplicationInfo. */ @@ -65,9 +66,8 @@ interface PackageLoadedParam { ApplicationInfo getApplicationInfo(); /** - * Gets default class loader. - * - * @return The default class loader + * Gets the default classloader of the current package. This is the classloader that loads + * the app's code, resources and custom {@link AppComponentFactory}. */ @RequiresApi(Build.VERSION_CODES.Q) @NonNull @@ -83,12 +83,18 @@ interface PackageLoadedParam { interface PackageReadyParam extends PackageLoadedParam { /** - * Gets the class loader of the package being loaded. - * - * @return The class loader. + * Gets the classloader of the current package. It may be different from {@link #getDefaultClassLoader()} + * if the package has a custom {@link android.app.AppComponentFactory} that creates a different classloader. */ @NonNull ClassLoader getClassLoader(); + + /** + * Gets the {@link AppComponentFactory} of the current package. + */ + @RequiresApi(Build.VERSION_CODES.P) + @NonNull + AppComponentFactory getAppComponentFactory(); } /** From 9af7dd149e8614dcc26e14b486ba00c13f865c4b Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sun, 1 Mar 2026 19:36:46 +0100 Subject: [PATCH 35/37] RFC: Rename onSystemServerLoaded -> onSystemServerStarting to avoid misunderstanding --- .../libxposed/api/XposedModuleInterface.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) 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 ac6b4cf..6c940d2 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java @@ -32,19 +32,6 @@ interface ModuleLoadedParam { String getProcessName(); } - /** - * Wraps information about system server. - */ - interface SystemServerLoadedParam { - /** - * Gets the class loader of system server. - * - * @return The class loader - */ - @NonNull - ClassLoader getClassLoader(); - } - /** * Wraps information about the package being loaded. */ @@ -81,6 +68,9 @@ interface PackageLoadedParam { boolean isFirstPackage(); } + /** + * Wraps information about the package whose classloader is ready. + */ interface PackageReadyParam extends PackageLoadedParam { /** * Gets the classloader of the current package. It may be different from {@link #getDefaultClassLoader()} @@ -97,6 +87,17 @@ interface PackageReadyParam extends PackageLoadedParam { AppComponentFactory getAppComponentFactory(); } + /** + * Wraps information about system server. + */ + interface SystemServerStartingParam { + /** + * Gets the class loader of system server. + */ + @NonNull + ClassLoader getClassLoader(); + } + /** * Gets notified when the module is loaded into the target process.
* This callback is guaranteed to be called exactly once for a process. @@ -128,10 +129,10 @@ default void onPackageReady(@NonNull PackageReadyParam param) { } /** - * Gets notified when the system server is loaded. + * Gets notified when system server is ready to start critical services. * * @param param Information about system server */ - default void onSystemServerLoaded(@NonNull SystemServerLoadedParam param) { + default void onSystemServerStarting(@NonNull SystemServerStartingParam param) { } } From 922a66f988f53abac44f5b7e926bb3c83841e23c Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sun, 1 Mar 2026 20:34:32 +0100 Subject: [PATCH 36/37] RFC: Fix proguard and more docs --- api/proguard-rules.pro | 14 ++++++++------ .../io/github/libxposed/api/XposedInterface.java | 4 ++-- .../libxposed/api/XposedInterfaceWrapper.java | 2 +- .../libxposed/api/errors/HookFailedError.java | 5 +++++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/api/proguard-rules.pro b/api/proguard-rules.pro index ac904d7..ae38534 100644 --- a/api/proguard-rules.pro +++ b/api/proguard-rules.pro @@ -1,8 +1,10 @@ -keep class io.github.libxposed.** { *; } --keepclassmembers,allowoptimization class ** implements io.github.libxposed.api.XposedInterface$Hooker { - public static *** before(); - public static *** before(io.github.libxposed.api.XposedInterface$BeforeHookCallback); - public static void after(); - public static void after(io.github.libxposed.api.XposedInterface$AfterHookCallback); - public static void after(io.github.libxposed.api.XposedInterface$AfterHookCallback, ***); +-keepclassmembers,allowoptimization class ** implements io.github.libxposed.api.XposedInterface$MethodHooker { + java.lang.Object intercept(io.github.libxposed.api.XposedInterface$MethodChain); +} +-keepclassmembers,allowoptimization class ** implements io.github.libxposed.api.XposedInterface$VoidMethodHooker { + void intercept(io.github.libxposed.api.XposedInterface$MethodChain); +} +-keepclassmembers,allowoptimization class ** implements io.github.libxposed.api.XposedInterface$CtorHooker { + void intercept(io.github.libxposed.api.XposedInterface$CtorChain); } 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 dc830c8..a630b3d 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -299,7 +299,7 @@ interface CtorChain extends Chain> { * @param thisObject The instance being constructed * @throws Throwable if any interceptor or the original constructor throws an exception */ - void proceedWith(@NonNull Object thisObject) throws Throwable; + void proceedWith(@NonNull T thisObject) throws Throwable; /** * Proceeds to the next interceptor in the chain with the given arguments and {@code this} pointer. @@ -308,7 +308,7 @@ interface CtorChain extends Chain> { * @param args The arguments used for the construction * @throws Throwable if any interceptor or the original constructor throws an exception */ - void proceedWith(@NonNull Object thisObject, Object... args) throws Throwable; + void proceedWith(@NonNull T thisObject, Object... args) throws Throwable; } /** 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 877c820..d573ac7 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -118,7 +118,7 @@ public final CtorInvoker getInvoker(@NonNull Constructor constructor) @Override public final void log(int priority, @Nullable String tag, @NonNull String msg) { ensureAttached(); - mBase.log(priority, tag, msg, null); + mBase.log(priority, tag, msg); } @Override diff --git a/api/src/main/java/io/github/libxposed/api/errors/HookFailedError.java b/api/src/main/java/io/github/libxposed/api/errors/HookFailedError.java index 0eb4b05..9224eaf 100644 --- a/api/src/main/java/io/github/libxposed/api/errors/HookFailedError.java +++ b/api/src/main/java/io/github/libxposed/api/errors/HookFailedError.java @@ -2,6 +2,11 @@ /** * Thrown to indicate that a hook failed due to framework internal error. + *

+ * Design Note: This inherits from {@link Error} rather than {@link RuntimeException} because hook failures are + * considered fatal framework bugs. Module developers should not attempt to catch this error to provide + * fallbacks. Instead, please report the issue to the framework maintainers so it can be fixed at the root. + *

*/ @SuppressWarnings("unused") public class HookFailedError extends XposedFrameworkError { From d63e95ef96bd4c30af45eb1380a12b46bd002d3a Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sun, 1 Mar 2026 20:51:32 +0100 Subject: [PATCH 37/37] RFC: Change version to 101.0.0 --- api/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 17d46d6..3f5cf94 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -37,7 +37,7 @@ publishing { register("api") { artifactId = "api" group = "io.github.libxposed" - version = "100" + version = "101.0.0" pom { name.set("api") description.set("Modern Xposed API")