Compare commits

...

10 Commits

Author SHA1 Message Date
WeiguangTWK 745afd390c API: Compatiablity for API 101 over API 100 2026-04-02 20:26:52 +08:00
Karma2424 b896dbcda3
fix build artifact (#38) 2024-11-04 13:32:35 +08:00
LoveSy ca2e4b8da8
Support hooking class static initializer 2024-08-23 00:41:26 +08:00
LoveSy 3248419435
Upgrade gradle 2024-08-16 23:18:44 +08:00
LoveSy 64e29bd657
Fix proguard rules 2024-07-23 00:59:29 +08:00
LoveSy 7b6727313c
Use name instead of annotation to indicate before/after callback 2024-07-22 22:27:23 +08:00
LoveSy 55efdf9d15
Add two methods for constructors 2024-07-22 21:53:57 +08:00
Howard Wu 5458273031
Fix docs path (#18) 2024-04-23 15:31:13 +08:00
Howard Wu 2f03a689cf
Upgrade `actions/upload-pages-artifact` (#17) 2024-04-23 15:19:50 +08:00
Howard Wu 02fd45cae8
Upgrade dependencies (#16)
for JDK 21
2024-04-23 14:06:53 +08:00
17 changed files with 582 additions and 96 deletions

View File

@ -19,14 +19,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0 fetch-depth: 0
- name: set up JDK 17 - name: set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '21'
distribution: 'temurin' distribution: 'temurin'
cache: gradle cache: gradle
@ -42,16 +42,18 @@ jobs:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }}
- name: Upload library - name: Upload library
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: library name: library
path: ~/.m2 path: ~/.m2/repository/
include-hidden-files: true
if-no-files-found: error
- name: Upload pages - name: Upload pages
uses: actions/upload-pages-artifact@v1 uses: actions/upload-pages-artifact@v3
with: with:
# Upload entire repository # Upload entire repository
path: 'api/build/intermediates/java_doc_dir/release' path: 'api/build/intermediates/java_doc_dir/release/javaDocReleaseGeneration'
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@main uses: actions/deploy-pages@v4

View File

@ -6,8 +6,8 @@ plugins {
android { android {
namespace = "io.github.libxposed.api" namespace = "io.github.libxposed.api"
compileSdk = 34 compileSdk = 35
buildToolsVersion = "34.0.0" buildToolsVersion = "35.0.0"
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 24
@ -20,8 +20,8 @@ android {
} }
compileOptions { compileOptions {
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_1_8
} }
publishing { publishing {
@ -92,6 +92,6 @@ signing {
dependencies { dependencies {
compileOnly("androidx.annotation:annotation:1.6.0") compileOnly(libs.annotation)
lintPublish(project(":checks")) lintPublish(project(":checks"))
} }

View File

@ -1 +1,8 @@
-keep class io.github.libxposed.** { *; } -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, ***);
}

View File

@ -10,14 +10,13 @@ import androidx.annotation.Nullable;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import io.github.libxposed.api.annotations.AfterInvocation;
import io.github.libxposed.api.annotations.BeforeInvocation;
import io.github.libxposed.api.annotations.XposedHooker;
import io.github.libxposed.api.errors.HookFailedError; import io.github.libxposed.api.errors.HookFailedError;
import io.github.libxposed.api.utils.DexParser; import io.github.libxposed.api.utils.DexParser;
@ -27,9 +26,28 @@ import io.github.libxposed.api.utils.DexParser;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public interface XposedInterface { public interface XposedInterface {
/** /**
* SDK API version. * The API version of this library.
*/ */
int API = 100; int LIB_API = 101;
/**
* @deprecated Use {@link #LIB_API}. Kept for API100 compatibility.
*/
@Deprecated
int API = LIB_API;
/**
* The framework has capability to hook system_server.
*/
long PROP_CAP_SYSTEM = 1L;
/**
* The framework provides remote preferences and remote files support.
*/
long PROP_CAP_REMOTE = 1L << 1;
/**
* The framework enables runtime Xposed API protection.
*/
long PROP_RT_API_PROTECTION = 1L << 2;
/** /**
* Indicates that the framework is running as root. * Indicates that the framework is running as root.
@ -166,9 +184,8 @@ public interface XposedInterface {
* like the old API. * like the old API.
* *
* <p> * <p>
* Classes implementing this interface should be annotated with {@link XposedHooker} and should provide * Classes implementing this interface should should provide two public static methods named
* two public static methods that are annotated with {@link BeforeInvocation} and {@link AfterInvocation}, * before and after for before invocation and after invocation respectively.
* respectively.
* </p> * </p>
* *
* <p> * <p>
@ -187,30 +204,24 @@ public interface XposedInterface {
* <p>Example usage:</p> * <p>Example usage:</p>
* *
* <pre>{@code * <pre>{@code
* @XposedHooker
* public class ExampleHooker implements Hooker { * public class ExampleHooker implements Hooker {
* *
* @BeforeInvocation
* public static void before(@NonNull BeforeHookCallback callback) { * public static void before(@NonNull BeforeHookCallback callback) {
* // Pre-hooking logic goes here * // Pre-hooking logic goes here
* } * }
* *
* @AfterInvocation
* public static void after(@NonNull AfterHookCallback callback) { * public static void after(@NonNull AfterHookCallback callback) {
* // Post-hooking logic goes here * // Post-hooking logic goes here
* } * }
* } * }
* *
* @XposedHooker
* public class ExampleHookerWithContext implements Hooker { * public class ExampleHookerWithContext implements Hooker {
* *
* @BeforeInvocation
* public static MyContext before(@NonNull BeforeHookCallback callback) { * public static MyContext before(@NonNull BeforeHookCallback callback) {
* // Pre-hooking logic goes here * // Pre-hooking logic goes here
* return new MyContext(); * return new MyContext();
* } * }
* *
* @AfterInvocation
* public static void after(@NonNull AfterHookCallback callback, MyContext context) { * public static void after(@NonNull AfterHookCallback callback, MyContext context) {
* // Post-hooking logic goes here * // Post-hooking logic goes here
* } * }
@ -218,6 +229,16 @@ public interface XposedInterface {
* }</pre> * }</pre>
*/ */
interface Hooker { interface Hooker {
@Nullable
default Object intercept(@NonNull Chain chain) throws Throwable {
return chain.proceed();
}
}
enum ExceptionMode {
DEFAULT,
PASSTHROUGH,
PROTECTIVE
} }
/** /**
@ -238,6 +259,221 @@ public interface XposedInterface {
void unhook(); void unhook();
} }
interface HookHandle {
@NonNull
Executable getExecutable();
void unhook();
}
/**
* Chain context for the API 101 style interceptor.
*/
interface Chain {
@NonNull
Executable getExecutable();
@Nullable
Object getThisObject();
@NonNull
Object[] getArgs();
@SuppressWarnings("unchecked")
@Nullable
default <T> T getArg(int index) {
return (T) getArgs()[index];
}
default void setArg(int index, @Nullable Object value) {
getArgs()[index] = value;
}
@Nullable
Object proceed() throws InvocationTargetException, IllegalArgumentException, IllegalAccessException;
}
/**
* API 101 interceptor.
*/
interface Interceptor {
@Nullable
Object intercept(@NonNull Chain chain) throws Throwable;
}
/**
* API 101 hook builder.
*/
interface HookBuilder {
@NonNull
default HookBuilder setPriority(int priority) {
return this;
}
@NonNull
default HookBuilder setExceptionMode(@NonNull ExceptionMode mode) {
return this;
}
@NonNull
HookHandle intercept(@NonNull Hooker hooker);
@NonNull
default HookHandle intercept(@NonNull Interceptor interceptor) {
return intercept(new Hooker() {
@SuppressWarnings("unused")
public Object intercept(@NonNull Chain chain) throws Throwable {
return interceptor.intercept(chain);
}
});
}
}
final class LegacyHookRegistry {
private LegacyHookRegistry() {
}
static final ConcurrentHashMap<Executable, HookEntry> ENTRIES = new ConcurrentHashMap<>();
}
final class HookEntry {
final XposedInterface base;
final Hooker hooker;
final Method interceptMethod;
final ExceptionMode exceptionMode;
HookEntry(@NonNull XposedInterface base, @NonNull Hooker hooker, @NonNull Method interceptMethod, @NonNull ExceptionMode exceptionMode) {
this.base = base;
this.hooker = hooker;
this.interceptMethod = interceptMethod;
this.exceptionMode = exceptionMode;
}
}
final class CompatHookBuilder implements HookBuilder {
private final XposedInterface base;
private final Executable origin;
private int priority = PRIORITY_DEFAULT;
private ExceptionMode exceptionMode = ExceptionMode.DEFAULT;
CompatHookBuilder(@NonNull XposedInterface base, @NonNull Executable origin) {
this.base = base;
this.origin = origin;
}
@NonNull
@Override
public HookBuilder setPriority(int priority) {
this.priority = priority;
return this;
}
@NonNull
@Override
public HookBuilder setExceptionMode(@NonNull ExceptionMode mode) {
this.exceptionMode = mode;
return this;
}
@NonNull
@Override
public HookHandle intercept(@NonNull Hooker hooker) {
final Method method = CompatHooker.findInterceptMethod(hooker.getClass());
LegacyHookRegistry.ENTRIES.put(origin, new HookEntry(base, hooker, method, exceptionMode));
final MethodUnhooker<? extends Executable> raw;
if (origin instanceof Method) {
raw = base.hook((Method) origin, priority, CompatHooker.class);
} else if (origin instanceof Constructor<?>) {
@SuppressWarnings({"rawtypes", "unchecked"})
MethodUnhooker<? extends Executable> cast = (MethodUnhooker) base.hook((Constructor) origin, priority, CompatHooker.class);
raw = cast;
} else {
LegacyHookRegistry.ENTRIES.remove(origin);
throw new IllegalArgumentException("Unsupported executable type: " + origin.getClass().getName());
}
return new HookHandle() {
@NonNull
@Override
public Executable getExecutable() {
return origin;
}
@Override
public void unhook() {
LegacyHookRegistry.ENTRIES.remove(origin);
raw.unhook();
}
};
}
}
final class CompatHooker implements Hooker {
private CompatHooker() {
}
static Method findInterceptMethod(@NonNull Class<?> cls) {
for (Method method : cls.getMethods()) {
if (!method.getName().equals("intercept") || method.getParameterCount() != 1) {
continue;
}
if (method.getParameterTypes()[0] == Chain.class) {
method.setAccessible(true);
return method;
}
}
throw new IllegalArgumentException("Hooker must declare public intercept(Chain): " + cls.getName());
}
public static void before(@NonNull BeforeHookCallback callback) {
var member = callback.getMember();
if (!(member instanceof Executable)) {
return;
}
var executable = (Executable) member;
var entry = LegacyHookRegistry.ENTRIES.get(executable);
if (entry == null) {
return;
}
var chain = new Chain() {
@NonNull
@Override
public Executable getExecutable() {
return executable;
}
@Nullable
@Override
public Object getThisObject() {
return callback.getThisObject();
}
@NonNull
@Override
public Object[] getArgs() {
return callback.getArgs();
}
@Nullable
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Object proceed() throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
if (executable instanceof Method method) {
return entry.base.invokeOrigin(method, callback.getThisObject(), callback.getArgs());
}
entry.base.invokeOrigin((Constructor) executable, callback.getThisObject(), callback.getArgs());
return callback.getThisObject();
}
};
try {
callback.returnAndSkip(entry.interceptMethod.invoke(entry.hooker, chain));
} catch (InvocationTargetException ite) {
callback.throwAndSkip(ite.getCause() != null ? ite.getCause() : ite);
} catch (Throwable t) {
callback.throwAndSkip(t);
}
}
}
/** /**
* Gets the Xposed framework name of current implementation. * Gets the Xposed framework name of current implementation.
* *
@ -246,6 +482,13 @@ public interface XposedInterface {
@NonNull @NonNull
String getFrameworkName(); String getFrameworkName();
/**
* Gets runtime API version.
*/
default int getApiVersion() {
return LIB_API;
}
/** /**
* Gets the Xposed framework version of current implementation. * Gets the Xposed framework version of current implementation.
* *
@ -261,6 +504,21 @@ public interface XposedInterface {
*/ */
long getFrameworkVersionCode(); long getFrameworkVersionCode();
/**
* Gets the Xposed framework properties.
*/
default long getFrameworkProperties() {
long prop = 0L;
if (getFrameworkPrivilege() == FRAMEWORK_PRIVILEGE_ROOT ||
getFrameworkPrivilege() == FRAMEWORK_PRIVILEGE_CONTAINER) {
prop |= PROP_CAP_SYSTEM;
}
if (getFrameworkPrivilege() != FRAMEWORK_PRIVILEGE_EMBEDDED) {
prop |= PROP_CAP_REMOTE;
}
return prop;
}
/** /**
* Gets the Xposed framework privilege of current implementation. * Gets the Xposed framework privilege of current implementation.
* *
@ -281,6 +539,37 @@ public interface XposedInterface {
@NonNull @NonNull
MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker); MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker);
/**
* Hook the static initializer of a class with default priority.
* <p>
* Note: If the class is initialized, the hook will never be called.
* </p>
*
* @param origin The class to be hooked
* @param hooker The hooker class
* @return Unhooker for canceling the hook
* @throws IllegalArgumentException if class has no static initializer or hooker is invalid
* @throws HookFailedError if hook fails due to framework internal error
*/
@NonNull
<T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, @NonNull Class<? extends Hooker> hooker);
/**
* Hook the static initializer of a class with specified priority.
* <p>
* Note: If the class is initialized, the hook will never be called.
* </p>
*
* @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
<T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, int priority, @NonNull Class<? extends Hooker> hooker);
/** /**
* Hook a method with specified priority. * Hook a method with specified priority.
* *
@ -324,6 +613,16 @@ public interface XposedInterface {
@NonNull @NonNull
<T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker); <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker);
@NonNull
default HookBuilder hook(@NonNull Executable origin) {
return new CompatHookBuilder(this, origin);
}
@NonNull
default HookBuilder hook(@NonNull Executable origin, int priority) {
return new CompatHookBuilder(this, origin).setPriority(priority);
}
/** /**
* Deoptimizes a method in case hooked callee is not called because of inline. * Deoptimizes a method in case hooked callee is not called because of inline.
* *
@ -365,6 +664,18 @@ public interface XposedInterface {
@Nullable @Nullable
Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; 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 <T> The type of the instance
* @see Constructor#newInstance(Object...)
*/
<T> void invokeOrigin(@NonNull Constructor<T> constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException;
/** /**
* Invokes a special (non-virtual) method on a given object instance, similar to the functionality of * Invokes a special (non-virtual) method on a given object instance, similar to the functionality of
* {@code CallNonVirtual<type>Method} in JNI, which invokes an instance (nonstatic) method on a Java * {@code CallNonVirtual<type>Method} in JNI, which invokes an instance (nonstatic) method on a Java
@ -382,6 +693,21 @@ public interface XposedInterface {
@Nullable @Nullable
Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException; 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 CallNonVirtual<type>Method} 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.
*
* <p>This method is useful when you need to call {@code super.xxx()} in a hooked constructor.</p>
*
* @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...)
*/
<T> void invokeSpecial(@NonNull Constructor<T> constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException;
/** /**
* Basically the same as {@link Constructor#newInstance(Object...)}, but calls the original constructor * Basically the same as {@link Constructor#newInstance(Object...)}, but calls the original constructor
* as it was before the interception by Xposed. * as it was before the interception by Xposed.
@ -420,6 +746,14 @@ public interface XposedInterface {
*/ */
void log(@NonNull String message); void log(@NonNull String message);
/**
* Writes a message to the Xposed log with explicit priority and optional tag.
*/
default void log(int priority, @Nullable String tag, @NonNull String msg) {
String prefix = tag == null || tag.trim().isEmpty() ? "" : ("[" + tag + "] ");
log(prefix + msg);
}
/** /**
* Writes a message with a stack trace to the Xposed log. * Writes a message with a stack trace to the Xposed log.
* *
@ -428,6 +762,18 @@ public interface XposedInterface {
*/ */
void log(@NonNull String message, @NonNull Throwable throwable); void log(@NonNull String message, @NonNull Throwable throwable);
/**
* Writes a message with stack trace to the Xposed log with explicit priority and optional tag.
*/
default void log(int priority, @Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
String prefix = tag == null || tag.trim().isEmpty() ? "" : ("[" + tag + "] ");
if (tr == null) {
log(prefix + msg);
} else {
log(prefix + msg, tr);
}
}
/** /**
* Parse a dex file in memory. * Parse a dex file in memory.
* *
@ -443,8 +789,17 @@ public interface XposedInterface {
* Gets the application info of the module. * Gets the application info of the module.
*/ */
@NonNull @NonNull
@Deprecated
ApplicationInfo getApplicationInfo(); ApplicationInfo getApplicationInfo();
/**
* Gets the application info of the module.
*/
@NonNull
default ApplicationInfo getModuleApplicationInfo() {
return getApplicationInfo();
}
/** /**
* Gets remote preferences stored in Xposed framework. Note that those are read-only in hooked apps. * Gets remote preferences stored in Xposed framework. Note that those are read-only in hooked apps.
* *

View File

@ -10,6 +10,7 @@ import androidx.annotation.Nullable;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -21,129 +22,210 @@ import io.github.libxposed.api.utils.DexParser;
*/ */
public class XposedInterfaceWrapper implements XposedInterface { public class XposedInterfaceWrapper implements XposedInterface {
private final XposedInterface mBase; private volatile XposedInterface mBase;
public XposedInterfaceWrapper() {
}
XposedInterfaceWrapper(@NonNull XposedInterface base) { XposedInterfaceWrapper(@NonNull XposedInterface base) {
mBase = base; mBase = base;
} }
/**
* Attaches the framework interface to the module. Modules should never call this method directly.
*/
@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 @NonNull
@Override @Override
public final String getFrameworkName() { public final String getFrameworkName() {
ensureAttached();
return mBase.getFrameworkName(); return mBase.getFrameworkName();
} }
@NonNull @NonNull
@Override @Override
public final String getFrameworkVersion() { public final String getFrameworkVersion() {
ensureAttached();
return mBase.getFrameworkVersion(); return mBase.getFrameworkVersion();
} }
@Override @Override
public final long getFrameworkVersionCode() { public final long getFrameworkVersionCode() {
ensureAttached();
return mBase.getFrameworkVersionCode(); return mBase.getFrameworkVersionCode();
} }
@Override @Override
public final int getFrameworkPrivilege() { public final int getFrameworkPrivilege() {
ensureAttached();
return mBase.getFrameworkPrivilege(); return mBase.getFrameworkPrivilege();
} }
@NonNull @NonNull
@Override @Override
public final MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker) { public final MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hook(origin, hooker); return mBase.hook(origin, hooker);
} }
@NonNull
@Override
public final HookBuilder hook(@NonNull Executable origin) {
ensureAttached();
return mBase.hook(origin);
}
@NonNull
@Override
public final HookBuilder hook(@NonNull Executable origin, int priority) {
ensureAttached();
return mBase.hook(origin, priority);
}
@NonNull
@Override
public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hookClassInitializer(origin, hooker);
}
@NonNull
@Override
public <T> MethodUnhooker<Constructor<T>> hookClassInitializer(@NonNull Class<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hookClassInitializer(origin, priority, hooker);
}
@NonNull @NonNull
@Override @Override
public final MethodUnhooker<Method> hook(@NonNull Method origin, int priority, @NonNull Class<? extends Hooker> hooker) { public final MethodUnhooker<Method> hook(@NonNull Method origin, int priority, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hook(origin, priority, hooker); return mBase.hook(origin, priority, hooker);
} }
@NonNull @NonNull
@Override @Override
public final <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull Class<? extends Hooker> hooker) { public final <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hook(origin, hooker); return mBase.hook(origin, hooker);
} }
@NonNull @NonNull
@Override @Override
public final <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) { public final <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
ensureAttached();
return mBase.hook(origin, priority, hooker); return mBase.hook(origin, priority, hooker);
} }
@Override @Override
public final boolean deoptimize(@NonNull Method method) { public final boolean deoptimize(@NonNull Method method) {
ensureAttached();
return mBase.deoptimize(method); return mBase.deoptimize(method);
} }
@Override @Override
public final <T> boolean deoptimize(@NonNull Constructor<T> constructor) { public final <T> boolean deoptimize(@NonNull Constructor<T> constructor) {
ensureAttached();
return mBase.deoptimize(constructor); return mBase.deoptimize(constructor);
} }
@Nullable @Nullable
@Override @Override
public final Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { public final Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
ensureAttached();
return mBase.invokeOrigin(method, thisObject, args); return mBase.invokeOrigin(method, thisObject, args);
} }
@Override
public <T> void invokeOrigin(@NonNull Constructor<T> constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
ensureAttached();
mBase.invokeOrigin(constructor, thisObject, args);
}
@Nullable @Nullable
@Override @Override
public final Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { 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.invokeSpecial(method, thisObject, args);
} }
@Override
public <T> void invokeSpecial(@NonNull Constructor<T> constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException {
ensureAttached();
mBase.invokeSpecial(constructor, thisObject, args);
}
@NonNull @NonNull
@Override @Override
public final <T> T newInstanceOrigin(@NonNull Constructor<T> constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { public final <T> T newInstanceOrigin(@NonNull Constructor<T> constructor, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException {
ensureAttached();
return mBase.newInstanceOrigin(constructor, args); return mBase.newInstanceOrigin(constructor, args);
} }
@NonNull @NonNull
@Override @Override
public final <T, U> U newInstanceSpecial(@NonNull Constructor<T> constructor, @NonNull Class<U> subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException { public final <T, U> U newInstanceSpecial(@NonNull Constructor<T> constructor, @NonNull Class<U> subClass, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, InstantiationException {
ensureAttached();
return mBase.newInstanceSpecial(constructor, subClass, args); return mBase.newInstanceSpecial(constructor, subClass, args);
} }
@Override @Override
public final void log(@NonNull String message) { public final void log(@NonNull String message) {
ensureAttached();
mBase.log(message); mBase.log(message);
} }
@Override @Override
public final void log(@NonNull String message, @NonNull Throwable throwable) { public final void log(@NonNull String message, @NonNull Throwable throwable) {
ensureAttached();
mBase.log(message, throwable); mBase.log(message, throwable);
} }
@Nullable @Nullable
@Override @Override
public final DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException { public final DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException {
ensureAttached();
return mBase.parseDex(dexData, includeAnnotations); return mBase.parseDex(dexData, includeAnnotations);
} }
@NonNull @NonNull
@Override @Override
public SharedPreferences getRemotePreferences(@NonNull String name) { public SharedPreferences getRemotePreferences(@NonNull String name) {
ensureAttached();
return mBase.getRemotePreferences(name); return mBase.getRemotePreferences(name);
} }
@NonNull @NonNull
@Override @Override
public ApplicationInfo getApplicationInfo() { public ApplicationInfo getApplicationInfo() {
ensureAttached();
return mBase.getApplicationInfo(); return mBase.getApplicationInfo();
} }
@NonNull @NonNull
@Override @Override
public String[] listRemoteFiles() { public String[] listRemoteFiles() {
ensureAttached();
return mBase.listRemoteFiles(); return mBase.listRemoteFiles();
} }
@NonNull @NonNull
@Override @Override
public ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException { public ParcelFileDescriptor openRemoteFile(@NonNull String name) throws FileNotFoundException {
ensureAttached();
return mBase.openRemoteFile(name); return mBase.openRemoteFile(name);
} }
} }

View File

@ -4,10 +4,19 @@ import androidx.annotation.NonNull;
/** /**
* Super class which all Xposed module entry classes should extend.<br/> * Super class which all Xposed module entry classes should extend.<br/>
* Entry classes will be instantiated exactly once for each process. * Entry classes will be instantiated exactly once for each process. Modules should avoid
* initialization work before {@link #onModuleLoaded(ModuleLoadedParam)} is called.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface { public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface {
/**
* New API-style constructor. The framework will attach itself via
* {@link #attachFramework(XposedInterface)} and then dispatch lifecycle callbacks.
*/
public XposedModule() {
super();
}
/** /**
* Instantiates a new Xposed module.<br/> * Instantiates a new Xposed module.<br/>
* When the module is loaded into the target process, the constructor will be called. * When the module is loaded into the target process, the constructor will be called.
@ -15,7 +24,10 @@ public abstract class XposedModule extends XposedInterfaceWrapper implements Xpo
* @param base The implementation interface provided by the framework, should not be used by the module * @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 * @param param Information about the process in which the module is loaded
*/ */
@Deprecated
public XposedModule(@NonNull XposedInterface base, @NonNull ModuleLoadedParam param) { public XposedModule(@NonNull XposedInterface base, @NonNull ModuleLoadedParam param) {
super(base); this();
attachFramework(base);
onModuleLoaded(param);
} }
} }

View File

@ -1,5 +1,6 @@
package io.github.libxposed.api; package io.github.libxposed.api;
import android.app.AppComponentFactory;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.os.Build; import android.os.Build;
@ -34,7 +35,7 @@ public interface XposedModuleInterface {
/** /**
* Wraps information about system server. * Wraps information about system server.
*/ */
interface SystemServerLoadedParam { interface SystemServerStartingParam {
/** /**
* Gets the class loader of system server. * Gets the class loader of system server.
* *
@ -44,6 +45,13 @@ public interface XposedModuleInterface {
ClassLoader getClassLoader(); ClassLoader getClassLoader();
} }
/**
* @deprecated Kept for API100 compatibility.
*/
@Deprecated
interface SystemServerLoadedParam extends SystemServerStartingParam {
}
/** /**
* Wraps information about the package being loaded. * Wraps information about the package being loaded.
*/ */
@ -73,6 +81,27 @@ public interface XposedModuleInterface {
@NonNull @NonNull
ClassLoader getDefaultClassLoader(); ClassLoader getDefaultClassLoader();
/**
* 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.
*/
boolean isFirstPackage();
/**
* @deprecated Kept for API100 compatibility. Use {@link PackageReadyParam#getClassLoader()}.
*/
@Deprecated
@NonNull
default ClassLoader getClassLoader() {
return getDefaultClassLoader();
}
}
/**
* Wraps information about the package whose class loader is ready.
*/
interface PackageReadyParam extends PackageLoadedParam {
/** /**
* Gets the class loader of the package being loaded. * Gets the class loader of the package being loaded.
* *
@ -82,11 +111,17 @@ public interface XposedModuleInterface {
ClassLoader getClassLoader(); ClassLoader getClassLoader();
/** /**
* Gets information about whether is this package the first and main package of the app process. * Gets the app component factory of the package being loaded.
*
* @return {@code true} if this is the first package.
*/ */
boolean isFirstPackage(); @RequiresApi(Build.VERSION_CODES.P)
@NonNull
AppComponentFactory getAppComponentFactory();
}
/**
* Gets notified when the module is loaded into the target process.
*/
default void onModuleLoaded(@NonNull ModuleLoadedParam param) {
} }
/** /**
@ -104,5 +139,19 @@ public interface XposedModuleInterface {
* @param param Information about system server * @param param Information about system server
*/ */
default void onSystemServerLoaded(@NonNull SystemServerLoadedParam param) { default void onSystemServerLoaded(@NonNull SystemServerLoadedParam param) {
onSystemServerStarting(param);
}
/**
* Gets notified when custom {@link android.app.AppComponentFactory} has created the final
* class loader.
*/
default void onPackageReady(@NonNull PackageReadyParam param) {
}
/**
* Gets notified when system server is ready to start critical services.
*/
default void onSystemServerStarting(@NonNull SystemServerStartingParam param) {
} }
} }

View File

@ -1,11 +0,0 @@
package io.github.libxposed.api.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterInvocation {
}

View File

@ -1,11 +0,0 @@
package io.github.libxposed.api.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BeforeInvocation {
}

View File

@ -1,11 +0,0 @@
package io.github.libxposed.api.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.TYPE_USE})
public @interface XposedHooker {
}

