diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 059e414..17d46d6 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -7,11 +7,11 @@ plugins { android { namespace = "io.github.libxposed.api" compileSdk = 36 - buildToolsVersion = "35.0.0" + buildToolsVersion = "36.1.0" androidResources.enable = false defaultConfig { - minSdk = 24 + minSdk = 26 consumerProguardFiles("proguard-rules.pro") } @@ -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 { @@ -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 431eadd..dc830c8 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterface.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterface.java @@ -10,10 +10,11 @@ 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; +import java.util.List; import io.github.libxposed.api.errors.HookFailedError; import io.github.libxposed.api.utils.DexParser; @@ -24,210 +25,439 @@ @SuppressWarnings("unused") public interface XposedInterface { /** - * SDK API version. + * The framework has the capability to hook system_server and other system processes. */ - int API = 100; - - /** - * Indicates that the framework is running as root. - */ - int FRAMEWORK_PRIVILEGE_ROOT = 0; + long CAP_SYSTEM = 1L; /** - * 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 = 1L << 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_DYNAMIC_CODE_API_ACCESS = 1L << 2; /** * The default hook priority. */ int PRIORITY_DEFAULT = 50; /** - * Execute the hook callback late. + * Execute at the end of the interception chain. */ - int PRIORITY_LOWEST = -10000; + int PRIORITY_LOWEST = Integer.MIN_VALUE; /** - * Execute the hook callback early. + * Execute at the beginning of the interception chain. */ - int PRIORITY_HIGHEST = 10000; + int PRIORITY_HIGHEST = Integer.MAX_VALUE; /** - * Contextual interface for before invocation callbacks. + * Invoker for a method or constructor. + * + * @param {@link Method} or {@link Constructor} */ - interface BeforeHookCallback { + interface Invoker, U extends Executable> { /** - * Gets the method / constructor to be hooked. + * Type of the invoker, which determines the hook chain to be invoked */ - @NonNull - Member getMember(); + sealed interface Type permits Type.Origin, Type.Chain { + /** + * A convenience constant for {@link Origin}. + */ + Origin ORIGIN = new Origin(); + + /** + * Invokes the original executable, skipping all hooks. + */ + record Origin() implements Type { + } + + /** + * Invokes the executable starting from the middle of the hook chain, skipping all + * hooks with priority higher than the given value. + * + * @param maxPriority The maximum priority of hooks to include in the chain + */ + record Chain(int maxPriority) implements Type { + /** + * Invoking the executable with full hook chain. + */ + public static final Chain FULL = new Chain(PRIORITY_HIGHEST); + } + } /** - * Gets the {@code this} object, or {@code null} if the method is static. + * 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. + * + * @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 getThisObject(); + Object invoke(@Nullable 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. + * + *

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 invokeSpecial(@NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; + } + + /** + * Invoker for a constructor. + * + * @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; /** - * Gets the arguments passed to the method / constructor. You can modify the arguments. + * 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 + * @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 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. * - * @param throwable The exception to be thrown + *

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

+ * + * @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 to be hooked. + * Gets the method / constructor being hooked. */ @NonNull - Member getMember(); + T getExecutable(); /** - * Gets the {@code this} object, or {@code null} if the method is static. + * 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 + 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; + } + + /** + * 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. For void methods, always returns {@code null}. + * @throws Throwable if any interceptor or the original method throws an exception */ - @NonNull - Object[] getArgs(); + @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 + */ + @Nullable + Object proceed(Object... args) 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 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 */ @Nullable - Object getResult(); + Object proceedWith(@NonNull Object thisObject) 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. + * 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 */ @Nullable - Throwable getThrowable(); + Object proceedWith(@NonNull 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 + T 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; + + /** + * 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. + * + * @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 Object thisObject, Object... args) throws Throwable; } /** - * 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. - *

- * - *

Example usage:

- * - *
{@code
-     *   public class ExampleHooker implements Hooker {
-     *
-     *       public static void before(@NonNull BeforeHookCallback callback) {
-     *           // Pre-hooking logic goes here
-     *       }
-     *
-     *       public static void after(@NonNull AfterHookCallback callback) {
-     *           // Post-hooking logic goes here
-     *       }
-     *   }
-     *
-     *   public class ExampleHookerWithContext implements Hooker {
+     * Hooker for a method or constructor.
      *
-     *       public static MyContext before(@NonNull BeforeHookCallback callback) {
-     *           // Pre-hooking logic goes here
-     *           return new MyContext();
-     *       }
-     *
-     *       public static void after(@NonNull AfterHookCallback callback, MyContext context) {
-     *           // Post-hooking logic goes here
-     *       }
-     *   }
-     * }
+ * @param {@link Method} or {@link Constructor} + */ + interface Hooker { + } + + /** + * Hooker for a method. + */ + interface MethodHooker extends Hooker { + /** + * 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. + *

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. + */ + @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 Hooker { + 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; } /** - * Interface for canceling a hook. + * Handle for a hook. * * @param {@link Method} or {@link Constructor} */ - interface MethodUnhooker { + interface HookHandle { /** - * Gets the method or constructor being hooked. + * Gets the method / constructor being hooked. */ @NonNull - T getOrigin(); + T getExecutable(); /** - * Cancels the hook. The behavior of calling this method multiple times is undefined. + * Cancels the hook. This method is idempotent. It is safe to call this method multiple times. */ 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. + * + * @return API version + */ + int getApiVersion(); + /** * Gets the Xposed framework name of current implementation. * @@ -252,214 +482,90 @@ interface MethodUnhooker { long getFrameworkVersionCode(); /** - * Gets the Xposed framework privilege of current implementation. + * Gets the Xposed framework capabilities. + * Capabilities with prefix CAP_RT_ may change among launches. * - * @return Framework privilege + * @return Framework capabilities */ - int getFrameworkPrivilege(); + long getFrameworkCapabilities(); /** - * Hook a method with default priority. + * Hook a method. * * @param origin The method to be hooked - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @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 - MethodUnhooker hook(@NonNull Method origin, @NonNull Class hooker); + MethodHookBuilder hook(@NonNull Method origin); /** - * Hook the static initializer of a class with default priority. - *

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

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

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

* - * @param origin The class to be hooked - * @param priority The hook priority - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @throws IllegalArgumentException if class has no static initializer or hooker is invalid - * @throws HookFailedError if hook fails due to framework internal error - */ - @NonNull - MethodUnhooker> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker); - - /** - * Hook a method with specified priority. - * - * @param origin The method to be hooked - * @param priority The hook priority - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, - * or hooker is invalid - * @throws HookFailedError if hook fails due to framework internal error - */ - @NonNull - MethodUnhooker hook(@NonNull Method origin, int priority, @NonNull Class hooker); - - /** - * Hook a constructor with default priority. - * - * @param The type of the constructor - * @param origin The constructor to be hooked - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, - * or hooker is invalid - * @throws HookFailedError if hook fails due to framework internal error - */ - @NonNull - MethodUnhooker> hook(@NonNull Constructor origin, @NonNull Class hooker); - - /** - * Hook a constructor with specified priority. - * - * @param The type of the constructor - * @param origin The constructor to be hooked - * @param priority The hook priority - * @param hooker The hooker class - * @return Unhooker for canceling the hook - * @throws IllegalArgumentException if origin is abstract, framework internal or {@link Method#invoke}, - * or hooker is invalid - * @throws HookFailedError if hook fails due to framework internal error + * @param origin The class to be hooked + * @return The builder for the hook */ @NonNull - MethodUnhooker> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker); + MethodHookBuilder hookClassInitializer(@NonNull Class origin); /** - * 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. + *

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).

* - * @param method The method to deoptimize + * @param executable The method / constructor 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 - * @return Indicate whether the deoptimizing succeed or not - * @see #deoptimize(Method) - */ - boolean deoptimize(@NonNull Constructor constructor); - - /** - * Basically the same as {@link Method#invoke(Object, Object...)}, but calls the original method - * as it was before the interception by Xposed. - * - * @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; - - /** - * Basically the same as {@link Constructor#newInstance(Object...)}, but calls the original constructor - * as it was before the interception by Xposed. - * - * @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; + boolean deoptimize(@NonNull Executable executable); /** - * 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 method invoker for the given method. The default type of the invoker is + * {@link Invoker.Type.Chain#FULL}. * - *

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...) + * @param method The method to get the invoker for + * @return The method invoker */ - @Nullable - Object invokeSpecial(@NonNull Method method, @NonNull 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. - * - *

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; + @NonNull + MethodInvoker getInvoker(@NonNull Method method); /** - * Basically the same as {@link Constructor#newInstance(Object...)}, but calls the original constructor - * as it was before the interception by Xposed. + * 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 - * @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...) + * @return The constructor invoker */ @NonNull - T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; + CtorInvoker getInvoker(@NonNull Constructor constructor); /** - * 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.

+ * Writes a message to the Xposed log. * - * @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 priority The log priority, see {@link android.util.Log} + * @param tag The log tag + * @param msg The log message */ - @NonNull - U newInstanceSpecial(@NonNull Constructor constructor, @NonNull Class subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException; + void log(int priority, @Nullable String tag, @NonNull String msg); /** * Writes a message to the Xposed log. @@ -471,26 +577,6 @@ interface MethodUnhooker { */ void log(int priority, @Nullable String tag, @NonNull String msg, @Nullable Throwable tr); - /** - * Writes a message to the Xposed log. - * @deprecated Use {@link #log(int, String, String, Throwable)} instead. - * This method is kept for compatibility with old hooker classes and will be removed in first release version. - * - * @param message The log message - */ - @Deprecated - void log(@NonNull String message); - - /** - * Writes a message with a stack trace to the Xposed log. - * @deprecated Use {@link #log(int, String, String, Throwable)} instead. - * - * @param message The log message - * @param throwable The Throwable object for the stack trace - */ - @Deprecated - void log(@NonNull String message, @NonNull Throwable throwable); - /** * Parse a dex file in memory. * diff --git a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java index feb8cea..877c820 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -10,167 +10,155 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.nio.ByteBuffer; 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 { - private final XposedInterface mBase; - - XposedInterfaceWrapper(@NonNull XposedInterface base) { + private XposedInterface mBase; + + /** + * Attaches the framework interface to the module. Modules should never call this method. + * + * @param base The framework interface + */ + @SuppressWarnings("unused") + public final void attachFramework(@NonNull XposedInterface base) { + if (mBase != null) { + throw new IllegalStateException("Framework already attached"); + } mBase = base; } + private void ensureAttached() { + if (mBase == null) { + throw new IllegalStateException("Framework not attached"); + } + } + + @Override + public final int getApiVersion() { + ensureAttached(); + return mBase.getApiVersion(); + } + @NonNull @Override public final String getFrameworkName() { + ensureAttached(); return mBase.getFrameworkName(); } @NonNull @Override public final String getFrameworkVersion() { + ensureAttached(); return mBase.getFrameworkVersion(); } @Override public final long getFrameworkVersionCode() { + ensureAttached(); return mBase.getFrameworkVersionCode(); } @Override - public final int getFrameworkPrivilege() { - return mBase.getFrameworkPrivilege(); + public final long getFrameworkCapabilities() { + ensureAttached(); + return mBase.getFrameworkCapabilities(); } @NonNull @Override - public final MethodUnhooker hook(@NonNull Method origin, @NonNull Class hooker) { - return mBase.hook(origin, hooker); + public final MethodHookBuilder hook(@NonNull Method origin) { + ensureAttached(); + return mBase.hook(origin); } @NonNull @Override - public MethodUnhooker> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker) { - return mBase.hookClassInitializer(origin, hooker); + public final CtorHookBuilder hook(@NonNull Constructor origin) { + ensureAttached(); + return mBase.hook(origin); } @NonNull @Override - public MethodUnhooker> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { - return mBase.hookClassInitializer(origin, priority, hooker); + public final MethodHookBuilder hookClassInitializer(@NonNull Class origin) { + ensureAttached(); + return mBase.hookClassInitializer(origin); } - @NonNull @Override - public final MethodUnhooker hook(@NonNull Method origin, int priority, @NonNull Class hooker) { - return mBase.hook(origin, priority, hooker); + public final boolean deoptimize(@NonNull Executable executable) { + ensureAttached(); + return mBase.deoptimize(executable); } @NonNull @Override - public final MethodUnhooker> hook(@NonNull Constructor origin, @NonNull Class hooker) { - return mBase.hook(origin, hooker); - } - - @NonNull - @Override - public final MethodUnhooker> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { - return mBase.hook(origin, priority, hooker); - } - - @Override - public final boolean deoptimize(@NonNull Method method) { - return mBase.deoptimize(method); - } - - @Override - public final boolean deoptimize(@NonNull Constructor constructor) { - return mBase.deoptimize(constructor); - } - - @Nullable - @Override - public final Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { - return mBase.invokeOrigin(method, thisObject, args); - } - - @Override - public void invokeOrigin(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { - mBase.invokeOrigin(constructor, thisObject, args); - } - - @Nullable - @Override - public final Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { - return mBase.invokeSpecial(method, thisObject, args); - } - - @Override - public void invokeSpecial(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { - mBase.invokeSpecial(constructor, thisObject, args); + public final MethodInvoker getInvoker(@NonNull Method method) { + ensureAttached(); + return mBase.getInvoker(method); } @NonNull @Override - public final T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { - return mBase.newInstanceOrigin(constructor, args); + public final CtorInvoker getInvoker(@NonNull Constructor constructor) { + ensureAttached(); + return mBase.getInvoker(constructor); } - @NonNull @Override - public final U newInstanceSpecial(@NonNull Constructor constructor, @NonNull Class subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { - return mBase.newInstanceSpecial(constructor, subClass, args); + 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(); mBase.log(priority, tag, msg, tr); } - @Override - public final void log(@NonNull String message) { - mBase.log(message); - } - - @Override - public final void log(@NonNull String message, @NonNull Throwable throwable) { - mBase.log(message, throwable); - } - @Nullable @Override public final DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException { + ensureAttached(); return mBase.parseDex(dexData, includeAnnotations); } @NonNull @Override - public SharedPreferences getRemotePreferences(@NonNull String name) { + public final SharedPreferences getRemotePreferences(@NonNull String name) { + ensureAttached(); return mBase.getRemotePreferences(name); } @NonNull @Override - public ApplicationInfo getApplicationInfo() { + public final ApplicationInfo getApplicationInfo() { + ensureAttached(); return mBase.getApplicationInfo(); } @NonNull @Override - public String[] listRemoteFiles() { + public final String[] listRemoteFiles() { + ensureAttached(); return mBase.listRemoteFiles(); } @NonNull @Override - public ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException { + public final ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException { + ensureAttached(); return mBase.openRemoteFile(name); } } diff --git a/api/src/main/java/io/github/libxposed/api/XposedModule.java b/api/src/main/java/io/github/libxposed/api/XposedModule.java index b2e1a03..475a49c 100644 --- a/api/src/main/java/io/github/libxposed/api/XposedModule.java +++ b/api/src/main/java/io/github/libxposed/api/XposedModule.java @@ -1,21 +1,10 @@ package io.github.libxposed.api; -import androidx.annotation.NonNull; - /** * Super class which all Xposed module entry classes should extend.
- * Entry classes will be instantiated exactly once for each process. + * Entry classes will be instantiated exactly once for each process. Modules should not do initialization + * work before {@link #onModuleLoaded(ModuleLoadedParam)} is called. */ @SuppressWarnings("unused") public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface { - /** - * Instantiates a new Xposed module.
- * When the module is loaded into the target process, the constructor will be called. - * - * @param base The implementation interface provided by the framework, should not be used by the module - * @param param Information about the process in which the module is loaded - */ - public XposedModule(@NonNull XposedInterface base, @NonNull ModuleLoadedParam param) { - super(base); - } } diff --git a/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java b/api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java index 1cb548c..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; @@ -16,9 +17,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(); @@ -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,39 +66,67 @@ 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 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. + */ + boolean isFirstPackage(); + } + + interface PackageReadyParam extends PackageLoadedParam { + /** + * 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 information about whether is this package the first and main package of the app process. - * - * @return {@code true} if this is the first package. + * Gets the {@link AppComponentFactory} of the current package. */ - boolean isFirstPackage(); + @RequiresApi(Build.VERSION_CODES.P) + @NonNull + AppComponentFactory getAppComponentFactory(); } /** - * Gets notified when a package is loaded into the app process.
+ * Gets notified when the module is loaded into the target process.
+ * This callback is guaranteed to be called exactly once for a process. + * + * @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 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. *