From 128ead6aab8861c73ae739719af669f373023e7a Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 28 Oct 2024 15:58:54 +0100 Subject: [PATCH 01/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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") From 229f62bbf0744ab90df5d97d944d361ededacee9 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 2 Mar 2026 08:20:43 +0100 Subject: [PATCH 38/53] RFC: Make getApiVersion fixed --- .../main/java/io/github/libxposed/api/XposedInterface.java | 6 ++++-- .../io/github/libxposed/api/XposedInterfaceWrapper.java | 2 +- 2 files changed, 5 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 a630b3d..71433b4 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -452,11 +452,13 @@ interface CtorHookBuilder extends HookBuilder, Constructor } /** - * Gets the Xposed API version of current implementation. + * Gets the Xposed API version. Framework implementations must not override this method. * * @return API version */ - int getApiVersion(); + default int getApiVersion() { + return 101; + } /** * 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 d573ac7..4e18b88 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -45,7 +45,7 @@ private void ensureAttached() { @Override public final int getApiVersion() { ensureAttached(); - return mBase.getApiVersion(); + return XposedInterface.super.getApiVersion(); } @NonNull From 26de0c2f18afd08c682d62a88c095dbbfaa6aae2 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 2 Mar 2026 08:47:04 +0100 Subject: [PATCH 39/53] RFC: Remove DexParser for now --- .../github/libxposed/api/XposedInterface.java | 20 +- .../libxposed/api/XposedInterfaceWrapper.java | 11 - .../github/libxposed/api/utils/DexParser.java | 376 ------------------ 3 files changed, 3 insertions(+), 404 deletions(-) delete mode 100644 api/src/main/java/io/github/libxposed/api/utils/DexParser.java 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 71433b4..55a23a5 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -8,16 +8,13 @@ import androidx.annotation.Nullable; 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; import java.util.List; import io.github.libxposed.api.errors.HookFailedError; -import io.github.libxposed.api.utils.DexParser; /** * Xposed interface for modules to operate on application processes. @@ -530,9 +527,9 @@ default int getApiVersion() { * 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 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).

+ * (but you can still search all callers by using DexKit). + * 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).

* * @param executable The method / constructor to deoptimize * @return Indicate whether the deoptimizing succeed or not @@ -579,17 +576,6 @@ default int getApiVersion() { */ void log(int priority, @Nullable String tag, @NonNull String msg, @Nullable Throwable tr); - /** - * Parse a dex file in memory. - * - * @param dexData The content of the dex file - * @param includeAnnotations Whether to include annotations - * @return The {@link DexParser} of the dex file - * @throws IOException if the dex file is invalid - */ - @Nullable - DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException; - /** * Gets the application info of the module. */ 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 4e18b88..c902e0c 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -8,13 +8,9 @@ import androidx.annotation.Nullable; import java.io.FileNotFoundException; -import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; -import java.nio.ByteBuffer; - -import io.github.libxposed.api.utils.DexParser; /** * Wrapper of {@link XposedInterface} used by modules to shield framework implementation details. @@ -127,13 +123,6 @@ public final void log(int priority, @Nullable String tag, @NonNull String msg, @ 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) { diff --git a/api/src/main/java/io/github/libxposed/api/utils/DexParser.java b/api/src/main/java/io/github/libxposed/api/utils/DexParser.java deleted file mode 100644 index 00a5f43..0000000 --- a/api/src/main/java/io/github/libxposed/api/utils/DexParser.java +++ /dev/null @@ -1,376 +0,0 @@ -package io.github.libxposed.api.utils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.Closeable; - -/** - * Xposed interface for parsing dex files. - */ -@SuppressWarnings("unused") -public interface DexParser extends Closeable { - /** - * The constant NO_INDEX. - */ - int NO_INDEX = 0xffffffff; - - /** - * The interface Array. - */ - interface Array { - /** - * Get values value [ ]. - * - * @return the value [ ] - */ - @NonNull - Value[] getValues(); - } - - /** - * The interface Annotation. - */ - interface Annotation { - /** - * Gets visibility. - * - * @return the visibility - */ - int getVisibility(); - - /** - * Gets type. - * - * @return the type - */ - @NonNull - TypeId getType(); - - /** - * Get elements element [ ]. - * - * @return the element [ ] - */ - @NonNull - Element[] getElements(); - } - - /** - * The interface Value. - */ - interface Value { - - /** - * Get value byte [ ]. - * - * @return the byte [ ] - */ - @Nullable - byte[] getValue(); - - /** - * Gets value type. - * - * @return the value type - */ - int getValueType(); - } - - /** - * The interface Element. - */ - interface Element extends Value { - /** - * Gets name. - * - * @return the name - */ - @NonNull - StringId getName(); - } - /** - * The interface Id. - */ - interface Id extends Comparable { - /** - * Gets id. - * - * @return the id - */ - int getId(); - } - - /** - * The interface Type id. - */ - interface TypeId extends Id { - /** - * Gets descriptor. - * - * @return the descriptor - */ - @NonNull - StringId getDescriptor(); - } - - - /** - * The interface String id. - */ - interface StringId extends Id { - /** - * Gets string. - * - * @return the string - */ - @NonNull - String getString(); - } - - /** - * The interface Field id. - */ - interface FieldId extends Id { - /** - * Gets type. - * - * @return the type - */ - @NonNull - TypeId getType(); - - /** - * Gets declaring class. - * - * @return the declaring class - */ - @NonNull - TypeId getDeclaringClass(); - - /** - * Gets name. - * - * @return the name - */ - @NonNull - StringId getName(); - } - - /** - * The interface Method id. - */ - interface MethodId extends Id { - /** - * Gets declaring class. - * - * @return the declaring class - */ - @NonNull - TypeId getDeclaringClass(); - - /** - * Gets prototype. - * - * @return the prototype - */ - @NonNull - ProtoId getPrototype(); - - /** - * Gets name. - * - * @return the name - */ - @NonNull - StringId getName(); - } - - /** - * The interface Proto id. - */ - interface ProtoId extends Id { - /** - * Gets shorty. - * - * @return the shorty - */ - @NonNull - StringId getShorty(); - - /** - * Gets return type. - * - * @return the return type - */ - @NonNull - TypeId getReturnType(); - - /** - * Get parameters type id [ ]. - * - * @return the type id [ ] - */ - @Nullable - TypeId[] getParameters(); - } - - /** - * Get string id string id [ ]. - * - * @return the string id [ ] - */ - @NonNull - StringId[] getStringId(); - - /** - * Get type id type id [ ]. - * - * @return the type id [ ] - */ - @NonNull - TypeId[] getTypeId(); - - /** - * Get field id field id [ ]. - * - * @return the field id [ ] - */ - @NonNull - FieldId[] getFieldId(); - - /** - * Get method id method id [ ]. - * - * @return the method id [ ] - */ - @NonNull - MethodId[] getMethodId(); - - /** - * Get proto id proto id [ ]. - * - * @return the proto id [ ] - */ - @NonNull - ProtoId[] getProtoId(); - - /** - * Get annotations annotation [ ]. - * - * @return the annotation [ ] - */ - @NonNull - Annotation[] getAnnotations(); - - /** - * Get arrays array [ ]. - * - * @return the array [ ] - */ - @NonNull - Array[] getArrays(); - - /** - * The interface Early stop visitor. - */ - interface EarlyStopVisitor { - /** - * Stop boolean. - * - * @return the boolean - */ - boolean stop(); - } - - /** - * The interface Member visitor. - */ - interface MemberVisitor extends EarlyStopVisitor { - } - - /** - * The interface Class visitor. - */ - interface ClassVisitor extends EarlyStopVisitor { - /** - * Visit member visitor. - * - * @param clazz the clazz - * @param accessFlags the access flags - * @param superClass the super class - * @param interfaces the interfaces - * @param sourceFile the source file - * @param staticFields the static fields - * @param staticFieldsAccessFlags the static fields access flags - * @param instanceFields the instance fields - * @param instanceFieldsAccessFlags the instance fields access flags - * @param directMethods the direct methods - * @param directMethodsAccessFlags the direct methods access flags - * @param virtualMethods the virtual methods - * @param virtualMethodsAccessFlags the virtual methods access flags - * @param annotations the annotations - * @return the member visitor - */ - @Nullable - MemberVisitor visit(int clazz, int accessFlags, int superClass, @NonNull int[] interfaces, int sourceFile, @NonNull int[] staticFields, @NonNull int[] staticFieldsAccessFlags, @NonNull int[] instanceFields, @NonNull int[] instanceFieldsAccessFlags, @NonNull int[] directMethods, @NonNull int[] directMethodsAccessFlags, @NonNull int[] virtualMethods, @NonNull int[] virtualMethodsAccessFlags, @NonNull int[] annotations); - } - - /** - * The interface Field visitor. - */ - interface FieldVisitor extends MemberVisitor { - /** - * Visit. - * - * @param field the field - * @param accessFlags the access flags - * @param annotations the annotations - */ - void visit(int field, int accessFlags, @NonNull int[] annotations); - } - - /** - * The interface Method visitor. - */ - interface MethodVisitor extends MemberVisitor { - /** - * Visit method body visitor. - * - * @param method the method - * @param accessFlags the access flags - * @param hasBody the has body - * @param annotations the annotations - * @param parameterAnnotations the parameter annotations - * @return the method body visitor - */ - @Nullable - MethodBodyVisitor visit(int method, int accessFlags, boolean hasBody, @NonNull int[] annotations, @NonNull int[] parameterAnnotations); - } - - /** - * The interface Method body visitor. - */ - interface MethodBodyVisitor { - /** - * Visit. - * - * @param method the method - * @param accessFlags the access flags - * @param referredStrings the referred strings - * @param invokedMethods the invoked methods - * @param accessedFields the accessed fields - * @param assignedFields the assigned fields - * @param opcodes the opcodes - */ - void visit(int method, int accessFlags, @NonNull int[] referredStrings, @NonNull int[] invokedMethods, @NonNull int[] accessedFields, @NonNull int[] assignedFields, @NonNull byte[] opcodes); - } - - /** - * Visit defined classes. - * - * @param visitor the visitor - * @throws IllegalStateException the illegal state exception - */ - void visitDefinedClasses(@NonNull ClassVisitor visitor) throws IllegalStateException; -} From c9cbaaf60a5bb03959736dc65d3e3e7266d41961 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 2 Mar 2026 09:03:05 +0100 Subject: [PATCH 40/53] RFC: Suggestions from Copilot --- .../github/libxposed/api/XposedInterface.java | 21 +++++++++++++------ .../libxposed/api/XposedInterfaceWrapper.java | 2 +- 2 files changed, 16 insertions(+), 7 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 55a23a5..e68195b 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -449,7 +449,7 @@ interface CtorHookBuilder extends HookBuilder, Constructor } /** - * Gets the Xposed API version. Framework implementations must not override this method. + * Gets the runtime Xposed API version. Framework implementations must not override this method. * * @return API version */ @@ -507,12 +507,21 @@ default int getApiVersion() { CtorHookBuilder hook(@NonNull Constructor origin); /** - * Hook the static initializer of a class. - *

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

+ * Hook the static initializer ({@code }) of a class. * - * @param origin The class to be hooked + *

The static initializer is treated as a regular {@code static void()} method with no parameters. + * Accordingly, in the {@link MethodChain} passed to the hooker:

+ *
    + *
  • {@link MethodChain#getExecutable()} returns a synthetic {@link Method} representing + * the static initializer.
  • + *
  • {@link MethodChain#getThisObject()} always returns {@code null}.
  • + *
  • {@link MethodChain#getArgs()} returns an empty list.
  • + *
  • {@link MethodChain#proceed()} returns {@code null}.
  • + *
+ * + *

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

+ * + * @param origin The class whose static initializer is to be hooked * @return The builder for the hook */ @NonNull 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 c902e0c..f410783 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 @@ */ public class XposedInterfaceWrapper implements XposedInterface { - private XposedInterface mBase; + private volatile XposedInterface mBase; /** * Attaches the framework interface to the module. Modules should never call this method. From c6b573ce6390ad4960f65cea18eff377b72bc261 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 2 Mar 2026 09:12:42 +0100 Subject: [PATCH 41/53] RFC: Remove checks. It will be implemented in a separate repo --- api/build.gradle.kts | 12 +++++------- checks/.gitignore | 1 - checks/build.gradle.kts | 16 ---------------- .../github/libxposed/lint/XposedIssueRegistry.kt | 9 --------- ...m.android.tools.lint.client.api.IssueRegistry | 1 - settings.gradle.kts | 2 +- 6 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 checks/.gitignore delete mode 100644 checks/build.gradle.kts delete mode 100644 checks/src/main/java/io/github/libxposed/lint/XposedIssueRegistry.kt delete mode 100644 checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 3f5cf94..7c8f37c 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -32,6 +32,11 @@ android { } } +dependencies { + compileOnly(libs.annotation) + compileOnly(libs.kotlin.stdlib) +} + publishing { publications { register("api") { @@ -89,10 +94,3 @@ signing { sign(publishing.publications) } } - - -dependencies { - compileOnly(libs.annotation) - compileOnly(libs.kotlin.stdlib) - lintPublish(project(":checks")) -} diff --git a/checks/.gitignore b/checks/.gitignore deleted file mode 100644 index 42afabf..0000000 --- a/checks/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/checks/build.gradle.kts b/checks/build.gradle.kts deleted file mode 100644 index 1ac0040..0000000 --- a/checks/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - java - alias(libs.plugins.kotlin) -} - -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 -} - -dependencies { - compileOnly(libs.lint.api) - compileOnly(libs.lint.checks) - compileOnly(libs.kotlin.stdlib) -} - diff --git a/checks/src/main/java/io/github/libxposed/lint/XposedIssueRegistry.kt b/checks/src/main/java/io/github/libxposed/lint/XposedIssueRegistry.kt deleted file mode 100644 index ec1d2b4..0000000 --- a/checks/src/main/java/io/github/libxposed/lint/XposedIssueRegistry.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.libxposed.lint - -import com.android.tools.lint.client.api.IssueRegistry -import com.android.tools.lint.detector.api.Issue - -class XposedIssueRegistry : IssueRegistry() { - override val issues: List - get() = emptyList() -} diff --git a/checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry deleted file mode 100644 index fed5aa4..0000000 --- a/checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry +++ /dev/null @@ -1 +0,0 @@ -io.github.libxposed.lint.XposedIssueRegistry diff --git a/settings.gradle.kts b/settings.gradle.kts index 23aa830..7e44cd3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,4 +16,4 @@ dependencyResolutionManagement { rootProject.name = "libxposed-api" -include(":api", ":checks") +include(":api") From 5a80c23ac9d26710a3037f8bb7fa41df8ced14b5 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 2 Mar 2026 09:15:49 +0100 Subject: [PATCH 42/53] RFC: Remove unused dependencies --- gradle/libs.versions.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7afe8af..5393904 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,11 @@ [versions] annotation = "1.9.1" kotlin = "2.3.10" -lint = "32.0.1" agp = "9.0.1" [plugins] -kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } agp-lib = { id = "com.android.library", version.ref = "agp" } [libraries] annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } -lint-api = { module = "com.android.tools.lint:lint-api", version.ref = "lint" } -lint-checks = { module = "com.android.tools.lint:lint-checks", version.ref = "lint" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } From 723a3ffd6d544887fa3391c872b3812d83c48bf8 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 2 Mar 2026 13:52:09 +0100 Subject: [PATCH 43/53] RFC: Use .error instead of .errors package --- api/src/main/java/io/github/libxposed/api/XposedInterface.java | 2 +- .../github/libxposed/api/{errors => error}/HookFailedError.java | 2 +- .../libxposed/api/{errors => error}/XposedFrameworkError.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename api/src/main/java/io/github/libxposed/api/{errors => error}/HookFailedError.java (95%) rename api/src/main/java/io/github/libxposed/api/{errors => error}/XposedFrameworkError.java (90%) 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 e68195b..1f36b31 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -14,7 +14,7 @@ import java.lang.reflect.Method; import java.util.List; -import io.github.libxposed.api.errors.HookFailedError; +import io.github.libxposed.api.error.HookFailedError; /** * Xposed interface for modules to operate on application processes. diff --git a/api/src/main/java/io/github/libxposed/api/errors/HookFailedError.java b/api/src/main/java/io/github/libxposed/api/error/HookFailedError.java similarity index 95% rename from api/src/main/java/io/github/libxposed/api/errors/HookFailedError.java rename to api/src/main/java/io/github/libxposed/api/error/HookFailedError.java index 9224eaf..5e049ce 100644 --- a/api/src/main/java/io/github/libxposed/api/errors/HookFailedError.java +++ b/api/src/main/java/io/github/libxposed/api/error/HookFailedError.java @@ -1,4 +1,4 @@ -package io.github.libxposed.api.errors; +package io.github.libxposed.api.error; /** * Thrown to indicate that a hook failed due to framework internal error. diff --git a/api/src/main/java/io/github/libxposed/api/errors/XposedFrameworkError.java b/api/src/main/java/io/github/libxposed/api/error/XposedFrameworkError.java similarity index 90% rename from api/src/main/java/io/github/libxposed/api/errors/XposedFrameworkError.java rename to api/src/main/java/io/github/libxposed/api/error/XposedFrameworkError.java index 0b3bba0..39651e1 100644 --- a/api/src/main/java/io/github/libxposed/api/errors/XposedFrameworkError.java +++ b/api/src/main/java/io/github/libxposed/api/error/XposedFrameworkError.java @@ -1,4 +1,4 @@ -package io.github.libxposed.api.errors; +package io.github.libxposed.api.error; /** * Thrown to indicate that the Xposed framework function is broken. From efa8a26b9d77d46a9971173a57ca53038ecb67a7 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Wed, 4 Mar 2026 00:32:56 +0100 Subject: [PATCH 44/53] RFC: Add LIB_API --- .../java/io/github/libxposed/api/XposedInterface.java | 8 +++++++- 1 file changed, 7 insertions(+), 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 1f36b31..941f838 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -21,6 +21,12 @@ */ @SuppressWarnings("unused") public interface XposedInterface { + /** + * The API version of this library. This is a static value for the framework. + * Modules should use {@link #getApiVersion()} to check the API version at runtime. + */ + int LIB_API = 101; + /** * The framework has the capability to hook system_server and other system processes. */ @@ -454,7 +460,7 @@ interface CtorHookBuilder extends HookBuilder, Constructor * @return API version */ default int getApiVersion() { - return 101; + return LIB_API; } /** From 036d9d0ac5193c2d1a353cfc3b883a52f38f9b1b Mon Sep 17 00:00:00 2001 From: Nullptr Date: Wed, 4 Mar 2026 07:23:51 +0100 Subject: [PATCH 45/53] RFC: Rename to getFrameworkProperties --- .../github/libxposed/api/XposedInterface.java | 24 ++++++------------- .../libxposed/api/XposedInterfaceWrapper.java | 4 ++-- 2 files changed, 9 insertions(+), 19 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 941f838..72d085c 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -30,15 +30,15 @@ public interface XposedInterface { /** * The framework has the capability to hook system_server and other system processes. */ - long CAP_SYSTEM = 1L; + long PROP_CAP_SYSTEM = 1L; /** * The framework provides remote preferences and remote files support. */ - long CAP_REMOTE = 1L << 1; + long PROP_CAP_REMOTE = 1L << 1; /** - * The framework allows dynamically loaded code to use Xposed APIs. + * The framework disallows accessing Xposed API via reflection or dynamically loaded code. */ - long CAP_RT_DYNAMIC_CODE_API_ACCESS = 1L << 2; + long PROP_RT_API_PROTECTION = 1L << 2; /** * The default hook priority. @@ -456,8 +456,6 @@ interface CtorHookBuilder extends HookBuilder, Constructor /** * Gets the runtime Xposed API version. Framework implementations must not override this method. - * - * @return API version */ default int getApiVersion() { return LIB_API; @@ -465,34 +463,26 @@ default int getApiVersion() { /** * Gets the Xposed framework name of current implementation. - * - * @return Framework name */ @NonNull String getFrameworkName(); /** * Gets the Xposed framework version of current implementation. - * - * @return Framework version */ @NonNull String getFrameworkVersion(); /** * Gets the Xposed framework version code of current implementation. - * - * @return Framework version code */ long getFrameworkVersionCode(); /** - * Gets the Xposed framework capabilities. - * Capabilities with prefix CAP_RT_ may change among launches. - * - * @return Framework capabilities + * Gets the Xposed framework properties. + * Properties with prefix PROP_RT_ may change among launches. */ - long getFrameworkCapabilities(); + long getFrameworkProperties(); /** * Hook a method. 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 f410783..f97f30f 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -65,9 +65,9 @@ public final long getFrameworkVersionCode() { } @Override - public final long getFrameworkCapabilities() { + public final long getFrameworkProperties() { ensureAttached(); - return mBase.getFrameworkCapabilities(); + return mBase.getFrameworkProperties(); } @NonNull From 29a87018a0eacac20b1031c60bdc003eda0b9f42 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Wed, 4 Mar 2026 10:24:19 +0100 Subject: [PATCH 46/53] RFC: Don't use variable length argument for chain which has annoying edge condition with null --- .../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 72d085c..76db7ab 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -242,7 +242,7 @@ interface MethodChain extends Chain { * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable - Object proceed(Object... args) throws Throwable; + Object proceed(@NonNull Object[] args) throws Throwable; /** * Proceeds to the next interceptor in the chain with the same arguments and given {@code this} pointer. @@ -267,7 +267,7 @@ interface MethodChain extends Chain { * @throws Throwable if any interceptor or the original method throws an exception */ @Nullable - Object proceedWith(@NonNull Object thisObject, Object... args) throws Throwable; + Object proceedWith(@NonNull Object thisObject, @NonNull Object[] args) throws Throwable; } /** @@ -294,7 +294,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 proceed(Object... args) throws Throwable; + void proceed(@NonNull Object[] args) throws Throwable; /** * Proceeds to the next interceptor in the chain with the same arguments and given {@code this} pointer. @@ -311,7 +311,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 T thisObject, Object... args) throws Throwable; + void proceedWith(@NonNull T thisObject, @NonNull Object[] args) throws Throwable; } /** From e52dc743e867bcdac902ebda4dee83b8cb035b48 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Thu, 5 Mar 2026 11:39:44 +0100 Subject: [PATCH 47/53] RFC: Simplify interface --- .../github/libxposed/api/XposedInterface.java | 262 ++++-------------- .../libxposed/api/XposedInterfaceWrapper.java | 13 +- 2 files changed, 54 insertions(+), 221 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 76db7ab..b98114d 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -55,8 +55,6 @@ public interface XposedInterface { /** * Invoker for a method or constructor. - * - * @param {@link Method} or {@link Constructor} */ interface Invoker, U extends Executable> { /** @@ -92,34 +90,33 @@ record Chain(int maxPriority) implements Type { * 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 { /** - * Invokes the method through the hook chain determined by the invoker's type. + * Invokes the method (or the constructor as a 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 * @return The result returned from the invoked method + *

For void methods and constructors, always returns {@code null}.

* @see Method#invoke(Object, Object...) */ @Nullable - Object invoke(@Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + Object invoke(Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** - * 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. + * Invokes the special (non-virtual) method (or the constructor as a 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 + *

For void methods and constructors, always returns {@code null}.

* @see Method#invoke(Object, Object...) */ @Nullable @@ -132,16 +129,6 @@ interface MethodInvoker extends Invoker { * @param The type of the constructor */ 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. - * - * @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; - /** * Creates a new instance through the hook chain determined by the invoker's type. * @@ -152,20 +139,6 @@ interface CtorInvoker extends Invoker, Constructor> { @NonNull T newInstance(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 thisObject The instance to be constructed - * @param args The arguments used for the construction - * @see Constructor#newInstance(Object...) - */ - void invokeSpecial(@NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; - /** * 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 @@ -211,162 +184,77 @@ interface Chain { */ @Nullable U getArg(int index) throws IndexOutOfBoundsException, ClassCastException; - } - /** - * Interceptor chain for a method. - */ - interface MethodChain extends Chain { /** - * Gets the {@code this} pointer for the method call, or {@code null} for static calls. + * Gets the {@code this} pointer for the call, or {@code null} for static methods. */ - @Nullable Object getThisObject(); /** * 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. For void methods, always returns {@code null}. - * @throws Throwable if any interceptor or the original method throws an exception + * @return The result returned from next interceptor or the original executable if current + * interceptor is the last one in the chain. + *

For void methods and constructors, always returns {@code null}.

+ * @throws Throwable if any interceptor or the original executable throws an exception */ - @Nullable Object proceed() throws Throwable; /** * 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. For void methods, always returns {@code null}. - * @throws Throwable if any interceptor or the original method throws an exception + * @param args The arguments used for the call + * @return The result returned from next interceptor or the original executable if current + * interceptor is the last one in the chain. + *

For void methods and constructors, always returns {@code null}.

+ * @throws Throwable if any interceptor or the original executable throws an exception */ - @Nullable Object proceed(@NonNull 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 - * @return The result returned from next interceptor or the original method if current - * 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 + * @param thisObject The {@code this} pointer for the call + * @return The result returned from next interceptor or the original executable if current + * interceptor is the last one in the chain. + *

For void methods and constructors, always returns {@code null}.

+ * @throws Throwable if any interceptor or the original executable 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 - * @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. For void methods, always returns {@code null}. - * @throws Throwable if any interceptor or the original method throws an exception + * @param thisObject The {@code this} pointer for the call + * @param args The arguments used for the call + * @return The result returned from next interceptor or the original executable if current + * interceptor is the last one in the chain. + *

For void methods and constructors, always returns {@code null}.

+ * @throws Throwable if any interceptor or the original executable throws an exception */ - @Nullable Object proceedWith(@NonNull Object thisObject, @NonNull Object[] args) throws Throwable; } - /** - * Interceptor chain for a constructor. - */ - interface CtorChain extends Chain> { - /** - * Gets the instance being constructed. Note that the instance may be not fully initialized when - * the chain is called. - */ - @NonNull - T getThisObject(); - - /** - * Proceeds to the next interceptor in the chain with the same arguments and {@code this} pointer. - * - * @throws Throwable if any interceptor or the original constructor throws an exception - */ - void proceed() throws Throwable; - - /** - * 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 construction - * @throws Throwable if any interceptor or the original constructor throws an exception - */ - void proceed(@NonNull 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 T thisObject) throws Throwable; - - /** - * Proceeds to the next interceptor in the chain with the given arguments and {@code this} pointer. - * - * @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 - */ - void proceedWith(@NonNull T thisObject, @NonNull Object[] args) throws Throwable; - } - /** * Hooker for a method or constructor. * * @param {@link Method} or {@link Constructor} */ interface Hooker { - } - - /** - * Hooker for a method. - */ - interface MethodHooker extends Hooker { /** - * Intercepts a method call. + * Intercepts a method / constructor call. * - * @param chain The interceptor chain for the method call + * @param chain The interceptor chain for the 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.

+ *

For void methods and constructors, 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. */ @Nullable - 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. - */ - interface CtorHooker extends Hooker> { - /** - * Intercepts a constructor call. - * - * @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. - */ - void intercept(@NonNull CtorChain chain) throws Throwable; + Object intercept(@NonNull Chain chain) throws Throwable; } /** @@ -390,10 +278,9 @@ interface HookHandle { /** * Builder for configuring a hook. * - * @param The concrete builder type for chaining - * @param {@link Method} or {@link Constructor} + * @param {@link Method} or {@link Constructor} */ - interface HookBuilder, U extends Executable> { + interface HookBuilder { /** * 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}. @@ -401,48 +288,10 @@ interface HookBuilder, U extends Executable> { * @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); + HookBuilder setPriority(int priority); /** - * 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. + * Sets the hooker for the method / constructor and builds the hook. * * @param hooker The hooker object * @return The handle for the hook @@ -451,7 +300,7 @@ interface CtorHookBuilder extends HookBuilder, Constructor * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle> intercept(@NonNull CtorHooker hooker); + HookHandle intercept(@NonNull Hooker hooker); } /** @@ -480,39 +329,30 @@ default int getApiVersion() { /** * Gets the Xposed framework properties. - * Properties with prefix PROP_RT_ may change among launches. + * Properties with prefix {@code PROP_RT_} may change among launches. */ long getFrameworkProperties(); /** - * Hook a method. - * - * @param origin The method to be hooked - * @return The builder for the hook - */ - @NonNull - MethodHookBuilder hook(@NonNull Method origin); - - /** - * Hook a constructor. + * Hook a method / constructor. * - * @param origin The constructor to be hooked + * @param origin The executable to be hooked * @return The builder for the hook */ @NonNull - CtorHookBuilder hook(@NonNull Constructor origin); + HookBuilder hook(@NonNull T origin); /** * Hook the static initializer ({@code }) of a class. * *

The static initializer is treated as a regular {@code static void()} method with no parameters. - * Accordingly, in the {@link MethodChain} passed to the hooker:

+ * Accordingly, in the {@link Chain} passed to the hooker:

*
    - *
  • {@link MethodChain#getExecutable()} returns a synthetic {@link Method} representing + *
  • {@link Chain#getExecutable()} returns a synthetic {@link Method} representing * the static initializer.
  • - *
  • {@link MethodChain#getThisObject()} always returns {@code null}.
  • - *
  • {@link MethodChain#getArgs()} returns an empty list.
  • - *
  • {@link MethodChain#proceed()} returns {@code null}.
  • + *
  • {@link Chain#getThisObject()} always returns {@code null}.
  • + *
  • {@link Chain#getArgs()} returns an empty list.
  • + *
  • {@link Chain#proceed()} returns {@code null}.
  • *
* *

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

@@ -521,7 +361,7 @@ default int getApiVersion() { * @return The builder for the hook */ @NonNull - MethodHookBuilder hookClassInitializer(@NonNull Class origin); + HookBuilder hookClassInitializer(@NonNull Class origin); /** * Deoptimizes a method / constructor in case hooked callee is not called because of inline. @@ -549,7 +389,7 @@ default int getApiVersion() { * @return The method invoker */ @NonNull - MethodInvoker getInvoker(@NonNull Method method); + Invoker getInvoker(@NonNull Method method); /** * Get a constructor invoker for the given constructor. The default type of the invoker is 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 f97f30f..c0b5012 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -72,21 +72,14 @@ public final long getFrameworkProperties() { @NonNull @Override - public final MethodHookBuilder hook(@NonNull Method origin) { + public final HookBuilder hook(@NonNull T origin) { ensureAttached(); return mBase.hook(origin); } @NonNull @Override - public final CtorHookBuilder hook(@NonNull Constructor origin) { - ensureAttached(); - return mBase.hook(origin); - } - - @NonNull - @Override - public final MethodHookBuilder hookClassInitializer(@NonNull Class origin) { + public final HookBuilder hookClassInitializer(@NonNull Class origin) { ensureAttached(); return mBase.hookClassInitializer(origin); } @@ -99,7 +92,7 @@ public final boolean deoptimize(@NonNull Executable executable) { @NonNull @Override - public final MethodInvoker getInvoker(@NonNull Method method) { + public final Invoker getInvoker(@NonNull Method method) { ensureAttached(); return mBase.getInvoker(method); } From 585f2816c55e0908a721595329dc23b38d709700 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 6 Mar 2026 16:42:15 +0100 Subject: [PATCH 48/53] RFC: Remove generics --- .../github/libxposed/api/XposedInterface.java | 28 ++++++++----------- .../libxposed/api/XposedInterfaceWrapper.java | 4 +-- 2 files changed, 13 insertions(+), 19 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 b98114d..df5ec2b 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -159,12 +159,12 @@ interface CtorInvoker extends Invoker, Constructor> { /** * Interceptor chain for a method or constructor. */ - interface Chain { + interface Chain { /** * Gets the method / constructor being hooked. */ @NonNull - T getExecutable(); + Executable getExecutable(); /** * Gets the arguments. The returned list is immutable. If you want to change the arguments, you @@ -239,10 +239,8 @@ interface Chain { /** * Hooker for a method or constructor. - * - * @param {@link Method} or {@link Constructor} */ - interface Hooker { + interface Hooker { /** * Intercepts a method / constructor call. * @@ -254,20 +252,18 @@ interface Hooker { * propagate to the caller if not caught by any interceptor. */ @Nullable - Object intercept(@NonNull Chain chain) throws Throwable; + Object intercept(@NonNull Chain chain) throws Throwable; } /** * Handle for a hook. - * - * @param {@link Method} or {@link Constructor} */ - interface HookHandle { + interface HookHandle { /** * Gets the method / constructor being hooked. */ @NonNull - T getExecutable(); + Executable getExecutable(); /** * Cancels the hook. This method is idempotent. It is safe to call this method multiple times. @@ -277,10 +273,8 @@ interface HookHandle { /** * Builder for configuring a hook. - * - * @param {@link Method} or {@link Constructor} */ - interface HookBuilder { + interface HookBuilder { /** * 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}. @@ -288,7 +282,7 @@ interface HookBuilder { * @param priority The priority of the hook * @return The builder itself for chaining */ - HookBuilder setPriority(int priority); + HookBuilder setPriority(int priority); /** * Sets the hooker for the method / constructor and builds the hook. @@ -300,7 +294,7 @@ interface HookBuilder { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle intercept(@NonNull Hooker hooker); + HookHandle intercept(@NonNull Hooker hooker); } /** @@ -340,7 +334,7 @@ default int getApiVersion() { * @return The builder for the hook */ @NonNull - HookBuilder hook(@NonNull T origin); + HookBuilder hook(@NonNull Executable origin); /** * Hook the static initializer ({@code }) of a class. @@ -361,7 +355,7 @@ default int getApiVersion() { * @return The builder for the hook */ @NonNull - HookBuilder hookClassInitializer(@NonNull Class origin); + HookBuilder hookClassInitializer(@NonNull Class origin); /** * 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 c0b5012..d3154d9 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -72,14 +72,14 @@ public final long getFrameworkProperties() { @NonNull @Override - public final HookBuilder hook(@NonNull T origin) { + public final HookBuilder hook(@NonNull Executable origin) { ensureAttached(); return mBase.hook(origin); } @NonNull @Override - public final HookBuilder hookClassInitializer(@NonNull Class origin) { + public final HookBuilder hookClassInitializer(@NonNull Class origin) { ensureAttached(); return mBase.hookClassInitializer(origin); } From 61bd590903ae3f65348e0c83ba8704fdaa5c7867 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 6 Mar 2026 16:55:14 +0100 Subject: [PATCH 49/53] Revert "RFC: Remove generics" This reverts commit 585f2816c55e0908a721595329dc23b38d709700. --- .../github/libxposed/api/XposedInterface.java | 28 +++++++++++-------- .../libxposed/api/XposedInterfaceWrapper.java | 4 +-- 2 files changed, 19 insertions(+), 13 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 df5ec2b..b98114d 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -159,12 +159,12 @@ interface CtorInvoker extends Invoker, Constructor> { /** * Interceptor chain for a method or constructor. */ - interface Chain { + interface Chain { /** * Gets the method / constructor being hooked. */ @NonNull - Executable getExecutable(); + T getExecutable(); /** * Gets the arguments. The returned list is immutable. If you want to change the arguments, you @@ -239,8 +239,10 @@ interface Chain { /** * Hooker for a method or constructor. + * + * @param {@link Method} or {@link Constructor} */ - interface Hooker { + interface Hooker { /** * Intercepts a method / constructor call. * @@ -252,18 +254,20 @@ interface Hooker { * propagate to the caller if not caught by any interceptor. */ @Nullable - Object intercept(@NonNull Chain chain) throws Throwable; + Object intercept(@NonNull Chain chain) throws Throwable; } /** * Handle for a hook. + * + * @param {@link Method} or {@link Constructor} */ - interface HookHandle { + interface HookHandle { /** * Gets the method / constructor being hooked. */ @NonNull - Executable getExecutable(); + T getExecutable(); /** * Cancels the hook. This method is idempotent. It is safe to call this method multiple times. @@ -273,8 +277,10 @@ interface HookHandle { /** * Builder for configuring a hook. + * + * @param {@link Method} or {@link Constructor} */ - interface HookBuilder { + interface HookBuilder { /** * 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}. @@ -282,7 +288,7 @@ interface HookBuilder { * @param priority The priority of the hook * @return The builder itself for chaining */ - HookBuilder setPriority(int priority); + HookBuilder setPriority(int priority); /** * Sets the hooker for the method / constructor and builds the hook. @@ -294,7 +300,7 @@ interface HookBuilder { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle intercept(@NonNull Hooker hooker); + HookHandle intercept(@NonNull Hooker hooker); } /** @@ -334,7 +340,7 @@ default int getApiVersion() { * @return The builder for the hook */ @NonNull - HookBuilder hook(@NonNull Executable origin); + HookBuilder hook(@NonNull T origin); /** * Hook the static initializer ({@code }) of a class. @@ -355,7 +361,7 @@ default int getApiVersion() { * @return The builder for the hook */ @NonNull - HookBuilder hookClassInitializer(@NonNull Class origin); + HookBuilder hookClassInitializer(@NonNull Class origin); /** * 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 d3154d9..c0b5012 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -72,14 +72,14 @@ public final long getFrameworkProperties() { @NonNull @Override - public final HookBuilder hook(@NonNull Executable origin) { + public final HookBuilder hook(@NonNull T origin) { ensureAttached(); return mBase.hook(origin); } @NonNull @Override - public final HookBuilder hookClassInitializer(@NonNull Class origin) { + public final HookBuilder hookClassInitializer(@NonNull Class origin) { ensureAttached(); return mBase.hookClassInitializer(origin); } From 75d36b45cf968f35fa52e718725fd99419229108 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Fri, 6 Mar 2026 16:57:45 +0100 Subject: [PATCH 50/53] RFC: Support general hooker --- 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 b98114d..c36a7da 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -300,7 +300,7 @@ interface HookBuilder { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle intercept(@NonNull Hooker hooker); + HookHandle intercept(@NonNull Hooker hooker); } /** From 13b4dd466a98dfc840c2377b2a52e4019472e28d Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 9 Mar 2026 07:14:45 +0100 Subject: [PATCH 51/53] RFC: Remove generics again --- .../github/libxposed/api/XposedInterface.java | 28 ++++++++----------- .../libxposed/api/XposedInterfaceWrapper.java | 4 +-- 2 files changed, 13 insertions(+), 19 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 c36a7da..df5ec2b 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -159,12 +159,12 @@ interface CtorInvoker extends Invoker, Constructor> { /** * Interceptor chain for a method or constructor. */ - interface Chain { + interface Chain { /** * Gets the method / constructor being hooked. */ @NonNull - T getExecutable(); + Executable getExecutable(); /** * Gets the arguments. The returned list is immutable. If you want to change the arguments, you @@ -239,10 +239,8 @@ interface Chain { /** * Hooker for a method or constructor. - * - * @param {@link Method} or {@link Constructor} */ - interface Hooker { + interface Hooker { /** * Intercepts a method / constructor call. * @@ -254,20 +252,18 @@ interface Hooker { * propagate to the caller if not caught by any interceptor. */ @Nullable - Object intercept(@NonNull Chain chain) throws Throwable; + Object intercept(@NonNull Chain chain) throws Throwable; } /** * Handle for a hook. - * - * @param {@link Method} or {@link Constructor} */ - interface HookHandle { + interface HookHandle { /** * Gets the method / constructor being hooked. */ @NonNull - T getExecutable(); + Executable getExecutable(); /** * Cancels the hook. This method is idempotent. It is safe to call this method multiple times. @@ -277,10 +273,8 @@ interface HookHandle { /** * Builder for configuring a hook. - * - * @param {@link Method} or {@link Constructor} */ - interface HookBuilder { + interface HookBuilder { /** * 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}. @@ -288,7 +282,7 @@ interface HookBuilder { * @param priority The priority of the hook * @return The builder itself for chaining */ - HookBuilder setPriority(int priority); + HookBuilder setPriority(int priority); /** * Sets the hooker for the method / constructor and builds the hook. @@ -300,7 +294,7 @@ interface HookBuilder { * @throws HookFailedError if hook fails due to framework internal error */ @NonNull - HookHandle intercept(@NonNull Hooker hooker); + HookHandle intercept(@NonNull Hooker hooker); } /** @@ -340,7 +334,7 @@ default int getApiVersion() { * @return The builder for the hook */ @NonNull - HookBuilder hook(@NonNull T origin); + HookBuilder hook(@NonNull Executable origin); /** * Hook the static initializer ({@code }) of a class. @@ -361,7 +355,7 @@ default int getApiVersion() { * @return The builder for the hook */ @NonNull - HookBuilder hookClassInitializer(@NonNull Class origin); + HookBuilder hookClassInitializer(@NonNull Class origin); /** * 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 c0b5012..d3154d9 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -72,14 +72,14 @@ public final long getFrameworkProperties() { @NonNull @Override - public final HookBuilder hook(@NonNull T origin) { + public final HookBuilder hook(@NonNull Executable origin) { ensureAttached(); return mBase.hook(origin); } @NonNull @Override - public final HookBuilder hookClassInitializer(@NonNull Class origin) { + public final HookBuilder hookClassInitializer(@NonNull Class origin) { ensureAttached(); return mBase.hookClassInitializer(origin); } From 57f8c07d258056734fcc0f07225aa04a9b0b9881 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 9 Mar 2026 09:27:19 +0100 Subject: [PATCH 52/53] RFC: Remove getArg generics because we don't want implicit cast --- .../io/github/libxposed/api/XposedInterface.java | 16 ++++++---------- .../libxposed/api/XposedModuleInterface.java | 14 +++++++------- 2 files changed, 13 insertions(+), 17 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 df5ec2b..ba87ed6 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -101,7 +101,6 @@ record Chain(int maxPriority) implements Type { *

For void methods and constructors, always returns {@code null}.

* @see Method#invoke(Object, Object...) */ - @Nullable Object invoke(Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; /** @@ -119,7 +118,6 @@ record Chain(int maxPriority) implements Type { *

For void methods and constructors, always returns {@code null}.

* @see Method#invoke(Object, Object...) */ - @Nullable Object invokeSpecial(@NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; } @@ -166,6 +164,11 @@ interface Chain { @NonNull Executable getExecutable(); + /** + * Gets the {@code this} pointer for the call, or {@code null} for static methods. + */ + Object getThisObject(); + /** * 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 @@ -182,13 +185,7 @@ interface Chain { * @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; - - /** - * Gets the {@code this} pointer for the call, or {@code null} for static methods. - */ - Object getThisObject(); + Object getArg(int index) throws IndexOutOfBoundsException, ClassCastException; /** * Proceeds to the next interceptor in the chain with the same arguments and {@code this} pointer. @@ -251,7 +248,6 @@ interface Hooker { * @throws Throwable Throw any exception from the interceptor. The exception will * propagate to the caller if not caught by any interceptor. */ - @Nullable Object intercept(@NonNull Chain chain) throws Throwable; } 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 6c940d2..cea5bbe 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java @@ -52,6 +52,13 @@ interface PackageLoadedParam { @NonNull ApplicationInfo getApplicationInfo(); + /** + * Returns whether this is the first and main package loaded in the app process. + * + * @return {@code true} if this is the first package. + */ + boolean isFirstPackage(); + /** * Gets the default classloader of the current package. This is the classloader that loads * the app's code, resources and custom {@link AppComponentFactory}. @@ -59,13 +66,6 @@ interface PackageLoadedParam { @RequiresApi(Build.VERSION_CODES.Q) @NonNull ClassLoader getDefaultClassLoader(); - - /** - * Returns whether this is the first and main package loaded in the app process. - * - * @return {@code true} if this is the first package. - */ - boolean isFirstPackage(); } /** From ccbde1b7dd35ad061f4a0f211a3b816580d5230f Mon Sep 17 00:00:00 2001 From: Nullptr Date: Mon, 9 Mar 2026 09:32:44 +0100 Subject: [PATCH 53/53] RFC: Rename XposedInterface.getApplicationInfo to getModuleApplicationInfo --- .../main/java/io/github/libxposed/api/XposedInterface.java | 2 +- .../java/io/github/libxposed/api/XposedInterfaceWrapper.java | 4 ++-- 2 files 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 ba87ed6..74238fa 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -415,7 +415,7 @@ default int getApiVersion() { * Gets the application info of the module. */ @NonNull - ApplicationInfo getApplicationInfo(); + ApplicationInfo getModuleApplicationInfo(); /** * Gets remote preferences stored in Xposed framework. Note that those are read-only in hooked apps. 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 d3154d9..20b10a8 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -125,9 +125,9 @@ public final SharedPreferences getRemotePreferences(@NonNull String name) { @NonNull @Override - public final ApplicationInfo getApplicationInfo() { + public final ApplicationInfo getModuleApplicationInfo() { ensureAttached(); - return mBase.getApplicationInfo(); + return mBase.getModuleApplicationInfo(); } @NonNull