View File

@ -1,3 +1,3 @@
tasks.register("Delete", Delete::class) { tasks.register("Delete", Delete::class) {
delete(rootProject.buildDir) delete(rootProject.layout.buildDirectory)
} }

View File

@ -4,8 +4,8 @@ plugins {
} }
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_21
} }
dependencies { dependencies {

View File

@ -1,13 +1,15 @@
[versions] [versions]
kotlin = "1.9.10" annotation = "1.8.0"
lint = "31.1.2" kotlin = "2.0.0"
agp = "8.1.2" lint = "31.5.1"
agp = "8.5.1"
[plugins] [plugins]
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
agp-lib = { id = "com.android.library", version.ref = "agp" } agp-lib = { id = "com.android.library", version.ref = "agp" }
[libraries] [libraries]
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
lint-api = { module = "com.android.tools.lint:lint-api", version.ref = "lint" } lint-api = { module = "com.android.tools.lint:lint-api", version.ref = "lint" }
lint-checks = { module = "com.android.tools.lint:lint-checks", 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" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }

Binary file not shown.

View File

@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

27
gradlew vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -83,7 +85,9 @@ done
# This is normally unused # This is normally unused
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -130,18 +134,21 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@ -149,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -198,11 +205,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command; # Collect all arguments for the java command:
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# shell script including quotes and variable substitutions, so put them in # and any embedded shellness will be escaped.
# double quotes to make sure that they get re-expanded; and # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# * put everything else in single quotes, so that it's not re-expanded. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \

22
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail