diff --git a/core/src/main/java/de/robv/android/xposed/IXposedHookCmdInit.java b/core/src/main/java/de/robv/android/xposed/IXposedHookCmdInit.java
index e7c8b1cc..655453b8 100644
--- a/core/src/main/java/de/robv/android/xposed/IXposedHookCmdInit.java
+++ b/core/src/main/java/de/robv/android/xposed/IXposedHookCmdInit.java
@@ -28,21 +28,29 @@ package de.robv.android.xposed;
* implemented anymore.
*/
public interface IXposedHookCmdInit extends IXposedMod {
- /**
- * Called very early during startup of a command-line tool.
- * @param startupParam Details about the module itself and the started process.
- * @throws Throwable Everything is caught, but it will prevent further initialization of the module.
- */
- void initCmdApp(StartupParam startupParam) throws Throwable;
+ /**
+ * Called very early during startup of a command-line tool.
+ *
+ * @param startupParam Details about the module itself and the started process.
+ * @throws Throwable Everything is caught, but it will prevent further initialization of the module.
+ */
+ void initCmdApp(StartupParam startupParam) throws Throwable;
- /** Data holder for {@link #initCmdApp}. */
- final class StartupParam {
- /*package*/ StartupParam() {}
+ /**
+ * Data holder for {@link #initCmdApp}.
+ */
+ final class StartupParam {
+ /*package*/ StartupParam() {
+ }
- /** The path to the module's APK. */
- public String modulePath;
+ /**
+ * The path to the module's APK.
+ */
+ public String modulePath;
- /** The class name of the tools that the hook was invoked for. */
- public String startClassName;
- }
+ /**
+ * The class name of the tools that the hook was invoked for.
+ */
+ public String startClassName;
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/IXposedHookInitPackageResources.java b/core/src/main/java/de/robv/android/xposed/IXposedHookInitPackageResources.java
index 73d739df..9b00ccea 100644
--- a/core/src/main/java/de/robv/android/xposed/IXposedHookInitPackageResources.java
+++ b/core/src/main/java/de/robv/android/xposed/IXposedHookInitPackageResources.java
@@ -33,33 +33,35 @@ import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResou
* registering it as a callback automatically.
*/
public interface IXposedHookInitPackageResources extends IXposedMod {
- /**
- * This method is called when resources for an app are being initialized.
- * Modules can call special methods of the {@link XResources} class in order to replace resources.
- *
- * @param resparam Information about the resources.
- * @throws Throwable Everything the callback throws is caught and logged.
- */
- void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
+ /**
+ * This method is called when resources for an app are being initialized.
+ * Modules can call special methods of the {@link XResources} class in order to replace resources.
+ *
+ * @param resparam Information about the resources.
+ * @throws Throwable Everything the callback throws is caught and logged.
+ */
+ void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
- /** @hide */
- final class Wrapper extends XC_InitPackageResources {
- private final IXposedHookInitPackageResources instance;
- private final String apkPath;
+ /**
+ * @hide
+ */
+ final class Wrapper extends XC_InitPackageResources {
+ private final IXposedHookInitPackageResources instance;
+ private final String apkPath;
- public Wrapper(IXposedHookInitPackageResources instance, String apkPath) {
- this.instance = instance;
- this.apkPath = apkPath;
- }
+ public Wrapper(IXposedHookInitPackageResources instance, String apkPath) {
+ this.instance = instance;
+ this.apkPath = apkPath;
+ }
- @Override
- public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
- instance.handleInitPackageResources(resparam);
- }
+ @Override
+ public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
+ instance.handleInitPackageResources(resparam);
+ }
- @Override
- public String getApkPath() {
- return apkPath;
- }
- }
+ @Override
+ public String getApkPath() {
+ return apkPath;
+ }
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/IXposedHookLoadPackage.java b/core/src/main/java/de/robv/android/xposed/IXposedHookLoadPackage.java
index 08d443eb..7be79f5f 100644
--- a/core/src/main/java/de/robv/android/xposed/IXposedHookLoadPackage.java
+++ b/core/src/main/java/de/robv/android/xposed/IXposedHookLoadPackage.java
@@ -33,33 +33,36 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
* registering it as a callback automatically.
*/
public interface IXposedHookLoadPackage extends IXposedMod {
- /**
- * This method is called when an app is loaded. It's called very early, even before
- * {@link Application#onCreate} is called.
- * Modules can set up their app-specific hooks here.
- *
- * @param lpparam Information about the app.
- * @throws Throwable Everything the callback throws is caught and logged.
- */
- void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
+ /**
+ * This method is called when an app is loaded. It's called very early, even before
+ * {@link Application#onCreate} is called.
+ * Modules can set up their app-specific hooks here.
+ *
+ * @param lpparam Information about the app.
+ * @throws Throwable Everything the callback throws is caught and logged.
+ */
+ void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
- /** @hide */
- final class Wrapper extends XC_LoadPackage {
- private final IXposedHookLoadPackage instance;
- private final String apkPath;
+ /**
+ * @hide
+ */
+ final class Wrapper extends XC_LoadPackage {
+ private final IXposedHookLoadPackage instance;
+ private final String apkPath;
- public Wrapper(IXposedHookLoadPackage instance, String apkPath) {
- this.instance = instance;
- this.apkPath = apkPath;
- }
- @Override
- public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
- instance.handleLoadPackage(lpparam);
- }
+ public Wrapper(IXposedHookLoadPackage instance, String apkPath) {
+ this.instance = instance;
+ this.apkPath = apkPath;
+ }
- @Override
- public String getApkPath() {
- return apkPath;
- }
- }
+ @Override
+ public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
+ instance.handleLoadPackage(lpparam);
+ }
+
+ @Override
+ public String getApkPath() {
+ return apkPath;
+ }
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/IXposedHookZygoteInit.java b/core/src/main/java/de/robv/android/xposed/IXposedHookZygoteInit.java
index 23277004..5e6d5c3a 100644
--- a/core/src/main/java/de/robv/android/xposed/IXposedHookZygoteInit.java
+++ b/core/src/main/java/de/robv/android/xposed/IXposedHookZygoteInit.java
@@ -35,16 +35,20 @@ import de.robv.android.xposed.callbacks.XCallback;
*
If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead.
*/
public interface IXposedHookZygoteInit extends IXposedMod {
- /**
- * Called very early during startup of Zygote.
- * @param startupParam Details about the module itself and the started process.
- * @throws Throwable everything is caught, but will prevent further initialization of the module.
- */
- void initZygote(StartupParam startupParam) throws Throwable;
+ /**
+ * Called very early during startup of Zygote.
+ *
+ * @param startupParam Details about the module itself and the started process.
+ * @throws Throwable everything is caught, but will prevent further initialization of the module.
+ */
+ void initZygote(StartupParam startupParam) throws Throwable;
- /** Data holder for {@link #initZygote}. */
- final class StartupParam extends XCallback.Param {
- /*package*/ StartupParam() {}
+ /**
+ * Data holder for {@link #initZygote}.
+ */
+ final class StartupParam extends XCallback.Param {
+ /*package*/ StartupParam() {
+ }
/**
* @param callbacks
@@ -54,15 +58,17 @@ public interface IXposedHookZygoteInit extends IXposedMod {
super(callbacks);
}
- /** The path to the module's APK. */
- public String modulePath;
+ /**
+ * The path to the module's APK.
+ */
+ public String modulePath;
- /**
- * Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
- * process that starts the system_server.
- */
- public boolean startsSystemServer;
- }
+ /**
+ * Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
+ * process that starts the system_server.
+ */
+ public boolean startsSystemServer;
+ }
/**
* @hide
@@ -83,9 +89,9 @@ public interface IXposedHookZygoteInit extends IXposedMod {
instance.initZygote(this.startupParam);
}
- @Override
- public String getApkPath() {
- return startupParam.modulePath;
- }
- }
+ @Override
+ public String getApkPath() {
+ return startupParam.modulePath;
+ }
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/IXposedMod.java b/core/src/main/java/de/robv/android/xposed/IXposedMod.java
index dd0fcb02..319fa560 100644
--- a/core/src/main/java/de/robv/android/xposed/IXposedMod.java
+++ b/core/src/main/java/de/robv/android/xposed/IXposedMod.java
@@ -20,5 +20,8 @@
package de.robv.android.xposed;
-/** Marker interface for Xposed modules. Cannot be implemented directly. */
-/* package */ interface IXposedMod {}
+/**
+ * Marker interface for Xposed modules. Cannot be implemented directly.
+ */
+/* package */ interface IXposedMod {
+}
diff --git a/core/src/main/java/de/robv/android/xposed/SELinuxHelper.java b/core/src/main/java/de/robv/android/xposed/SELinuxHelper.java
index 0156c0f3..a5c8fe8c 100644
--- a/core/src/main/java/de/robv/android/xposed/SELinuxHelper.java
+++ b/core/src/main/java/de/robv/android/xposed/SELinuxHelper.java
@@ -27,49 +27,50 @@ import de.robv.android.xposed.services.DirectAccessService;
* A helper to work with (or without) SELinux, abstracting much of its big complexity.
*/
public final class SELinuxHelper {
- private SELinuxHelper() {}
+ private SELinuxHelper() {
+ }
- /**
- * Determines whether SELinux is disabled or enabled.
- *
- * @return A boolean indicating whether SELinux is enabled.
- */
- public static boolean isSELinuxEnabled() {
- // lsp: always enabled
- return true;
- }
+ /**
+ * Determines whether SELinux is disabled or enabled.
+ *
+ * @return A boolean indicating whether SELinux is enabled.
+ */
+ public static boolean isSELinuxEnabled() {
+ // lsp: always enabled
+ return true;
+ }
- /**
- * Determines whether SELinux is permissive or enforcing.
- *
- * @return A boolean indicating whether SELinux is enforcing.
- */
- public static boolean isSELinuxEnforced() {
- // lsp: always enforcing
- return true;
- }
+ /**
+ * Determines whether SELinux is permissive or enforcing.
+ *
+ * @return A boolean indicating whether SELinux is enforcing.
+ */
+ public static boolean isSELinuxEnforced() {
+ // lsp: always enforcing
+ return true;
+ }
- /**
- * Gets the security context of the current process.
- *
- * @return A String representing the security context of the current process.
- */
- public static String getContext() {
- return null;
- }
+ /**
+ * Gets the security context of the current process.
+ *
+ * @return A String representing the security context of the current process.
+ */
+ public static String getContext() {
+ return null;
+ }
- /**
- * Retrieve the service to be used when accessing files in {@code /data/data/*}.
- *
- *
IMPORTANT: If you call this from the Zygote process,
- * don't re-use the result in different process!
- *
- * @return An instance of the service.
- */
- public static BaseService getAppDataFileService() {
+ /**
+ * Retrieve the service to be used when accessing files in {@code /data/data/*}.
+ *
+ *
IMPORTANT: If you call this from the Zygote process,
+ * don't re-use the result in different process!
+ *
+ * @return An instance of the service.
+ */
+ public static BaseService getAppDataFileService() {
return sServiceAppDataFile;
}
- private static final BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
+ private static final BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
}
diff --git a/core/src/main/java/de/robv/android/xposed/XC_MethodHook.java b/core/src/main/java/de/robv/android/xposed/XC_MethodHook.java
index 9fa3193c..e593064b 100644
--- a/core/src/main/java/de/robv/android/xposed/XC_MethodHook.java
+++ b/core/src/main/java/de/robv/android/xposed/XC_MethodHook.java
@@ -32,157 +32,175 @@ import de.robv.android.xposed.callbacks.XCallback;
* {@link #beforeHookedMethod} and/or {@link #afterHookedMethod}.
*/
public abstract class XC_MethodHook extends XCallback {
- /**
- * Creates a new callback with default priority.
- */
- @SuppressWarnings("deprecation")
- public XC_MethodHook() {
- super();
- }
+ /**
+ * Creates a new callback with default priority.
+ */
+ @SuppressWarnings("deprecation")
+ public XC_MethodHook() {
+ super();
+ }
- /**
- * Creates a new callback with a specific priority.
- *
- *
Note that {@link #afterHookedMethod} will be called in reversed order, i.e.
- * the callback with the highest priority will be called last. This way, the callback has the
- * final control over the return value. {@link #beforeHookedMethod} is called as usual, i.e.
- * highest priority first.
- *
- * @param priority See {@link XCallback#priority}.
- */
- public XC_MethodHook(int priority) {
- super(priority);
- }
+ /**
+ * Creates a new callback with a specific priority.
+ *
+ *
Note that {@link #afterHookedMethod} will be called in reversed order, i.e.
+ * the callback with the highest priority will be called last. This way, the callback has the
+ * final control over the return value. {@link #beforeHookedMethod} is called as usual, i.e.
+ * highest priority first.
+ *
+ * @param priority See {@link XCallback#priority}.
+ */
+ public XC_MethodHook(int priority) {
+ super(priority);
+ }
- /**
- * Called before the invocation of the method.
- *
- *
You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
- * to prevent the original method from being called.
- *
- *
Note that implementations shouldn't call {@code super(param)}, it's not necessary.
- *
- * @param param Information about the method call.
- * @throws Throwable Everything the callback throws is caught and logged.
- */
- protected void beforeHookedMethod(MethodHookParam param) throws Throwable {}
+ /**
+ * Called before the invocation of the method.
+ *
+ *
You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
+ * to prevent the original method from being called.
+ *
+ *
Note that implementations shouldn't call {@code super(param)}, it's not necessary.
+ *
+ * @param param Information about the method call.
+ * @throws Throwable Everything the callback throws is caught and logged.
+ */
+ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ }
- public void callBeforeHookedMethod(MethodHookParam param) throws Throwable {
- beforeHookedMethod(param);
- }
+ public void callBeforeHookedMethod(MethodHookParam param) throws Throwable {
+ beforeHookedMethod(param);
+ }
- /**
- * Called after the invocation of the method.
- *
- *
You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
- * to modify the return value of the original method.
- *
- *
Note that implementations shouldn't call {@code super(param)}, it's not necessary.
- *
- * @param param Information about the method call.
- * @throws Throwable Everything the callback throws is caught and logged.
- */
- protected void afterHookedMethod(MethodHookParam param) throws Throwable {}
+ /**
+ * Called after the invocation of the method.
+ *
+ *
You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
+ * to modify the return value of the original method.
+ *
+ *
Note that implementations shouldn't call {@code super(param)}, it's not necessary.
+ *
+ * @param param Information about the method call.
+ * @throws Throwable Everything the callback throws is caught and logged.
+ */
+ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
+ }
- public void callAfterHookedMethod(MethodHookParam param) throws Throwable {
- afterHookedMethod(param);
- }
+ public void callAfterHookedMethod(MethodHookParam param) throws Throwable {
+ afterHookedMethod(param);
+ }
- /**
- * Wraps information about the method call and allows to influence it.
- */
- public static final class MethodHookParam extends XCallback.Param {
- /** @hide */
- @SuppressWarnings("deprecation")
- public MethodHookParam() {
- super();
- }
+ /**
+ * Wraps information about the method call and allows to influence it.
+ */
+ public static final class MethodHookParam extends XCallback.Param {
+ /**
+ * @hide
+ */
+ @SuppressWarnings("deprecation")
+ public MethodHookParam() {
+ super();
+ }
- /** The hooked method/constructor. */
- public Member method;
+ /**
+ * The hooked method/constructor.
+ */
+ public Member method;
- /** The {@code this} reference for an instance method, or {@code null} for static methods. */
- public Object thisObject;
+ /**
+ * The {@code this} reference for an instance method, or {@code null} for static methods.
+ */
+ public Object thisObject;
- /** Arguments to the method call. */
- public Object[] args;
+ /**
+ * Arguments to the method call.
+ */
+ public Object[] args;
- private Object result = null;
- private Throwable throwable = null;
- public boolean returnEarly = false;
+ private Object result = null;
+ private Throwable throwable = null;
+ public boolean returnEarly = false;
- /** Returns the result of the method call. */
- public Object getResult() {
- return result;
- }
+ /**
+ * Returns the result of the method call.
+ */
+ public Object getResult() {
+ return result;
+ }
- /**
- * Modify the result of the method call.
- *
- *
If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
- */
- public void setResult(Object result) {
- this.result = result;
- this.throwable = null;
- this.returnEarly = true;
- }
+ /**
+ * Modify the result of the method call.
+ *
+ *
If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
+ */
+ public void setResult(Object result) {
+ this.result = result;
+ this.throwable = null;
+ this.returnEarly = true;
+ }
- /** Returns the {@link Throwable} thrown by the method, or {@code null}. */
- public Throwable getThrowable() {
- return throwable;
- }
+ /**
+ * Returns the {@link Throwable} thrown by the method, or {@code null}.
+ */
+ public Throwable getThrowable() {
+ return throwable;
+ }
- /** Returns true if an exception was thrown by the method. */
- public boolean hasThrowable() {
- return throwable != null;
- }
+ /**
+ * Returns true if an exception was thrown by the method.
+ */
+ public boolean hasThrowable() {
+ return throwable != null;
+ }
- /**
- * Modify the exception thrown of the method call.
- *
- *
If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
- */
- public void setThrowable(Throwable throwable) {
- this.throwable = throwable;
- this.result = null;
- this.returnEarly = true;
- }
+ /**
+ * Modify the exception thrown of the method call.
+ *
+ *
If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
+ */
+ public void setThrowable(Throwable throwable) {
+ this.throwable = throwable;
+ this.result = null;
+ this.returnEarly = true;
+ }
- /** Returns the result of the method call, or throws the Throwable caused by it. */
- public Object getResultOrThrowable() throws Throwable {
- if (throwable != null)
- throw throwable;
- return result;
- }
- }
+ /**
+ * Returns the result of the method call, or throws the Throwable caused by it.
+ */
+ public Object getResultOrThrowable() throws Throwable {
+ if (throwable != null)
+ throw throwable;
+ return result;
+ }
+ }
- /**
- * An object with which the method/constructor can be unhooked.
- */
- public class Unhook implements IXUnhook {
- private final Member hookMethod;
+ /**
+ * An object with which the method/constructor can be unhooked.
+ */
+ public class Unhook implements IXUnhook {
+ private final Member hookMethod;
- /*package*/ Unhook(Member hookMethod) {
- this.hookMethod = hookMethod;
- }
+ /*package*/ Unhook(Member hookMethod) {
+ this.hookMethod = hookMethod;
+ }
- /**
- * Returns the method/constructor that has been hooked.
- */
- public Member getHookedMethod() {
- return hookMethod;
- }
+ /**
+ * Returns the method/constructor that has been hooked.
+ */
+ public Member getHookedMethod() {
+ return hookMethod;
+ }
- @Override
- public XC_MethodHook getCallback() {
- return XC_MethodHook.this;
- }
+ @Override
+ public XC_MethodHook getCallback() {
+ return XC_MethodHook.this;
+ }
- @SuppressWarnings("deprecation")
- @Override
- public void unhook() {
- XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
- }
+ @SuppressWarnings("deprecation")
+ @Override
+ public void unhook() {
+ XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
+ }
- }
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/XC_MethodReplacement.java b/core/src/main/java/de/robv/android/xposed/XC_MethodReplacement.java
index f9169b60..3bb3b237 100644
--- a/core/src/main/java/de/robv/android/xposed/XC_MethodReplacement.java
+++ b/core/src/main/java/de/robv/android/xposed/XC_MethodReplacement.java
@@ -26,83 +26,88 @@ import de.robv.android.xposed.callbacks.XCallback;
* A special case of {@link XC_MethodHook} which completely replaces the original method.
*/
public abstract class XC_MethodReplacement extends XC_MethodHook {
- /**
- * Creates a new callback with default priority.
- */
- public XC_MethodReplacement() {
- super();
- }
+ /**
+ * Creates a new callback with default priority.
+ */
+ public XC_MethodReplacement() {
+ super();
+ }
- /**
- * Creates a new callback with a specific priority.
- *
- * @param priority See {@link XCallback#priority}.
- */
- public XC_MethodReplacement(int priority) {
- super(priority);
- }
+ /**
+ * Creates a new callback with a specific priority.
+ *
+ * @param priority See {@link XCallback#priority}.
+ */
+ public XC_MethodReplacement(int priority) {
+ super(priority);
+ }
- /** @hide */
- @Override
- protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
- try {
- Object result = replaceHookedMethod(param);
- param.setResult(result);
- } catch (Throwable t) {
- XposedBridge.log(t);
- param.setThrowable(t);
- }
- }
+ /**
+ * @hide
+ */
+ @Override
+ protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ try {
+ Object result = replaceHookedMethod(param);
+ param.setResult(result);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ param.setThrowable(t);
+ }
+ }
- /** @hide */
- @Override
- @SuppressWarnings("EmptyMethod")
- protected final void afterHookedMethod(MethodHookParam param) throws Throwable {}
+ /**
+ * @hide
+ */
+ @Override
+ @SuppressWarnings("EmptyMethod")
+ protected final void afterHookedMethod(MethodHookParam param) throws Throwable {
+ }
- /**
- * Shortcut for replacing a method completely. Whatever is returned/thrown here is taken
- * instead of the result of the original method (which will not be called).
- *
- * Note that implementations shouldn't call {@code super(param)}, it's not necessary.
- *
- * @param param Information about the method call.
- * @throws Throwable Anything that is thrown by the callback will be passed on to the original caller.
- */
- @SuppressWarnings("UnusedParameters")
- protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable;
+ /**
+ * Shortcut for replacing a method completely. Whatever is returned/thrown here is taken
+ * instead of the result of the original method (which will not be called).
+ *
+ *
Note that implementations shouldn't call {@code super(param)}, it's not necessary.
+ *
+ * @param param Information about the method call.
+ * @throws Throwable Anything that is thrown by the callback will be passed on to the original caller.
+ */
+ @SuppressWarnings("UnusedParameters")
+ protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable;
- /**
- * Predefined callback that skips the method without replacements.
- */
- public static final XC_MethodReplacement DO_NOTHING = new XC_MethodReplacement(PRIORITY_HIGHEST*2) {
- @Override
- protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
- return null;
- }
- };
+ /**
+ * Predefined callback that skips the method without replacements.
+ */
+ public static final XC_MethodReplacement DO_NOTHING = new XC_MethodReplacement(PRIORITY_HIGHEST * 2) {
+ @Override
+ protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
+ return null;
+ }
+ };
- /**
- * Creates a callback which always returns a specific value.
- *
- * @param result The value that should be returned to callers of the hooked method.
- */
- public static XC_MethodReplacement returnConstant(final Object result) {
- return returnConstant(PRIORITY_DEFAULT, result);
- }
+ /**
+ * Creates a callback which always returns a specific value.
+ *
+ * @param result The value that should be returned to callers of the hooked method.
+ */
+ public static XC_MethodReplacement returnConstant(final Object result) {
+ return returnConstant(PRIORITY_DEFAULT, result);
+ }
- /**
- * Like {@link #returnConstant(Object)}, but allows to specify a priority for the callback.
- *
- * @param priority See {@link XCallback#priority}.
- * @param result The value that should be returned to callers of the hooked method.
- */
- public static XC_MethodReplacement returnConstant(int priority, final Object result) {
- return new XC_MethodReplacement(priority) {
- @Override
- protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
- return result;
- }
- };
- }
+ /**
+ * Like {@link #returnConstant(Object)}, but allows to specify a priority for the callback.
+ *
+ * @param priority See {@link XCallback#priority}.
+ * @param result The value that should be returned to callers of the hooked method.
+ */
+ public static XC_MethodReplacement returnConstant(int priority, final Object result) {
+ return new XC_MethodReplacement(priority) {
+ @Override
+ protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
+ return result;
+ }
+ };
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/XSharedPreferences.java b/core/src/main/java/de/robv/android/xposed/XSharedPreferences.java
index d2501d8b..1d73b9dc 100644
--- a/core/src/main/java/de/robv/android/xposed/XSharedPreferences.java
+++ b/core/src/main/java/de/robv/android/xposed/XSharedPreferences.java
@@ -28,6 +28,7 @@ import android.preference.PreferenceManager;
import android.util.Log;
import com.android.internal.util.XmlUtils;
+
import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.util.MetaDataReader;
@@ -99,7 +100,8 @@ public final class XSharedPreferences implements SharedPreferences {
Path dir = (Path) key.watchable();
Path path = dir.resolve((Path) event.context());
String pathStr = path.toString();
- if (BuildConfig.DEBUG) Log.v(TAG, "File " + path.toString() + " event: " + kind.name());
+ if (BuildConfig.DEBUG)
+ Log.v(TAG, "File " + path.toString() + " event: " + kind.name());
// We react to both real and backup files due to rare race conditions
if (pathStr.endsWith(".bak")) {
if (kind != StandardWatchEventKinds.ENTRY_DELETE) {
@@ -114,7 +116,8 @@ public final class XSharedPreferences implements SharedPreferences {
try {
l.onSharedPreferenceChanged(data.mPrefs, null);
} catch (Throwable t) {
- if (BuildConfig.DEBUG) Log.e(TAG, "Fail in preference change listener", t);
+ if (BuildConfig.DEBUG)
+ Log.e(TAG, "Fail in preference change listener", t);
}
}
}
@@ -183,7 +186,7 @@ public final class XSharedPreferences implements SharedPreferences {
}
}
if (newModule) {
- mFile = new File(serviceClient.getPrefsPath( packageName ), prefFileName + ".xml");
+ mFile = new File(serviceClient.getPrefsPath(packageName), prefFileName + ".xml");
} else {
mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml");
}
@@ -209,7 +212,8 @@ public final class XSharedPreferences implements SharedPreferences {
if (sWatcherDaemon == null || !sWatcherDaemon.isAlive()) {
initWatcherDaemon();
}
- if (BuildConfig.DEBUG) Log.d(TAG, "tryRegisterWatcher: registered file watcher for " + path);
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "tryRegisterWatcher: registered file watcher for " + path);
} catch (AccessDeniedException accDeniedEx) {
if (BuildConfig.DEBUG) Log.e(TAG, "tryRegisterWatcher: access denied to " + path);
} catch (Exception e) {
@@ -232,7 +236,8 @@ public final class XSharedPreferences implements SharedPreferences {
if (!atLeastOneValid) {
try {
sWatcher.close();
- } catch (Exception ignore) { }
+ } catch (Exception ignore) {
+ }
}
}
}
@@ -522,7 +527,7 @@ public final class XSharedPreferences implements SharedPreferences {
if (listener == null)
throw new IllegalArgumentException("listener cannot be null");
- synchronized(this) {
+ synchronized (this) {
if (mListeners.put(listener, sContent) == null) {
tryRegisterWatcher();
}
@@ -537,7 +542,7 @@ public final class XSharedPreferences implements SharedPreferences {
*/
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
- synchronized(this) {
+ synchronized (this) {
if (mListeners.remove(listener) != null && mListeners.isEmpty()) {
tryUnregisterWatcher();
}
diff --git a/core/src/main/java/de/robv/android/xposed/XposedBridge.java b/core/src/main/java/de/robv/android/xposed/XposedBridge.java
index 9842e31c..ac58958f 100644
--- a/core/src/main/java/de/robv/android/xposed/XposedBridge.java
+++ b/core/src/main/java/de/robv/android/xposed/XposedBridge.java
@@ -20,11 +20,16 @@
package de.robv.android.xposed;
+import static de.robv.android.xposed.XposedHelpers.setObjectField;
+
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.Log;
import org.lsposed.lspd.BuildConfig;
+import org.lsposed.lspd.nativebridge.ModuleLogger;
+import org.lsposed.lspd.nativebridge.ResourcesHook;
+import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Executable;
@@ -43,12 +48,6 @@ import java.util.Set;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
-import org.lsposed.lspd.nativebridge.ModuleLogger;
-import org.lsposed.lspd.nativebridge.ResourcesHook;
-import org.lsposed.lspd.yahfa.dexmaker.DynamicBridge;
-import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
-
-import static de.robv.android.xposed.XposedHelpers.setObjectField;
/**
* This class contains most of Xposed's central logic, such as initialization and callbacks used by
@@ -56,377 +55,373 @@ import static de.robv.android.xposed.XposedHelpers.setObjectField;
*/
@SuppressWarnings("JniMissingFunction")
public final class XposedBridge {
- /**
- * The system class loader which can be used to locate Android framework classes.
- * Application classes cannot be retrieved from it.
- *
- * @see ClassLoader#getSystemClassLoader
- */
- public static final ClassLoader BOOTCLASSLOADER = XposedBridge.class.getClassLoader();
+ /**
+ * The system class loader which can be used to locate Android framework classes.
+ * Application classes cannot be retrieved from it.
+ *
+ * @see ClassLoader#getSystemClassLoader
+ */
+ public static final ClassLoader BOOTCLASSLOADER = XposedBridge.class.getClassLoader();
- /** @hide */
- public static final String TAG = "LSPosed-Bridge";
+ /**
+ * @hide
+ */
+ public static final String TAG = "LSPosed-Bridge";
- /** @deprecated Use {@link #getXposedVersion()} instead. */
- @Deprecated
- public static int XPOSED_BRIDGE_VERSION;
+ /**
+ * @deprecated Use {@link #getXposedVersion()} instead.
+ */
+ @Deprecated
+ public static int XPOSED_BRIDGE_VERSION;
- /*package*/ static boolean isZygote = true; // ed: RuntimeInit.main() tool process not supported yet
+ private static final Object[] EMPTY_ARRAY = new Object[0];
- // This field is set "magically" on MIUI.
- /*package*/ static long BOOT_START_TIME;
+ // built-in handlers
+ private static final Map> sHookedMethodCallbacks = new NoValuesHashMap<>();
+ public static final CopyOnWriteSortedSet sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
+ /*package*/ static final CopyOnWriteSortedSet sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
+ /*package*/ static final CopyOnWriteSortedSet sInitZygoteCallbacks = new CopyOnWriteSortedSet<>();
- private static final Object[] EMPTY_ARRAY = new Object[0];
+ private XposedBridge() {
+ }
- // built-in handlers
- private static final Map> sHookedMethodCallbacks = new NoValuesHashMap<>();
- public static final CopyOnWriteSortedSet sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
- /*package*/ static final CopyOnWriteSortedSet sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
- /*package*/ static final CopyOnWriteSortedSet sInitZygoteCallbacks = new CopyOnWriteSortedSet<>();
+ public static volatile ClassLoader dummyClassLoader = null;
- private XposedBridge() {}
-
- public static volatile ClassLoader dummyClassLoader = null;
-
- public static void initXResources() {
+ public static void initXResources() {
if (dummyClassLoader != null) {
- return;
- }
- try {
- Resources res = Resources.getSystem();
- Class> resClass = res.getClass();
- Class> taClass = TypedArray.class;
- try {
- TypedArray ta = res.obtainTypedArray(res.getIdentifier(
- "preloaded_drawables", "array", "android"));
- taClass = ta.getClass();
- ta.recycle();
- } catch (Resources.NotFoundException nfe) {
- XposedBridge.log(nfe);
- }
- ResourcesHook.removeFinalFlagNative(resClass);
- ResourcesHook.removeFinalFlagNative(taClass);
- ClassLoader myCL = XposedBridge.class.getClassLoader();
- dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass, taClass);
- dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass");
- dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
- setObjectField(myCL, "parent", dummyClassLoader);
- } catch (Throwable throwable) {
- XposedBridge.log(throwable);
- XposedInit.disableResources = true;
- }
- }
+ return;
+ }
+ try {
+ Resources res = Resources.getSystem();
+ Class> resClass = res.getClass();
+ Class> taClass = TypedArray.class;
+ try {
+ TypedArray ta = res.obtainTypedArray(res.getIdentifier(
+ "preloaded_drawables", "array", "android"));
+ taClass = ta.getClass();
+ ta.recycle();
+ } catch (Resources.NotFoundException nfe) {
+ XposedBridge.log(nfe);
+ }
+ ResourcesHook.removeFinalFlagNative(resClass);
+ ResourcesHook.removeFinalFlagNative(taClass);
+ ClassLoader myCL = XposedBridge.class.getClassLoader();
+ dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass, taClass);
+ dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass");
+ dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
+ setObjectField(myCL, "parent", dummyClassLoader);
+ } catch (Throwable throwable) {
+ XposedBridge.log(throwable);
+ XposedInit.disableResources = true;
+ }
+ }
- /**
- * Returns the currently installed version of the Xposed framework.
- */
- public static int getXposedVersion() {
- return BuildConfig.API_CODE;
- }
+ /**
+ * Returns the currently installed version of the Xposed framework.
+ */
+ public static int getXposedVersion() {
+ return BuildConfig.API_CODE;
+ }
- /**
- * Writes a message to the Xposed modules log.
- *
- * DON'T FLOOD THE LOG!!! This is only meant for error logging.
- * If you want to write information/debug messages, use logcat.
- *
- * @param text The log message.
- */
- public synchronized static void log(String text) {
- Log.i(TAG, text);
- ModuleLogger.log(text, false);
- }
+ /**
+ * Writes a message to the Xposed modules log.
+ *
+ *
DON'T FLOOD THE LOG!!! This is only meant for error logging.
+ * If you want to write information/debug messages, use logcat.
+ *
+ * @param text The log message.
+ */
+ public synchronized static void log(String text) {
+ Log.i(TAG, text);
+ ModuleLogger.log(text, false);
+ }
- /**
- * Logs a stack trace to the Xposed modules log.
- *
- *
DON'T FLOOD THE LOG!!! This is only meant for error logging.
- * If you want to write information/debug messages, use logcat.
- *
- * @param t The Throwable object for the stack trace.
- */
- public synchronized static void log(Throwable t) {
- String logStr = Log.getStackTraceString(t);
- Log.e(TAG, logStr);
- ModuleLogger.log(logStr, true);
- }
+ /**
+ * Logs a stack trace to the Xposed modules log.
+ *
+ *
DON'T FLOOD THE LOG!!! This is only meant for error logging.
+ * If you want to write information/debug messages, use logcat.
+ *
+ * @param t The Throwable object for the stack trace.
+ */
+ public synchronized static void log(Throwable t) {
+ String logStr = Log.getStackTraceString(t);
+ Log.e(TAG, logStr);
+ ModuleLogger.log(logStr, true);
+ }
- /**
- * Hook any method (or constructor) with the specified callback. See below for some wrappers
- * that make it easier to find a method/constructor in one step.
- *
- * @param hookMethod The method to be hooked.
- * @param callback The callback to be executed when the hooked method is called.
- * @return An object that can be used to remove the hook.
- *
- * @see XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)
- * @see XposedHelpers#findAndHookMethod(Class, String, Object...)
- * @see #hookAllMethods
- * @see XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...)
- * @see XposedHelpers#findAndHookConstructor(Class, Object...)
- * @see #hookAllConstructors
- */
- public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
- if (!(hookMethod instanceof Executable)) {
- throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
- }
- // No check interface because there may be default methods
+ /**
+ * Hook any method (or constructor) with the specified callback. See below for some wrappers
+ * that make it easier to find a method/constructor in one step.
+ *
+ * @param hookMethod The method to be hooked.
+ * @param callback The callback to be executed when the hooked method is called.
+ * @return An object that can be used to remove the hook.
+ * @see XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)
+ * @see XposedHelpers#findAndHookMethod(Class, String, Object...)
+ * @see #hookAllMethods
+ * @see XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...)
+ * @see XposedHelpers#findAndHookConstructor(Class, Object...)
+ * @see #hookAllConstructors
+ */
+ public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
+ if (!(hookMethod instanceof Executable)) {
+ throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
+ }
+ // No check interface because there may be default methods
/*else if (hookMethod.getDeclaringClass().isInterface()) {
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
}*/
- else if (Modifier.isAbstract(hookMethod.getModifiers())) {
- throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
- }
+ else if (Modifier.isAbstract(hookMethod.getModifiers())) {
+ throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
+ }
- Executable targetMethod = (Executable) hookMethod;
+ Executable targetMethod = (Executable) hookMethod;
- if (callback == null) {
- throw new IllegalArgumentException("callback should not be null!");
- }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback should not be null!");
+ }
- boolean newMethod = false;
- CopyOnWriteSortedSet callbacks;
- synchronized (sHookedMethodCallbacks) {
- callbacks = sHookedMethodCallbacks.get(targetMethod);
- if (callbacks == null) {
- callbacks = new CopyOnWriteSortedSet<>();
- sHookedMethodCallbacks.put(targetMethod, callbacks);
- newMethod = true;
- }
- }
- callbacks.add(callback);
+ boolean newMethod = false;
+ CopyOnWriteSortedSet callbacks;
+ synchronized (sHookedMethodCallbacks) {
+ callbacks = sHookedMethodCallbacks.get(targetMethod);
+ if (callbacks == null) {
+ callbacks = new CopyOnWriteSortedSet<>();
+ sHookedMethodCallbacks.put(targetMethod, callbacks);
+ newMethod = true;
+ }
+ }
+ callbacks.add(callback);
- if (newMethod) {
- AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
+ if (newMethod) {
+ AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
if (!YahfaHooker.shouldDelayHook(targetMethod)) {
- YahfaHooker.hookMethod(targetMethod, additionalInfo);
- } else {
- PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo);
- }
+ YahfaHooker.hookMethod(targetMethod, additionalInfo);
+ } else {
+ PendingHooks.recordPendingMethod((Method) hookMethod, additionalInfo);
+ }
}
return callback.new Unhook(hookMethod);
- }
+ }
- /**
- * Removes the callback for a hooked method/constructor.
- *
- * @deprecated Use {@link XC_MethodHook.Unhook#unhook} instead. An instance of the {@code Unhook}
- * class is returned when you hook the method.
- *
- * @param hookMethod The method for which the callback should be removed.
- * @param callback The reference to the callback as specified in {@link #hookMethod}.
- */
- @Deprecated
- public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
- CopyOnWriteSortedSet callbacks;
- synchronized (sHookedMethodCallbacks) {
- callbacks = sHookedMethodCallbacks.get(hookMethod);
- if (callbacks == null)
- return;
- }
- callbacks.remove(callback);
- }
+ /**
+ * Removes the callback for a hooked method/constructor.
+ *
+ * @param hookMethod The method for which the callback should be removed.
+ * @param callback The reference to the callback as specified in {@link #hookMethod}.
+ * @deprecated Use {@link XC_MethodHook.Unhook#unhook} instead. An instance of the {@code Unhook}
+ * class is returned when you hook the method.
+ */
+ @Deprecated
+ public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
+ CopyOnWriteSortedSet callbacks;
+ synchronized (sHookedMethodCallbacks) {
+ callbacks = sHookedMethodCallbacks.get(hookMethod);
+ if (callbacks == null)
+ return;
+ }
+ callbacks.remove(callback);
+ }
- /**
- * Hooks all methods with a certain name that were declared in the specified class. Inherited
- * methods and constructors are not considered. For constructors, use
- * {@link #hookAllConstructors} instead.
- *
- * @param hookClass The class to check for declared methods.
- * @param methodName The name of the method(s) to hook.
- * @param callback The callback to be executed when the hooked methods are called.
- * @return A set containing one object for each found method which can be used to unhook it.
- */
- @SuppressWarnings("UnusedReturnValue")
- public static Set hookAllMethods(Class> hookClass, String methodName, XC_MethodHook callback) {
- Set unhooks = new HashSet<>();
- for (Member method : hookClass.getDeclaredMethods())
- if (method.getName().equals(methodName))
- unhooks.add(hookMethod(method, callback));
- return unhooks;
- }
+ /**
+ * Hooks all methods with a certain name that were declared in the specified class. Inherited
+ * methods and constructors are not considered. For constructors, use
+ * {@link #hookAllConstructors} instead.
+ *
+ * @param hookClass The class to check for declared methods.
+ * @param methodName The name of the method(s) to hook.
+ * @param callback The callback to be executed when the hooked methods are called.
+ * @return A set containing one object for each found method which can be used to unhook it.
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public static Set hookAllMethods(Class> hookClass, String methodName, XC_MethodHook callback) {
+ Set unhooks = new HashSet<>();
+ for (Member method : hookClass.getDeclaredMethods())
+ if (method.getName().equals(methodName))
+ unhooks.add(hookMethod(method, callback));
+ return unhooks;
+ }
- /**
- * Hook all constructors of the specified class.
- *
- * @param hookClass The class to check for constructors.
- * @param callback The callback to be executed when the hooked constructors are called.
- * @return A set containing one object for each found constructor which can be used to unhook it.
- */
- @SuppressWarnings("UnusedReturnValue")
- public static Set hookAllConstructors(Class> hookClass, XC_MethodHook callback) {
- Set unhooks = new HashSet<>();
- for (Member constructor : hookClass.getDeclaredConstructors())
- unhooks.add(hookMethod(constructor, callback));
- return unhooks;
- }
+ /**
+ * Hook all constructors of the specified class.
+ *
+ * @param hookClass The class to check for constructors.
+ * @param callback The callback to be executed when the hooked constructors are called.
+ * @return A set containing one object for each found constructor which can be used to unhook it.
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public static Set hookAllConstructors(Class> hookClass, XC_MethodHook callback) {
+ Set unhooks = new HashSet<>();
+ for (Member constructor : hookClass.getDeclaredConstructors())
+ unhooks.add(hookMethod(constructor, callback));
+ return unhooks;
+ }
- /**
- * Adds a callback to be executed when an app ("Android package") is loaded.
- *
- * You probably don't need to call this. Simply implement {@link IXposedHookLoadPackage}
- * in your module class and Xposed will take care of registering it as a callback.
- *
- * @param callback The callback to be executed.
- * @hide
- */
- public static void hookLoadPackage(XC_LoadPackage callback) {
- synchronized (sLoadedPackageCallbacks) {
- sLoadedPackageCallbacks.add(callback);
- }
- }
+ /**
+ * Adds a callback to be executed when an app ("Android package") is loaded.
+ *
+ *
You probably don't need to call this. Simply implement {@link IXposedHookLoadPackage}
+ * in your module class and Xposed will take care of registering it as a callback.
+ *
+ * @param callback The callback to be executed.
+ * @hide
+ */
+ public static void hookLoadPackage(XC_LoadPackage callback) {
+ synchronized (sLoadedPackageCallbacks) {
+ sLoadedPackageCallbacks.add(callback);
+ }
+ }
- public static void clearLoadedPackages() {
- synchronized (sLoadedPackageCallbacks) {
- sLoadedPackageCallbacks.clear();
- }
- }
+ public static void clearLoadedPackages() {
+ synchronized (sLoadedPackageCallbacks) {
+ sLoadedPackageCallbacks.clear();
+ }
+ }
- /**
- * Adds a callback to be executed when the resources for an app are initialized.
- *
- *
You probably don't need to call this. Simply implement {@link IXposedHookInitPackageResources}
- * in your module class and Xposed will take care of registering it as a callback.
- *
- * @param callback The callback to be executed.
- * @hide
- */
- public static void hookInitPackageResources(XC_InitPackageResources callback) {
- synchronized (sInitPackageResourcesCallbacks) {
- sInitPackageResourcesCallbacks.add(callback);
- }
- }
+ /**
+ * Adds a callback to be executed when the resources for an app are initialized.
+ *
+ *
You probably don't need to call this. Simply implement {@link IXposedHookInitPackageResources}
+ * in your module class and Xposed will take care of registering it as a callback.
+ *
+ * @param callback The callback to be executed.
+ * @hide
+ */
+ public static void hookInitPackageResources(XC_InitPackageResources callback) {
+ synchronized (sInitPackageResourcesCallbacks) {
+ sInitPackageResourcesCallbacks.add(callback);
+ }
+ }
- public static void hookInitZygote(XC_InitZygote callback) {
- synchronized (sInitZygoteCallbacks) {
- sInitZygoteCallbacks.add(callback);
- }
- }
+ public static void hookInitZygote(XC_InitZygote callback) {
+ synchronized (sInitZygoteCallbacks) {
+ sInitZygoteCallbacks.add(callback);
+ }
+ }
- /**
- * Basically the same as {@link Method#invoke}, but calls the original method
- * as it was before the interception by Xposed. Also, access permissions are not checked.
- *
- *
There are very few cases where this method is needed. A common mistake is
- * to replace a method and then invoke the original one based on dynamic conditions. This
- * creates overhead and skips further hooks by other modules. Instead, just hook (don't replace)
- * the method and call {@code param.setResult(null)} in {@link XC_MethodHook#beforeHookedMethod}
- * if the original method should be skipped.
- *
- * @param method The method to be called.
- * @param thisObject For non-static calls, the "this" pointer, otherwise {@code null}.
- * @param args Arguments for the method call as Object[] array.
- * @return The result returned from the invoked method.
- * @throws NullPointerException
- * if {@code receiver == null} for a non-static method
- * @throws IllegalAccessException
- * if this method is not accessible (see {@link AccessibleObject})
- * @throws IllegalArgumentException
- * if the number of arguments doesn't match the number of parameters, the receiver
- * is incompatible with the declaring class, or an argument could not be unboxed
- * or converted by a widening conversion to the corresponding parameter type
- * @throws InvocationTargetException
- * if an exception was thrown by the invoked method
- */
- public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
- throws Throwable {
- if (args == null) {
- args = EMPTY_ARRAY;
- }
+ /**
+ * Basically the same as {@link Method#invoke}, but calls the original method
+ * as it was before the interception by Xposed. Also, access permissions are not checked.
+ *
+ *
There are very few cases where this method is needed. A common mistake is
+ * to replace a method and then invoke the original one based on dynamic conditions. This
+ * creates overhead and skips further hooks by other modules. Instead, just hook (don't replace)
+ * the method and call {@code param.setResult(null)} in {@link XC_MethodHook#beforeHookedMethod}
+ * if the original method should be skipped.
+ *
+ * @param method The method to be called.
+ * @param thisObject For non-static calls, the "this" pointer, otherwise {@code null}.
+ * @param args Arguments for the method call as Object[] array.
+ * @return The result returned from the invoked method.
+ * @throws NullPointerException if {@code receiver == null} for a non-static method
+ * @throws IllegalAccessException if this method is not accessible (see {@link AccessibleObject})
+ * @throws IllegalArgumentException if the number of arguments doesn't match the number of parameters, the receiver
+ * is incompatible with the declaring class, or an argument could not be unboxed
+ * or converted by a widening conversion to the corresponding parameter type
+ * @throws InvocationTargetException if an exception was thrown by the invoked method
+ */
+ public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
+ throws Throwable {
+ if (args == null) {
+ args = EMPTY_ARRAY;
+ }
- if (!(method instanceof Executable)) {
- throw new IllegalArgumentException("method must be of type Method or Constructor");
- }
+ if (!(method instanceof Executable)) {
+ throw new IllegalArgumentException("method must be of type Method or Constructor");
+ }
- return YahfaHooker.invokeOriginalMethod((Executable) method, thisObject, args);
- }
+ return YahfaHooker.invokeOriginalMethod((Executable) method, thisObject, args);
+ }
- private static class NoValuesHashMap extends HashMap {
- @Override
- public Collection values() {
- return Collections.EMPTY_LIST;
- }
+ private static class NoValuesHashMap extends HashMap {
+ @Override
+ public Collection values() {
+ return Collections.EMPTY_LIST;
+ }
- @Override
- public void clear() {
+ @Override
+ public void clear() {
- }
+ }
- @Override
- public Set keySet() {
- return Collections.EMPTY_SET;
- }
+ @Override
+ public Set keySet() {
+ return Collections.EMPTY_SET;
+ }
- @Override
- public Set> entrySet() {
- return Collections.EMPTY_SET;
- }
+ @Override
+ public Set> entrySet() {
+ return Collections.EMPTY_SET;
+ }
- @Override
- public int size() {
- return 0;
- }
+ @Override
+ public int size() {
+ return 0;
+ }
- @Override
- public boolean isEmpty() {
- return true;
- }
- }
+ @Override
+ public boolean isEmpty() {
+ return true;
+ }
+ }
- /** @hide */
- public static final class CopyOnWriteSortedSet {
- private transient volatile Object[] elements = EMPTY_ARRAY;
+ /**
+ * @hide
+ */
+ public static final class CopyOnWriteSortedSet {
+ private transient volatile Object[] elements = EMPTY_ARRAY;
- @SuppressWarnings("UnusedReturnValue")
- public synchronized boolean add(E e) {
- int index = indexOf(e);
- if (index >= 0)
- return false;
+ @SuppressWarnings("UnusedReturnValue")
+ public synchronized boolean add(E e) {
+ int index = indexOf(e);
+ if (index >= 0)
+ return false;
- Object[] newElements = new Object[elements.length + 1];
- System.arraycopy(elements, 0, newElements, 0, elements.length);
- newElements[elements.length] = e;
- Arrays.sort(newElements);
- elements = newElements;
- return true;
- }
+ Object[] newElements = new Object[elements.length + 1];
+ System.arraycopy(elements, 0, newElements, 0, elements.length);
+ newElements[elements.length] = e;
+ Arrays.sort(newElements);
+ elements = newElements;
+ return true;
+ }
- @SuppressWarnings("UnusedReturnValue")
- public synchronized boolean remove(E e) {
- int index = indexOf(e);
- if (index == -1)
- return false;
+ @SuppressWarnings("UnusedReturnValue")
+ public synchronized boolean remove(E e) {
+ int index = indexOf(e);
+ if (index == -1)
+ return false;
- Object[] newElements = new Object[elements.length - 1];
- System.arraycopy(elements, 0, newElements, 0, index);
- System.arraycopy(elements, index + 1, newElements, index, elements.length - index - 1);
- elements = newElements;
- return true;
- }
+ Object[] newElements = new Object[elements.length - 1];
+ System.arraycopy(elements, 0, newElements, 0, index);
+ System.arraycopy(elements, index + 1, newElements, index, elements.length - index - 1);
+ elements = newElements;
+ return true;
+ }
- private int indexOf(Object o) {
- for (int i = 0; i < elements.length; i++) {
- if (o.equals(elements[i]))
- return i;
- }
- return -1;
- }
+ private int indexOf(Object o) {
+ for (int i = 0; i < elements.length; i++) {
+ if (o.equals(elements[i]))
+ return i;
+ }
+ return -1;
+ }
- public Object[] getSnapshot() {
- return elements;
- }
+ public Object[] getSnapshot() {
+ return elements;
+ }
- public synchronized void clear() {
- elements = EMPTY_ARRAY;
- }
- }
+ public synchronized void clear() {
+ elements = EMPTY_ARRAY;
+ }
+ }
- public static class AdditionalHookInfo {
- public final CopyOnWriteSortedSet callbacks;
+ public static class AdditionalHookInfo {
+ public final CopyOnWriteSortedSet callbacks;
- private AdditionalHookInfo(CopyOnWriteSortedSet callbacks) {
- this.callbacks = callbacks;
- }
- }
+ private AdditionalHookInfo(CopyOnWriteSortedSet callbacks) {
+ this.callbacks = callbacks;
+ }
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/XposedHelpers.java b/core/src/main/java/de/robv/android/xposed/XposedHelpers.java
index 8950ba0d..4e3dee26 100644
--- a/core/src/main/java/de/robv/android/xposed/XposedHelpers.java
+++ b/core/src/main/java/de/robv/android/xposed/XposedHelpers.java
@@ -50,1596 +50,1703 @@ import org.apache.commons.lang3.reflect.MemberUtilsX;
* Helpers that simplify hooking and calling methods/constructors, getting and settings fields, ...
*/
public final class XposedHelpers {
- private XposedHelpers() {}
-
- private static final HashMap fieldCache = new HashMap<>();
- private static final HashMap methodCache = new HashMap<>();
- private static final HashMap> constructorCache = new HashMap<>();
- private static final WeakHashMap> additionalFields = new WeakHashMap<>();
- private static final HashMap> sMethodDepth = new HashMap<>();
-
- /**
- * Look up a class with the specified class loader.
- *
- * There are various allowed syntaxes for the class name, but it's recommended to use one of
- * these:
- *
- * {@code java.lang.String}
- * {@code java.lang.String[]} (array)
- * {@code android.app.ActivityThread.ResourcesKey}
- * {@code android.app.ActivityThread$ResourcesKey}
- *
- *
- * @param className The class name in one of the formats mentioned above.
- * @param classLoader The class loader, or {@code null} for the boot class loader.
- * @return A reference to the class.
- * @throws ClassNotFoundError In case the class was not found.
- */
- public static Class> findClass(String className, ClassLoader classLoader) {
- if (classLoader == null)
- classLoader = XposedBridge.BOOTCLASSLOADER;
- try {
- return ClassUtils.getClass(classLoader, className, false);
- } catch (ClassNotFoundException e) {
- throw new ClassNotFoundError(e);
- }
- }
-
- /**
- * Look up and return a class if it exists.
- * Like {@link #findClass}, but doesn't throw an exception if the class doesn't exist.
- *
- * @param className The class name.
- * @param classLoader The class loader, or {@code null} for the boot class loader.
- * @return A reference to the class, or {@code null} if it doesn't exist.
- */
- public static Class> findClassIfExists(String className, ClassLoader classLoader) {
- try {
- return findClass(className, classLoader);
- } catch (ClassNotFoundError e) {
- return null;
- }
- }
-
- /**
- * Look up a field in a class and set it to accessible.
- *
- * @param clazz The class which either declares or inherits the field.
- * @param fieldName The field name.
- * @return A reference to the field.
- * @throws NoSuchFieldError In case the field was not found.
- */
- public static Field findField(Class> clazz, String fieldName) {
- String fullFieldName = clazz.getName() + '#' + fieldName;
-
- if (fieldCache.containsKey(fullFieldName)) {
- Field field = fieldCache.get(fullFieldName);
- if (field == null)
- throw new NoSuchFieldError(fullFieldName);
- return field;
- }
-
- try {
- Field field = findFieldRecursiveImpl(clazz, fieldName);
- field.setAccessible(true);
- fieldCache.put(fullFieldName, field);
- return field;
- } catch (NoSuchFieldException e) {
- fieldCache.put(fullFieldName, null);
- throw new NoSuchFieldError(fullFieldName);
- }
- }
-
- /**
- * Look up and return a field if it exists.
- * Like {@link #findField}, but doesn't throw an exception if the field doesn't exist.
- *
- * @param clazz The class which either declares or inherits the field.
- * @param fieldName The field name.
- * @return A reference to the field, or {@code null} if it doesn't exist.
- */
- public static Field findFieldIfExists(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName);
- } catch (NoSuchFieldError e) {
- return null;
- }
- }
-
- private static Field findFieldRecursiveImpl(Class> clazz, String fieldName) throws NoSuchFieldException {
- try {
- return clazz.getDeclaredField(fieldName);
- } catch (NoSuchFieldException e) {
- while (true) {
- clazz = clazz.getSuperclass();
- if (clazz == null || clazz.equals(Object.class))
- break;
-
- try {
- return clazz.getDeclaredField(fieldName);
- } catch (NoSuchFieldException ignored) {}
- }
- throw e;
- }
- }
-
- /**
- * Returns the first field of the given type in a class.
- * Might be useful for Proguard'ed classes to identify fields with unique types.
- *
- * @param clazz The class which either declares or inherits the field.
- * @param type The type of the field.
- * @return A reference to the first field of the given type.
- * @throws NoSuchFieldError In case no matching field was not found.
- */
- public static Field findFirstFieldByExactType(Class> clazz, Class> type) {
- Class> clz = clazz;
- do {
- for (Field field : clz.getDeclaredFields()) {
- if (field.getType() == type) {
- field.setAccessible(true);
- return field;
- }
- }
- } while ((clz = clz.getSuperclass()) != null);
-
- throw new NoSuchFieldError("Field of type " + type.getName() + " in class " + clazz.getName());
- }
-
- /**
- * Look up a method and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
- * for details.
- */
- public static XC_MethodHook.Unhook findAndHookMethod(Class> clazz, String methodName, Object... parameterTypesAndCallback) {
- if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
- throw new IllegalArgumentException("no callback defined");
-
- XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
- Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
-
- return XposedBridge.hookMethod(m, callback);
- }
-
- /**
- * Look up a method and hook it. The last argument must be the callback for the hook.
- *
- * This combines calls to {@link #findMethodExact(Class, String, Object...)} and
- * {@link XposedBridge#hookMethod}.
- *
- *
The method must be declared or overridden in the given class, inherited
- * methods are not considered! That's because each method implementation exists only once in
- * the memory, and when classes inherit it, they just get another reference to the implementation.
- * Hooking a method therefore applies to all classes inheriting the same implementation. You
- * have to expect that the hook applies to subclasses (unless they override the method), but you
- * shouldn't have to worry about hooks applying to superclasses, hence this "limitation".
- * There could be undesired or even dangerous hooks otherwise, e.g. if you hook
- * {@code SomeClass.equals()} and that class doesn't override the {@code equals()} on some ROMs,
- * making you hook {@code Object.equals()} instead.
- *
- *
There are two ways to specify the parameter types. If you already have a reference to the
- * {@link Class}, use that. For Android framework classes, you can often use something like
- * {@code String.class}. If you don't have the class reference, you can simply use the
- * full class name as a string, e.g. {@code java.lang.String} or {@code com.example.MyClass}.
- * It will be passed to {@link #findClass} with the same class loader that is used for the target
- * method, see its documentation for the allowed notations.
- *
- *
Primitive types, such as {@code int}, can be specified using {@code int.class} (recommended)
- * or {@code Integer.TYPE}. Note that {@code Integer.class} doesn't refer to {@code int} but to
- * {@code Integer}, which is a normal class (boxed primitive). Therefore it must not be used when
- * the method expects an {@code int} parameter - it has to be used for {@code Integer} parameters
- * though, so check the method signature in detail.
- *
- *
As last argument to this method (after the list of target method parameters), you need
- * to specify the callback that should be executed when the method is invoked. It's usually
- * an anonymous subclass of {@link XC_MethodHook} or {@link XC_MethodReplacement}.
- *
- *
Example
- *
- * // In order to hook this method ...
- * package com.example;
- * public class SomeClass {
- * public int doSomething(String s, int i, MyClass m) {
- * ...
- * }
- * }
- *
- * // ... you can use this call:
- * findAndHookMethod("com.example.SomeClass", lpparam.classLoader, String.class, int.class, "com.example.MyClass", new XC_MethodHook() {
- * @Override
- * protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
- * String oldText = (String) param.args[0];
- * Log.d("MyModule", oldText);
- *
- * param.args[0] = "test";
- * param.args[1] = 42; // auto-boxing is working here
- * setBooleanField(param.args[2], "great", true);
- *
- * // This would not work (as MyClass can't be resolved at compile time):
- * // MyClass myClass = (MyClass) param.args[2];
- * // myClass.great = true;
- * }
- * });
- *
- *
- * @param className The name of the class which implements the method.
- * @param classLoader The class loader for resolving the target and parameter classes.
- * @param methodName The target method name.
- * @param parameterTypesAndCallback The parameter types of the target method, plus the callback.
- * @throws NoSuchMethodError In case the method was not found.
- * @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
- * @return An object which can be used to remove the callback again.
- */
- public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
- return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
- }
-
- /**
- * Look up a method in a class and set it to accessible.
- * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
- */
- public static Method findMethodExact(Class> clazz, String methodName, Object... parameterTypes) {
- return findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypes));
- }
-
- /**
- * Look up and return a method if it exists.
- * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
- */
- public static Method findMethodExactIfExists(Class> clazz, String methodName, Object... parameterTypes) {
- try {
- return findMethodExact(clazz, methodName, parameterTypes);
- } catch (ClassNotFoundError | NoSuchMethodError e) {
- return null;
- }
- }
-
- /**
- * Look up a method in a class and set it to accessible.
- * The method must be declared or overridden in the given class.
- *
- * See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} for details about
- * the method and parameter type resolution.
- *
- * @param className The name of the class which implements the method.
- * @param classLoader The class loader for resolving the target and parameter classes.
- * @param methodName The target method name.
- * @param parameterTypes The parameter types of the target method.
- * @throws NoSuchMethodError In case the method was not found.
- * @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
- * @return A reference to the method.
- */
- public static Method findMethodExact(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
- return findMethodExact(findClass(className, classLoader), methodName, getParameterClasses(classLoader, parameterTypes));
- }
-
- /**
- * Look up and return a method if it exists.
- * Like {@link #findMethodExact(String, ClassLoader, String, Object...)}, but doesn't throw an
- * exception if the method doesn't exist.
- *
- * @param className The name of the class which implements the method.
- * @param classLoader The class loader for resolving the target and parameter classes.
- * @param methodName The target method name.
- * @param parameterTypes The parameter types of the target method.
- * @return A reference to the method, or {@code null} if it doesn't exist.
- */
- public static Method findMethodExactIfExists(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
- try {
- return findMethodExact(className, classLoader, methodName, parameterTypes);
- } catch (ClassNotFoundError | NoSuchMethodError e) {
- return null;
- }
- }
-
- /**
- * Look up a method in a class and set it to accessible.
- * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
- *
- *
This variant requires that you already have reference to all the parameter types.
- */
- public static Method findMethodExact(Class> clazz, String methodName, Class>... parameterTypes) {
- String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact";
-
- if (methodCache.containsKey(fullMethodName)) {
- Method method = methodCache.get(fullMethodName);
- if (method == null)
- throw new NoSuchMethodError(fullMethodName);
- return method;
- }
-
- try {
- Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
- method.setAccessible(true);
- methodCache.put(fullMethodName, method);
- return method;
- } catch (NoSuchMethodException e) {
- methodCache.put(fullMethodName, null);
- throw new NoSuchMethodError(fullMethodName);
- }
- }
-
- /**
- * Returns an array of all methods declared/overridden in a class with the specified parameter types.
- *
- *
The return type is optional, it will not be compared if it is {@code null}.
- * Use {@code void.class} if you want to search for methods returning nothing.
- *
- * @param clazz The class to look in.
- * @param returnType The return type, or {@code null} (see above).
- * @param parameterTypes The parameter types.
- * @return An array with matching methods, all set to accessible already.
- */
- public static Method[] findMethodsByExactParameters(Class> clazz, Class> returnType, Class>... parameterTypes) {
- List result = new LinkedList<>();
- for (Method method : clazz.getDeclaredMethods()) {
- if (returnType != null && returnType != method.getReturnType())
- continue;
-
- Class>[] methodParameterTypes = method.getParameterTypes();
- if (parameterTypes.length != methodParameterTypes.length)
- continue;
-
- boolean match = true;
- for (int i = 0; i < parameterTypes.length; i++) {
- if (parameterTypes[i] != methodParameterTypes[i]) {
- match = false;
- break;
- }
- }
-
- if (!match)
- continue;
-
- method.setAccessible(true);
- result.add(method);
- }
- return result.toArray(new Method[result.size()]);
- }
-
- /**
- * Look up a method in a class and set it to accessible.
- *
- * This does'nt only look for exact matches, but for the best match. All considered candidates
- * must be compatible with the given parameter types, i.e. the parameters must be assignable
- * to the method's formal parameters. Inherited methods are considered here.
- *
- * @param clazz The class which declares, inherits or overrides the method.
- * @param methodName The method name.
- * @param parameterTypes The types of the method's parameters.
- * @return A reference to the best-matching method.
- * @throws NoSuchMethodError In case no suitable method was found.
- */
- public static Method findMethodBestMatch(Class> clazz, String methodName, Class>... parameterTypes) {
- String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#bestmatch";
-
- if (methodCache.containsKey(fullMethodName)) {
- Method method = methodCache.get(fullMethodName);
- if (method == null)
- throw new NoSuchMethodError(fullMethodName);
- return method;
- }
-
- try {
- Method method = findMethodExact(clazz, methodName, parameterTypes);
- methodCache.put(fullMethodName, method);
- return method;
- } catch (NoSuchMethodError ignored) {}
-
- Method bestMatch = null;
- Class> clz = clazz;
- boolean considerPrivateMethods = true;
- do {
- for (Method method : clz.getDeclaredMethods()) {
- // don't consider private methods of superclasses
- if (!considerPrivateMethods && Modifier.isPrivate(method.getModifiers()))
- continue;
-
- // compare name and parameters
- if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
- // get accessible version of method
- if (bestMatch == null || MemberUtilsX.compareMethodFit(
- method,
- bestMatch,
- parameterTypes) < 0) {
- bestMatch = method;
- }
- }
- }
- considerPrivateMethods = false;
- } while ((clz = clz.getSuperclass()) != null);
-
- if (bestMatch != null) {
- bestMatch.setAccessible(true);
- methodCache.put(fullMethodName, bestMatch);
- return bestMatch;
- } else {
- NoSuchMethodError e = new NoSuchMethodError(fullMethodName);
- methodCache.put(fullMethodName, null);
- throw e;
- }
- }
-
- /**
- * Look up a method in a class and set it to accessible.
- *
- *
See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
- * determines the parameter types from the classes of the given objects.
- */
- public static Method findMethodBestMatch(Class> clazz, String methodName, Object... args) {
- return findMethodBestMatch(clazz, methodName, getParameterTypes(args));
- }
-
- /**
- * Look up a method in a class and set it to accessible.
- *
- *
See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
- * determines the parameter types from the classes of the given objects. For any item that is
- * {@code null}, the type is taken from {@code parameterTypes} instead.
- */
- public static Method findMethodBestMatch(Class> clazz, String methodName, Class>[] parameterTypes, Object[] args) {
- Class>[] argsClasses = null;
- for (int i = 0; i < parameterTypes.length; i++) {
- if (parameterTypes[i] != null)
- continue;
- if (argsClasses == null)
- argsClasses = getParameterTypes(args);
- parameterTypes[i] = argsClasses[i];
- }
- return findMethodBestMatch(clazz, methodName, parameterTypes);
- }
-
- /**
- * Returns an array with the classes of the given objects.
- */
- public static Class>[] getParameterTypes(Object... args) {
- Class>[] clazzes = new Class>[args.length];
- for (int i = 0; i < args.length; i++) {
- clazzes[i] = (args[i] != null) ? args[i].getClass() : null;
- }
- return clazzes;
- }
-
- /**
- * Retrieve classes from an array, where each element might either be a Class
- * already, or a String with the full class name.
- */
- private static Class>[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypesAndCallback) {
- Class>[] parameterClasses = null;
- for (int i = parameterTypesAndCallback.length - 1; i >= 0; i--) {
- Object type = parameterTypesAndCallback[i];
- if (type == null)
- throw new ClassNotFoundError("parameter type must not be null", null);
-
- // ignore trailing callback
- if (type instanceof XC_MethodHook)
- continue;
-
- if (parameterClasses == null)
- parameterClasses = new Class>[i+1];
-
- if (type instanceof Class)
- parameterClasses[i] = (Class>) type;
- else if (type instanceof String)
- parameterClasses[i] = findClass((String) type, classLoader);
- else
- throw new ClassNotFoundError("parameter type must either be specified as Class or String", null);
- }
-
- // if there are no arguments for the method
- if (parameterClasses == null)
- parameterClasses = new Class>[0];
-
- return parameterClasses;
- }
-
- /**
- * Returns an array of the given classes.
- */
- public static Class>[] getClassesAsArray(Class>... clazzes) {
- return clazzes;
- }
-
- private static String getParametersString(Class>... clazzes) {
- StringBuilder sb = new StringBuilder("(");
- boolean first = true;
- for (Class> clazz : clazzes) {
- if (first)
- first = false;
- else
- sb.append(",");
-
- if (clazz != null)
- sb.append(clazz.getCanonicalName());
- else
- sb.append("null");
- }
- sb.append(")");
- return sb.toString();
- }
-
- /**
- * Look up a constructor of a class and set it to accessible.
- * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
- */
- public static Constructor> findConstructorExact(Class> clazz, Object... parameterTypes) {
- return findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypes));
- }
-
- /**
- * Look up and return a constructor if it exists.
- * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
- */
- public static Constructor> findConstructorExactIfExists(Class> clazz, Object... parameterTypes) {
- try {
- return findConstructorExact(clazz, parameterTypes);
- } catch (ClassNotFoundError | NoSuchMethodError e) {
- return null;
- }
- }
-
- /**
- * Look up a constructor of a class and set it to accessible.
- * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
- */
- public static Constructor> findConstructorExact(String className, ClassLoader classLoader, Object... parameterTypes) {
- return findConstructorExact(findClass(className, classLoader), getParameterClasses(classLoader, parameterTypes));
- }
-
- /**
- * Look up and return a constructor if it exists.
- * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
- */
- public static Constructor> findConstructorExactIfExists(String className, ClassLoader classLoader, Object... parameterTypes) {
- try {
- return findConstructorExact(className, classLoader, parameterTypes);
- } catch (ClassNotFoundError | NoSuchMethodError e) {
- return null;
- }
- }
-
- /**
- * Look up a constructor of a class and set it to accessible.
- * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
- */
- public static Constructor> findConstructorExact(Class> clazz, Class>... parameterTypes) {
- String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact";
-
- if (constructorCache.containsKey(fullConstructorName)) {
- Constructor> constructor = constructorCache.get(fullConstructorName);
- if (constructor == null)
- throw new NoSuchMethodError(fullConstructorName);
- return constructor;
- }
-
- try {
- Constructor> constructor = clazz.getDeclaredConstructor(parameterTypes);
- constructor.setAccessible(true);
- constructorCache.put(fullConstructorName, constructor);
- return constructor;
- } catch (NoSuchMethodException e) {
- constructorCache.put(fullConstructorName, null);
- throw new NoSuchMethodError(fullConstructorName);
- }
- }
-
- /**
- * Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
- * for details.
- */
- public static XC_MethodHook.Unhook findAndHookConstructor(Class> clazz, Object... parameterTypesAndCallback) {
- if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
- throw new IllegalArgumentException("no callback defined");
-
- XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
- Constructor> m = findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
-
- return XposedBridge.hookMethod(m, callback);
- }
-
- /**
- * Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
- * for details.
- */
- public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) {
- return findAndHookConstructor(findClass(className, classLoader), parameterTypesAndCallback);
- }
-
- /**
- * Look up a constructor in a class and set it to accessible.
- *
- *
See {@link #findMethodBestMatch(Class, String, Class...)} for details.
- */
- public static Constructor> findConstructorBestMatch(Class> clazz, Class>... parameterTypes) {
- String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#bestmatch";
-
- if (constructorCache.containsKey(fullConstructorName)) {
- Constructor> constructor = constructorCache.get(fullConstructorName);
- if (constructor == null)
- throw new NoSuchMethodError(fullConstructorName);
- return constructor;
- }
-
- try {
- Constructor> constructor = findConstructorExact(clazz, parameterTypes);
- constructorCache.put(fullConstructorName, constructor);
- return constructor;
- } catch (NoSuchMethodError ignored) {}
-
- Constructor> bestMatch = null;
- Constructor>[] constructors = clazz.getDeclaredConstructors();
- for (Constructor> constructor : constructors) {
- // compare name and parameters
- if (ClassUtils.isAssignable(parameterTypes, constructor.getParameterTypes(), true)) {
- // get accessible version of method
- if (bestMatch == null || MemberUtilsX.compareConstructorFit(
- constructor,
- bestMatch,
- parameterTypes) < 0) {
- bestMatch = constructor;
- }
- }
- }
-
- if (bestMatch != null) {
- bestMatch.setAccessible(true);
- constructorCache.put(fullConstructorName, bestMatch);
- return bestMatch;
- } else {
- NoSuchMethodError e = new NoSuchMethodError(fullConstructorName);
- constructorCache.put(fullConstructorName, null);
- throw e;
- }
- }
-
- /**
- * Look up a constructor in a class and set it to accessible.
- *
- *
See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
- * determines the parameter types from the classes of the given objects.
- */
- public static Constructor> findConstructorBestMatch(Class> clazz, Object... args) {
- return findConstructorBestMatch(clazz, getParameterTypes(args));
- }
-
- /**
- * Look up a constructor in a class and set it to accessible.
- *
- *
See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
- * determines the parameter types from the classes of the given objects. For any item that is
- * {@code null}, the type is taken from {@code parameterTypes} instead.
- */
- public static Constructor> findConstructorBestMatch(Class> clazz, Class>[] parameterTypes, Object[] args) {
- Class>[] argsClasses = null;
- for (int i = 0; i < parameterTypes.length; i++) {
- if (parameterTypes[i] != null)
- continue;
- if (argsClasses == null)
- argsClasses = getParameterTypes(args);
- parameterTypes[i] = argsClasses[i];
- }
- return findConstructorBestMatch(clazz, parameterTypes);
- }
-
- /**
- * Thrown when a class loader is unable to find a class. Unlike {@link ClassNotFoundException},
- * callers are not forced to explicitly catch this. If uncaught, the error will be passed to the
- * next caller in the stack.
- */
- public static final class ClassNotFoundError extends Error {
- private static final long serialVersionUID = -1070936889459514628L;
-
- /** @hide */
- public ClassNotFoundError(Throwable cause) {
- super(cause);
- }
-
- /** @hide */
- public ClassNotFoundError(String detailMessage, Throwable cause) {
- super(detailMessage, cause);
- }
- }
-
- /**
- * Returns the index of the first parameter declared with the given type.
- *
- * @throws NoSuchFieldError if there is no parameter with that type.
- * @hide
- */
- public static int getFirstParameterIndexByType(Member method, Class> type) {
- Class>[] classes = (method instanceof Method) ?
- ((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
- for (int i = 0 ; i < classes.length; i++) {
- if (classes[i] == type) {
- return i;
- }
- }
- throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
- }
-
- /**
- * Returns the index of the parameter declared with the given type, ensuring that there is exactly one such parameter.
- *
- * @throws NoSuchFieldError if there is no or more than one parameter with that type.
- * @hide
- */
- public static int getParameterIndexByType(Member method, Class> type) {
- Class>[] classes = (method instanceof Method) ?
- ((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
- int idx = -1;
- for (int i = 0 ; i < classes.length; i++) {
- if (classes[i] == type) {
- if (idx == -1) {
- idx = i;
- } else {
- throw new NoSuchFieldError("More than one parameter of type " + type + " found in " + method);
- }
- }
- }
- if (idx != -1) {
- return idx;
- } else {
- throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
- }
- }
-
- //#################################################################################################
- /** Sets the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setObjectField(Object obj, String fieldName, Object value) {
- try {
- findField(obj.getClass(), fieldName).set(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setBooleanField(Object obj, String fieldName, boolean value) {
- try {
- findField(obj.getClass(), fieldName).setBoolean(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setByteField(Object obj, String fieldName, byte value) {
- try {
- findField(obj.getClass(), fieldName).setByte(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setCharField(Object obj, String fieldName, char value) {
- try {
- findField(obj.getClass(), fieldName).setChar(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setDoubleField(Object obj, String fieldName, double value) {
- try {
- findField(obj.getClass(), fieldName).setDouble(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setFloatField(Object obj, String fieldName, float value) {
- try {
- findField(obj.getClass(), fieldName).setFloat(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setIntField(Object obj, String fieldName, int value) {
- try {
- findField(obj.getClass(), fieldName).setInt(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setLongField(Object obj, String fieldName, long value) {
- try {
- findField(obj.getClass(), fieldName).setLong(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static void setShortField(Object obj, String fieldName, short value) {
- try {
- findField(obj.getClass(), fieldName).setShort(obj, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- //#################################################################################################
- /** Returns the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static Object getObjectField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).get(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** For inner classes, returns the surrounding instance, i.e. the {@code this} reference of the surrounding class. */
- public static Object getSurroundingThis(Object obj) {
- return getObjectField(obj, "this$0");
- }
-
- /** Returns the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- public static boolean getBooleanField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).getBoolean(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Returns the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static byte getByteField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).getByte(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Returns the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static char getCharField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).getChar(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Returns the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static double getDoubleField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).getDouble(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Returns the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static float getFloatField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).getFloat(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Returns the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static int getIntField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).getInt(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Returns the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static long getLongField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).getLong(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Returns the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
- public static short getShortField(Object obj, String fieldName) {
- try {
- return findField(obj.getClass(), fieldName).getShort(obj);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- //#################################################################################################
- /** Sets the value of a static object field in the given class. See also {@link #findField}. */
- public static void setStaticObjectField(Class> clazz, String fieldName, Object value) {
- try {
- findField(clazz, fieldName).set(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code boolean} field in the given class. See also {@link #findField}. */
- public static void setStaticBooleanField(Class> clazz, String fieldName, boolean value) {
- try {
- findField(clazz, fieldName).setBoolean(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code byte} field in the given class. See also {@link #findField}. */
- public static void setStaticByteField(Class> clazz, String fieldName, byte value) {
- try {
- findField(clazz, fieldName).setByte(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code char} field in the given class. See also {@link #findField}. */
- public static void setStaticCharField(Class> clazz, String fieldName, char value) {
- try {
- findField(clazz, fieldName).setChar(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code double} field in the given class. See also {@link #findField}. */
- public static void setStaticDoubleField(Class> clazz, String fieldName, double value) {
- try {
- findField(clazz, fieldName).setDouble(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code float} field in the given class. See also {@link #findField}. */
- public static void setStaticFloatField(Class> clazz, String fieldName, float value) {
- try {
- findField(clazz, fieldName).setFloat(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code int} field in the given class. See also {@link #findField}. */
- public static void setStaticIntField(Class> clazz, String fieldName, int value) {
- try {
- findField(clazz, fieldName).setInt(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code long} field in the given class. See also {@link #findField}. */
- public static void setStaticLongField(Class> clazz, String fieldName, long value) {
- try {
- findField(clazz, fieldName).setLong(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code short} field in the given class. See also {@link #findField}. */
- public static void setStaticShortField(Class> clazz, String fieldName, short value) {
- try {
- findField(clazz, fieldName).setShort(null, value);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- //#################################################################################################
- /** Returns the value of a static object field in the given class. See also {@link #findField}. */
- public static Object getStaticObjectField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).get(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Returns the value of a static {@code boolean} field in the given class. See also {@link #findField}. */
- public static boolean getStaticBooleanField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).getBoolean(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code byte} field in the given class. See also {@link #findField}. */
- public static byte getStaticByteField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).getByte(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code char} field in the given class. See also {@link #findField}. */
- public static char getStaticCharField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).getChar(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code double} field in the given class. See also {@link #findField}. */
- public static double getStaticDoubleField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).getDouble(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code float} field in the given class. See also {@link #findField}. */
- public static float getStaticFloatField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).getFloat(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code int} field in the given class. See also {@link #findField}. */
- public static int getStaticIntField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).getInt(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code long} field in the given class. See also {@link #findField}. */
- public static long getStaticLongField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).getLong(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- /** Sets the value of a static {@code short} field in the given class. See also {@link #findField}. */
- public static short getStaticShortField(Class> clazz, String fieldName) {
- try {
- return findField(clazz, fieldName).getShort(null);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- }
- }
-
- //#################################################################################################
- /**
- * Calls an instance or static method of the given object.
- * The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
- *
- * @param obj The object instance. A class reference is not sufficient!
- * @param methodName The method name.
- * @param args The arguments for the method call.
- * @throws NoSuchMethodError In case no suitable method was found.
- * @throws InvocationTargetError In case an exception was thrown by the invoked method.
- */
- public static Object callMethod(Object obj, String methodName, Object... args) {
- try {
- return findMethodBestMatch(obj.getClass(), methodName, args).invoke(obj, args);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw new InvocationTargetError(e.getCause());
- }
- }
-
- /**
- * Calls an instance or static method of the given object.
- * See {@link #callMethod(Object, String, Object...)}.
- *
- *
This variant allows you to specify parameter types, which can help in case there are multiple
- * methods with the same name, especially if you call it with {@code null} parameters.
- */
- public static Object callMethod(Object obj, String methodName, Class>[] parameterTypes, Object... args) {
- try {
- return findMethodBestMatch(obj.getClass(), methodName, parameterTypes, args).invoke(obj, args);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw new InvocationTargetError(e.getCause());
- }
- }
-
- /**
- * Calls a static method of the given class.
- * The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
- *
- * @param clazz The class reference.
- * @param methodName The method name.
- * @param args The arguments for the method call.
- * @throws NoSuchMethodError In case no suitable method was found.
- * @throws InvocationTargetError In case an exception was thrown by the invoked method.
- */
- public static Object callStaticMethod(Class> clazz, String methodName, Object... args) {
- try {
- return findMethodBestMatch(clazz, methodName, args).invoke(null, args);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw new InvocationTargetError(e.getCause());
- }
- }
-
- /**
- * Calls a static method of the given class.
- * See {@link #callStaticMethod(Class, String, Object...)}.
- *
- *
This variant allows you to specify parameter types, which can help in case there are multiple
- * methods with the same name, especially if you call it with {@code null} parameters.
- */
- public static Object callStaticMethod(Class> clazz, String methodName, Class>[] parameterTypes, Object... args) {
- try {
- return findMethodBestMatch(clazz, methodName, parameterTypes, args).invoke(null, args);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw new InvocationTargetError(e.getCause());
- }
- }
-
- /**
- * This class provides a wrapper for an exception thrown by a method invocation.
- *
- * @see #callMethod(Object, String, Object...)
- * @see #callStaticMethod(Class, String, Object...)
- * @see #newInstance(Class, Object...)
- */
- public static final class InvocationTargetError extends Error {
- private static final long serialVersionUID = -1070936889459514628L;
-
- /** @hide */
- public InvocationTargetError(Throwable cause) {
- super(cause);
- }
- }
-
- //#################################################################################################
- /**
- * Creates a new instance of the given class.
- * The constructor is resolved using {@link #findConstructorBestMatch(Class, Object...)}.
- *
- * @param clazz The class reference.
- * @param args The arguments for the constructor call.
- * @throws NoSuchMethodError In case no suitable constructor was found.
- * @throws InvocationTargetError In case an exception was thrown by the invoked method.
- * @throws InstantiationError In case the class cannot be instantiated.
- */
- public static Object newInstance(Class> clazz, Object... args) {
- try {
- return findConstructorBestMatch(clazz, args).newInstance(args);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw new InvocationTargetError(e.getCause());
- } catch (InstantiationException e) {
- throw new InstantiationError(e.getMessage());
- }
- }
-
- /**
- * Creates a new instance of the given class.
- * See {@link #newInstance(Class, Object...)}.
- *
- *
This variant allows you to specify parameter types, which can help in case there are multiple
- * constructors with the same name, especially if you call it with {@code null} parameters.
- */
- public static Object newInstance(Class> clazz, Class>[] parameterTypes, Object... args) {
- try {
- return findConstructorBestMatch(clazz, parameterTypes, args).newInstance(args);
- } catch (IllegalAccessException e) {
- // should not happen
- XposedBridge.log(e);
- throw new IllegalAccessError(e.getMessage());
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (InvocationTargetException e) {
- throw new InvocationTargetError(e.getCause());
- } catch (InstantiationException e) {
- throw new InstantiationError(e.getMessage());
- }
- }
-
- //#################################################################################################
-
- /**
- * Attaches any value to an object instance. This simulates adding an instance field.
- * The value can be retrieved again with {@link #getAdditionalInstanceField}.
- *
- * @param obj The object instance for which the value should be stored.
- * @param key The key in the value map for this object instance.
- * @param value The value to store.
- * @return The previously stored value for this instance/key combination, or {@code null} if there was none.
- */
- public static Object setAdditionalInstanceField(Object obj, String key, Object value) {
- if (obj == null)
- throw new NullPointerException("object must not be null");
- if (key == null)
- throw new NullPointerException("key must not be null");
-
- HashMap objectFields;
- synchronized (additionalFields) {
- objectFields = additionalFields.get(obj);
- if (objectFields == null) {
- objectFields = new HashMap<>();
- additionalFields.put(obj, objectFields);
- }
- }
-
- synchronized (objectFields) {
- return objectFields.put(key, value);
- }
- }
-
- /**
- * Returns a value which was stored with {@link #setAdditionalInstanceField}.
- *
- * @param obj The object instance for which the value has been stored.
- * @param key The key in the value map for this object instance.
- * @return The stored value for this instance/key combination, or {@code null} if there is none.
- */
- public static Object getAdditionalInstanceField(Object obj, String key) {
- if (obj == null)
- throw new NullPointerException("object must not be null");
- if (key == null)
- throw new NullPointerException("key must not be null");
-
- HashMap objectFields;
- synchronized (additionalFields) {
- objectFields = additionalFields.get(obj);
- if (objectFields == null)
- return null;
- }
-
- synchronized (objectFields) {
- return objectFields.get(key);
- }
- }
-
- /**
- * Removes and returns a value which was stored with {@link #setAdditionalInstanceField}.
- *
- * @param obj The object instance for which the value has been stored.
- * @param key The key in the value map for this object instance.
- * @return The previously stored value for this instance/key combination, or {@code null} if there was none.
- */
- public static Object removeAdditionalInstanceField(Object obj, String key) {
- if (obj == null)
- throw new NullPointerException("object must not be null");
- if (key == null)
- throw new NullPointerException("key must not be null");
-
- HashMap objectFields;
- synchronized (additionalFields) {
- objectFields = additionalFields.get(obj);
- if (objectFields == null)
- return null;
- }
-
- synchronized (objectFields) {
- return objectFields.remove(key);
- }
- }
-
- /** Like {@link #setAdditionalInstanceField}, but the value is stored for the class of {@code obj}. */
- public static Object setAdditionalStaticField(Object obj, String key, Object value) {
- return setAdditionalInstanceField(obj.getClass(), key, value);
- }
-
- /** Like {@link #getAdditionalInstanceField}, but the value is returned for the class of {@code obj}. */
- public static Object getAdditionalStaticField(Object obj, String key) {
- return getAdditionalInstanceField(obj.getClass(), key);
- }
-
- /** Like {@link #removeAdditionalInstanceField}, but the value is removed and returned for the class of {@code obj}. */
- public static Object removeAdditionalStaticField(Object obj, String key) {
- return removeAdditionalInstanceField(obj.getClass(), key);
- }
-
- /** Like {@link #setAdditionalInstanceField}, but the value is stored for {@code clazz}. */
- public static Object setAdditionalStaticField(Class> clazz, String key, Object value) {
- return setAdditionalInstanceField(clazz, key, value);
- }
-
- /** Like {@link #setAdditionalInstanceField}, but the value is returned for {@code clazz}. */
- public static Object getAdditionalStaticField(Class> clazz, String key) {
- return getAdditionalInstanceField(clazz, key);
- }
-
- /** Like {@link #setAdditionalInstanceField}, but the value is removed and returned for {@code clazz}. */
- public static Object removeAdditionalStaticField(Class> clazz, String key) {
- return removeAdditionalInstanceField(clazz, key);
- }
-
- //#################################################################################################
- /**
- * Loads an asset from a resource object and returns the content as {@code byte} array.
- *
- * @param res The resources from which the asset should be loaded.
- * @param path The path to the asset, as in {@link AssetManager#open}.
- * @return The content of the asset.
- */
- public static byte[] assetAsByteArray(Resources res, String path) throws IOException {
- return inputStreamToByteArray(res.getAssets().open(path));
- }
-
- /*package*/ static byte[] inputStreamToByteArray(InputStream is) throws IOException {
- ByteArrayOutputStream buf = new ByteArrayOutputStream();
- byte[] temp = new byte[1024];
- int read;
-
- while ((read = is.read(temp)) > 0) {
- buf.write(temp, 0, read);
- }
- is.close();
- return buf.toByteArray();
- }
-
- /**
- * Invokes the {@link Closeable#close()} method, ignoring IOExceptions.
- */
- /*package*/ static void closeSilently(Closeable c) {
- if (c != null) {
- try {
- c.close();
- } catch (IOException ignored) {}
- }
- }
-
- /**
- * Returns the lowercase hex string representation of a file's MD5 hash sum.
- */
- public static String getMD5Sum(String file) throws IOException {
- try {
- MessageDigest digest = MessageDigest.getInstance("MD5");
- InputStream is = new FileInputStream(file);
- byte[] buffer = new byte[8192];
- int read;
- while ((read = is.read(buffer)) > 0) {
- digest.update(buffer, 0, read);
- }
- is.close();
- byte[] md5sum = digest.digest();
- BigInteger bigInt = new BigInteger(1, md5sum);
- return bigInt.toString(16);
- } catch (NoSuchAlgorithmException e) {
- return "";
- }
- }
-
- //#################################################################################################
- /**
- * Increments the depth counter for the given method.
- *
- * The intention of the method depth counter is to keep track of the call depth for recursive
- * methods, e.g. to override parameters only for the outer call. The Xposed framework uses this
- * to load drawable replacements only once per call, even when multiple
- * {@link Resources#getDrawable} variants call each other.
- *
- * @param method The method name. Should be prefixed with a unique, module-specific string.
- * @return The updated depth.
- */
- public static int incrementMethodDepth(String method) {
- return getMethodDepthCounter(method).get().incrementAndGet();
- }
-
- /**
- * Decrements the depth counter for the given method.
- * See {@link #incrementMethodDepth} for details.
- *
- * @param method The method name. Should be prefixed with a unique, module-specific string.
- * @return The updated depth.
- */
- public static int decrementMethodDepth(String method) {
- return getMethodDepthCounter(method).get().decrementAndGet();
- }
-
- /**
- * Returns the current depth counter for the given method.
- * See {@link #incrementMethodDepth} for details.
- *
- * @param method The method name. Should be prefixed with a unique, module-specific string.
- * @return The updated depth.
- */
- public static int getMethodDepth(String method) {
- return getMethodDepthCounter(method).get().get();
- }
-
- private static ThreadLocal getMethodDepthCounter(String method) {
- synchronized (sMethodDepth) {
- ThreadLocal counter = sMethodDepth.get(method);
- if (counter == null) {
- counter = new ThreadLocal() {
- @Override
- protected AtomicInteger initialValue() {
- return new AtomicInteger();
- }
- };
- sMethodDepth.put(method, counter);
- }
- return counter;
- }
- }
-
- //#################################################################################################
- // TODO helpers for view traversing
+ private XposedHelpers() {
+ }
+
+ private static final HashMap fieldCache = new HashMap<>();
+ private static final HashMap methodCache = new HashMap<>();
+ private static final HashMap> constructorCache = new HashMap<>();
+ private static final WeakHashMap> additionalFields = new WeakHashMap<>();
+ private static final HashMap> sMethodDepth = new HashMap<>();
+
+ /**
+ * Look up a class with the specified class loader.
+ *
+ * There are various allowed syntaxes for the class name, but it's recommended to use one of
+ * these:
+ *
+ * {@code java.lang.String}
+ * {@code java.lang.String[]} (array)
+ * {@code android.app.ActivityThread.ResourcesKey}
+ * {@code android.app.ActivityThread$ResourcesKey}
+ *
+ *
+ * @param className The class name in one of the formats mentioned above.
+ * @param classLoader The class loader, or {@code null} for the boot class loader.
+ * @return A reference to the class.
+ * @throws ClassNotFoundError In case the class was not found.
+ */
+ public static Class> findClass(String className, ClassLoader classLoader) {
+ if (classLoader == null)
+ classLoader = XposedBridge.BOOTCLASSLOADER;
+ try {
+ return ClassUtils.getClass(classLoader, className, false);
+ } catch (ClassNotFoundException e) {
+ throw new ClassNotFoundError(e);
+ }
+ }
+
+ /**
+ * Look up and return a class if it exists.
+ * Like {@link #findClass}, but doesn't throw an exception if the class doesn't exist.
+ *
+ * @param className The class name.
+ * @param classLoader The class loader, or {@code null} for the boot class loader.
+ * @return A reference to the class, or {@code null} if it doesn't exist.
+ */
+ public static Class> findClassIfExists(String className, ClassLoader classLoader) {
+ try {
+ return findClass(className, classLoader);
+ } catch (ClassNotFoundError e) {
+ return null;
+ }
+ }
+
+ /**
+ * Look up a field in a class and set it to accessible.
+ *
+ * @param clazz The class which either declares or inherits the field.
+ * @param fieldName The field name.
+ * @return A reference to the field.
+ * @throws NoSuchFieldError In case the field was not found.
+ */
+ public static Field findField(Class> clazz, String fieldName) {
+ String fullFieldName = clazz.getName() + '#' + fieldName;
+
+ if (fieldCache.containsKey(fullFieldName)) {
+ Field field = fieldCache.get(fullFieldName);
+ if (field == null)
+ throw new NoSuchFieldError(fullFieldName);
+ return field;
+ }
+
+ try {
+ Field field = findFieldRecursiveImpl(clazz, fieldName);
+ field.setAccessible(true);
+ fieldCache.put(fullFieldName, field);
+ return field;
+ } catch (NoSuchFieldException e) {
+ fieldCache.put(fullFieldName, null);
+ throw new NoSuchFieldError(fullFieldName);
+ }
+ }
+
+ /**
+ * Look up and return a field if it exists.
+ * Like {@link #findField}, but doesn't throw an exception if the field doesn't exist.
+ *
+ * @param clazz The class which either declares or inherits the field.
+ * @param fieldName The field name.
+ * @return A reference to the field, or {@code null} if it doesn't exist.
+ */
+ public static Field findFieldIfExists(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName);
+ } catch (NoSuchFieldError e) {
+ return null;
+ }
+ }
+
+ private static Field findFieldRecursiveImpl(Class> clazz, String fieldName) throws NoSuchFieldException {
+ try {
+ return clazz.getDeclaredField(fieldName);
+ } catch (NoSuchFieldException e) {
+ while (true) {
+ clazz = clazz.getSuperclass();
+ if (clazz == null || clazz.equals(Object.class))
+ break;
+
+ try {
+ return clazz.getDeclaredField(fieldName);
+ } catch (NoSuchFieldException ignored) {
+ }
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the first field of the given type in a class.
+ * Might be useful for Proguard'ed classes to identify fields with unique types.
+ *
+ * @param clazz The class which either declares or inherits the field.
+ * @param type The type of the field.
+ * @return A reference to the first field of the given type.
+ * @throws NoSuchFieldError In case no matching field was not found.
+ */
+ public static Field findFirstFieldByExactType(Class> clazz, Class> type) {
+ Class> clz = clazz;
+ do {
+ for (Field field : clz.getDeclaredFields()) {
+ if (field.getType() == type) {
+ field.setAccessible(true);
+ return field;
+ }
+ }
+ } while ((clz = clz.getSuperclass()) != null);
+
+ throw new NoSuchFieldError("Field of type " + type.getName() + " in class " + clazz.getName());
+ }
+
+ /**
+ * Look up a method and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
+ * for details.
+ */
+ public static XC_MethodHook.Unhook findAndHookMethod(Class> clazz, String methodName, Object... parameterTypesAndCallback) {
+ if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook))
+ throw new IllegalArgumentException("no callback defined");
+
+ XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length - 1];
+ Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
+
+ return XposedBridge.hookMethod(m, callback);
+ }
+
+ /**
+ * Look up a method and hook it. The last argument must be the callback for the hook.
+ *
+ * This combines calls to {@link #findMethodExact(Class, String, Object...)} and
+ * {@link XposedBridge#hookMethod}.
+ *
+ *
The method must be declared or overridden in the given class, inherited
+ * methods are not considered! That's because each method implementation exists only once in
+ * the memory, and when classes inherit it, they just get another reference to the implementation.
+ * Hooking a method therefore applies to all classes inheriting the same implementation. You
+ * have to expect that the hook applies to subclasses (unless they override the method), but you
+ * shouldn't have to worry about hooks applying to superclasses, hence this "limitation".
+ * There could be undesired or even dangerous hooks otherwise, e.g. if you hook
+ * {@code SomeClass.equals()} and that class doesn't override the {@code equals()} on some ROMs,
+ * making you hook {@code Object.equals()} instead.
+ *
+ *
There are two ways to specify the parameter types. If you already have a reference to the
+ * {@link Class}, use that. For Android framework classes, you can often use something like
+ * {@code String.class}. If you don't have the class reference, you can simply use the
+ * full class name as a string, e.g. {@code java.lang.String} or {@code com.example.MyClass}.
+ * It will be passed to {@link #findClass} with the same class loader that is used for the target
+ * method, see its documentation for the allowed notations.
+ *
+ *
Primitive types, such as {@code int}, can be specified using {@code int.class} (recommended)
+ * or {@code Integer.TYPE}. Note that {@code Integer.class} doesn't refer to {@code int} but to
+ * {@code Integer}, which is a normal class (boxed primitive). Therefore it must not be used when
+ * the method expects an {@code int} parameter - it has to be used for {@code Integer} parameters
+ * though, so check the method signature in detail.
+ *
+ *
As last argument to this method (after the list of target method parameters), you need
+ * to specify the callback that should be executed when the method is invoked. It's usually
+ * an anonymous subclass of {@link XC_MethodHook} or {@link XC_MethodReplacement}.
+ *
+ *
Example
+ *
+ * // In order to hook this method ...
+ * package com.example;
+ * public class SomeClass {
+ * public int doSomething(String s, int i, MyClass m) {
+ * ...
+ * }
+ * }
+ *
+ * // ... you can use this call:
+ * findAndHookMethod("com.example.SomeClass", lpparam.classLoader, String.class, int.class, "com.example.MyClass", new XC_MethodHook() {
+ * @Override
+ * protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
+ * String oldText = (String) param.args[0];
+ * Log.d("MyModule", oldText);
+ *
+ * param.args[0] = "test";
+ * param.args[1] = 42; // auto-boxing is working here
+ * setBooleanField(param.args[2], "great", true);
+ *
+ * // This would not work (as MyClass can't be resolved at compile time):
+ * // MyClass myClass = (MyClass) param.args[2];
+ * // myClass.great = true;
+ * }
+ * });
+ *
+ *
+ * @param className The name of the class which implements the method.
+ * @param classLoader The class loader for resolving the target and parameter classes.
+ * @param methodName The target method name.
+ * @param parameterTypesAndCallback The parameter types of the target method, plus the callback.
+ * @return An object which can be used to remove the callback again.
+ * @throws NoSuchMethodError In case the method was not found.
+ * @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
+ */
+ public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
+ return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
+ }
+
+ /**
+ * Look up a method in a class and set it to accessible.
+ * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
+ */
+ public static Method findMethodExact(Class> clazz, String methodName, Object... parameterTypes) {
+ return findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypes));
+ }
+
+ /**
+ * Look up and return a method if it exists.
+ * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
+ */
+ public static Method findMethodExactIfExists(Class> clazz, String methodName, Object... parameterTypes) {
+ try {
+ return findMethodExact(clazz, methodName, parameterTypes);
+ } catch (ClassNotFoundError | NoSuchMethodError e) {
+ return null;
+ }
+ }
+
+ /**
+ * Look up a method in a class and set it to accessible.
+ * The method must be declared or overridden in the given class.
+ *
+ * See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} for details about
+ * the method and parameter type resolution.
+ *
+ * @param className The name of the class which implements the method.
+ * @param classLoader The class loader for resolving the target and parameter classes.
+ * @param methodName The target method name.
+ * @param parameterTypes The parameter types of the target method.
+ * @return A reference to the method.
+ * @throws NoSuchMethodError In case the method was not found.
+ * @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
+ */
+ public static Method findMethodExact(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
+ return findMethodExact(findClass(className, classLoader), methodName, getParameterClasses(classLoader, parameterTypes));
+ }
+
+ /**
+ * Look up and return a method if it exists.
+ * Like {@link #findMethodExact(String, ClassLoader, String, Object...)}, but doesn't throw an
+ * exception if the method doesn't exist.
+ *
+ * @param className The name of the class which implements the method.
+ * @param classLoader The class loader for resolving the target and parameter classes.
+ * @param methodName The target method name.
+ * @param parameterTypes The parameter types of the target method.
+ * @return A reference to the method, or {@code null} if it doesn't exist.
+ */
+ public static Method findMethodExactIfExists(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
+ try {
+ return findMethodExact(className, classLoader, methodName, parameterTypes);
+ } catch (ClassNotFoundError | NoSuchMethodError e) {
+ return null;
+ }
+ }
+
+ /**
+ * Look up a method in a class and set it to accessible.
+ * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
+ *
+ *
This variant requires that you already have reference to all the parameter types.
+ */
+ public static Method findMethodExact(Class> clazz, String methodName, Class>... parameterTypes) {
+ String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact";
+
+ if (methodCache.containsKey(fullMethodName)) {
+ Method method = methodCache.get(fullMethodName);
+ if (method == null)
+ throw new NoSuchMethodError(fullMethodName);
+ return method;
+ }
+
+ try {
+ Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
+ method.setAccessible(true);
+ methodCache.put(fullMethodName, method);
+ return method;
+ } catch (NoSuchMethodException e) {
+ methodCache.put(fullMethodName, null);
+ throw new NoSuchMethodError(fullMethodName);
+ }
+ }
+
+ /**
+ * Returns an array of all methods declared/overridden in a class with the specified parameter types.
+ *
+ *
The return type is optional, it will not be compared if it is {@code null}.
+ * Use {@code void.class} if you want to search for methods returning nothing.
+ *
+ * @param clazz The class to look in.
+ * @param returnType The return type, or {@code null} (see above).
+ * @param parameterTypes The parameter types.
+ * @return An array with matching methods, all set to accessible already.
+ */
+ public static Method[] findMethodsByExactParameters(Class> clazz, Class> returnType, Class>... parameterTypes) {
+ List result = new LinkedList<>();
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (returnType != null && returnType != method.getReturnType())
+ continue;
+
+ Class>[] methodParameterTypes = method.getParameterTypes();
+ if (parameterTypes.length != methodParameterTypes.length)
+ continue;
+
+ boolean match = true;
+ for (int i = 0; i < parameterTypes.length; i++) {
+ if (parameterTypes[i] != methodParameterTypes[i]) {
+ match = false;
+ break;
+ }
+ }
+
+ if (!match)
+ continue;
+
+ method.setAccessible(true);
+ result.add(method);
+ }
+ return result.toArray(new Method[result.size()]);
+ }
+
+ /**
+ * Look up a method in a class and set it to accessible.
+ *
+ * This does'nt only look for exact matches, but for the best match. All considered candidates
+ * must be compatible with the given parameter types, i.e. the parameters must be assignable
+ * to the method's formal parameters. Inherited methods are considered here.
+ *
+ * @param clazz The class which declares, inherits or overrides the method.
+ * @param methodName The method name.
+ * @param parameterTypes The types of the method's parameters.
+ * @return A reference to the best-matching method.
+ * @throws NoSuchMethodError In case no suitable method was found.
+ */
+ public static Method findMethodBestMatch(Class> clazz, String methodName, Class>... parameterTypes) {
+ String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#bestmatch";
+
+ if (methodCache.containsKey(fullMethodName)) {
+ Method method = methodCache.get(fullMethodName);
+ if (method == null)
+ throw new NoSuchMethodError(fullMethodName);
+ return method;
+ }
+
+ try {
+ Method method = findMethodExact(clazz, methodName, parameterTypes);
+ methodCache.put(fullMethodName, method);
+ return method;
+ } catch (NoSuchMethodError ignored) {
+ }
+
+ Method bestMatch = null;
+ Class> clz = clazz;
+ boolean considerPrivateMethods = true;
+ do {
+ for (Method method : clz.getDeclaredMethods()) {
+ // don't consider private methods of superclasses
+ if (!considerPrivateMethods && Modifier.isPrivate(method.getModifiers()))
+ continue;
+
+ // compare name and parameters
+ if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
+ // get accessible version of method
+ if (bestMatch == null || MemberUtilsX.compareMethodFit(
+ method,
+ bestMatch,
+ parameterTypes) < 0) {
+ bestMatch = method;
+ }
+ }
+ }
+ considerPrivateMethods = false;
+ } while ((clz = clz.getSuperclass()) != null);
+
+ if (bestMatch != null) {
+ bestMatch.setAccessible(true);
+ methodCache.put(fullMethodName, bestMatch);
+ return bestMatch;
+ } else {
+ NoSuchMethodError e = new NoSuchMethodError(fullMethodName);
+ methodCache.put(fullMethodName, null);
+ throw e;
+ }
+ }
+
+ /**
+ * Look up a method in a class and set it to accessible.
+ *
+ *
See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
+ * determines the parameter types from the classes of the given objects.
+ */
+ public static Method findMethodBestMatch(Class> clazz, String methodName, Object... args) {
+ return findMethodBestMatch(clazz, methodName, getParameterTypes(args));
+ }
+
+ /**
+ * Look up a method in a class and set it to accessible.
+ *
+ *
See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
+ * determines the parameter types from the classes of the given objects. For any item that is
+ * {@code null}, the type is taken from {@code parameterTypes} instead.
+ */
+ public static Method findMethodBestMatch(Class> clazz, String methodName, Class>[] parameterTypes, Object[] args) {
+ Class>[] argsClasses = null;
+ for (int i = 0; i < parameterTypes.length; i++) {
+ if (parameterTypes[i] != null)
+ continue;
+ if (argsClasses == null)
+ argsClasses = getParameterTypes(args);
+ parameterTypes[i] = argsClasses[i];
+ }
+ return findMethodBestMatch(clazz, methodName, parameterTypes);
+ }
+
+ /**
+ * Returns an array with the classes of the given objects.
+ */
+ public static Class>[] getParameterTypes(Object... args) {
+ Class>[] clazzes = new Class>[args.length];
+ for (int i = 0; i < args.length; i++) {
+ clazzes[i] = (args[i] != null) ? args[i].getClass() : null;
+ }
+ return clazzes;
+ }
+
+ /**
+ * Retrieve classes from an array, where each element might either be a Class
+ * already, or a String with the full class name.
+ */
+ private static Class>[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypesAndCallback) {
+ Class>[] parameterClasses = null;
+ for (int i = parameterTypesAndCallback.length - 1; i >= 0; i--) {
+ Object type = parameterTypesAndCallback[i];
+ if (type == null)
+ throw new ClassNotFoundError("parameter type must not be null", null);
+
+ // ignore trailing callback
+ if (type instanceof XC_MethodHook)
+ continue;
+
+ if (parameterClasses == null)
+ parameterClasses = new Class>[i + 1];
+
+ if (type instanceof Class)
+ parameterClasses[i] = (Class>) type;
+ else if (type instanceof String)
+ parameterClasses[i] = findClass((String) type, classLoader);
+ else
+ throw new ClassNotFoundError("parameter type must either be specified as Class or String", null);
+ }
+
+ // if there are no arguments for the method
+ if (parameterClasses == null)
+ parameterClasses = new Class>[0];
+
+ return parameterClasses;
+ }
+
+ /**
+ * Returns an array of the given classes.
+ */
+ public static Class>[] getClassesAsArray(Class>... clazzes) {
+ return clazzes;
+ }
+
+ private static String getParametersString(Class>... clazzes) {
+ StringBuilder sb = new StringBuilder("(");
+ boolean first = true;
+ for (Class> clazz : clazzes) {
+ if (first)
+ first = false;
+ else
+ sb.append(",");
+
+ if (clazz != null)
+ sb.append(clazz.getCanonicalName());
+ else
+ sb.append("null");
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ /**
+ * Look up a constructor of a class and set it to accessible.
+ * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
+ */
+ public static Constructor> findConstructorExact(Class> clazz, Object... parameterTypes) {
+ return findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypes));
+ }
+
+ /**
+ * Look up and return a constructor if it exists.
+ * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
+ */
+ public static Constructor> findConstructorExactIfExists(Class> clazz, Object... parameterTypes) {
+ try {
+ return findConstructorExact(clazz, parameterTypes);
+ } catch (ClassNotFoundError | NoSuchMethodError e) {
+ return null;
+ }
+ }
+
+ /**
+ * Look up a constructor of a class and set it to accessible.
+ * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
+ */
+ public static Constructor> findConstructorExact(String className, ClassLoader classLoader, Object... parameterTypes) {
+ return findConstructorExact(findClass(className, classLoader), getParameterClasses(classLoader, parameterTypes));
+ }
+
+ /**
+ * Look up and return a constructor if it exists.
+ * See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
+ */
+ public static Constructor> findConstructorExactIfExists(String className, ClassLoader classLoader, Object... parameterTypes) {
+ try {
+ return findConstructorExact(className, classLoader, parameterTypes);
+ } catch (ClassNotFoundError | NoSuchMethodError e) {
+ return null;
+ }
+ }
+
+ /**
+ * Look up a constructor of a class and set it to accessible.
+ * See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
+ */
+ public static Constructor> findConstructorExact(Class> clazz, Class>... parameterTypes) {
+ String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact";
+
+ if (constructorCache.containsKey(fullConstructorName)) {
+ Constructor> constructor = constructorCache.get(fullConstructorName);
+ if (constructor == null)
+ throw new NoSuchMethodError(fullConstructorName);
+ return constructor;
+ }
+
+ try {
+ Constructor> constructor = clazz.getDeclaredConstructor(parameterTypes);
+ constructor.setAccessible(true);
+ constructorCache.put(fullConstructorName, constructor);
+ return constructor;
+ } catch (NoSuchMethodException e) {
+ constructorCache.put(fullConstructorName, null);
+ throw new NoSuchMethodError(fullConstructorName);
+ }
+ }
+
+ /**
+ * Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
+ * for details.
+ */
+ public static XC_MethodHook.Unhook findAndHookConstructor(Class> clazz, Object... parameterTypesAndCallback) {
+ if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook))
+ throw new IllegalArgumentException("no callback defined");
+
+ XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length - 1];
+ Constructor> m = findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
+
+ return XposedBridge.hookMethod(m, callback);
+ }
+
+ /**
+ * Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
+ * for details.
+ */
+ public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) {
+ return findAndHookConstructor(findClass(className, classLoader), parameterTypesAndCallback);
+ }
+
+ /**
+ * Look up a constructor in a class and set it to accessible.
+ *
+ *
See {@link #findMethodBestMatch(Class, String, Class...)} for details.
+ */
+ public static Constructor> findConstructorBestMatch(Class> clazz, Class>... parameterTypes) {
+ String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#bestmatch";
+
+ if (constructorCache.containsKey(fullConstructorName)) {
+ Constructor> constructor = constructorCache.get(fullConstructorName);
+ if (constructor == null)
+ throw new NoSuchMethodError(fullConstructorName);
+ return constructor;
+ }
+
+ try {
+ Constructor> constructor = findConstructorExact(clazz, parameterTypes);
+ constructorCache.put(fullConstructorName, constructor);
+ return constructor;
+ } catch (NoSuchMethodError ignored) {
+ }
+
+ Constructor> bestMatch = null;
+ Constructor>[] constructors = clazz.getDeclaredConstructors();
+ for (Constructor> constructor : constructors) {
+ // compare name and parameters
+ if (ClassUtils.isAssignable(parameterTypes, constructor.getParameterTypes(), true)) {
+ // get accessible version of method
+ if (bestMatch == null || MemberUtilsX.compareConstructorFit(
+ constructor,
+ bestMatch,
+ parameterTypes) < 0) {
+ bestMatch = constructor;
+ }
+ }
+ }
+
+ if (bestMatch != null) {
+ bestMatch.setAccessible(true);
+ constructorCache.put(fullConstructorName, bestMatch);
+ return bestMatch;
+ } else {
+ NoSuchMethodError e = new NoSuchMethodError(fullConstructorName);
+ constructorCache.put(fullConstructorName, null);
+ throw e;
+ }
+ }
+
+ /**
+ * Look up a constructor in a class and set it to accessible.
+ *
+ *
See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
+ * determines the parameter types from the classes of the given objects.
+ */
+ public static Constructor> findConstructorBestMatch(Class> clazz, Object... args) {
+ return findConstructorBestMatch(clazz, getParameterTypes(args));
+ }
+
+ /**
+ * Look up a constructor in a class and set it to accessible.
+ *
+ *
See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
+ * determines the parameter types from the classes of the given objects. For any item that is
+ * {@code null}, the type is taken from {@code parameterTypes} instead.
+ */
+ public static Constructor> findConstructorBestMatch(Class> clazz, Class>[] parameterTypes, Object[] args) {
+ Class>[] argsClasses = null;
+ for (int i = 0; i < parameterTypes.length; i++) {
+ if (parameterTypes[i] != null)
+ continue;
+ if (argsClasses == null)
+ argsClasses = getParameterTypes(args);
+ parameterTypes[i] = argsClasses[i];
+ }
+ return findConstructorBestMatch(clazz, parameterTypes);
+ }
+
+ /**
+ * Thrown when a class loader is unable to find a class. Unlike {@link ClassNotFoundException},
+ * callers are not forced to explicitly catch this. If uncaught, the error will be passed to the
+ * next caller in the stack.
+ */
+ public static final class ClassNotFoundError extends Error {
+ private static final long serialVersionUID = -1070936889459514628L;
+
+ /**
+ * @hide
+ */
+ public ClassNotFoundError(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * @hide
+ */
+ public ClassNotFoundError(String detailMessage, Throwable cause) {
+ super(detailMessage, cause);
+ }
+ }
+
+ /**
+ * Returns the index of the first parameter declared with the given type.
+ *
+ * @throws NoSuchFieldError if there is no parameter with that type.
+ * @hide
+ */
+ public static int getFirstParameterIndexByType(Member method, Class> type) {
+ Class>[] classes = (method instanceof Method) ?
+ ((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
+ for (int i = 0; i < classes.length; i++) {
+ if (classes[i] == type) {
+ return i;
+ }
+ }
+ throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
+ }
+
+ /**
+ * Returns the index of the parameter declared with the given type, ensuring that there is exactly one such parameter.
+ *
+ * @throws NoSuchFieldError if there is no or more than one parameter with that type.
+ * @hide
+ */
+ public static int getParameterIndexByType(Member method, Class> type) {
+ Class>[] classes = (method instanceof Method) ?
+ ((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
+ int idx = -1;
+ for (int i = 0; i < classes.length; i++) {
+ if (classes[i] == type) {
+ if (idx == -1) {
+ idx = i;
+ } else {
+ throw new NoSuchFieldError("More than one parameter of type " + type + " found in " + method);
+ }
+ }
+ }
+ if (idx != -1) {
+ return idx;
+ } else {
+ throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
+ }
+ }
+
+ //#################################################################################################
+
+ /**
+ * Sets the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setObjectField(Object obj, String fieldName, Object value) {
+ try {
+ findField(obj.getClass(), fieldName).set(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setBooleanField(Object obj, String fieldName, boolean value) {
+ try {
+ findField(obj.getClass(), fieldName).setBoolean(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setByteField(Object obj, String fieldName, byte value) {
+ try {
+ findField(obj.getClass(), fieldName).setByte(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setCharField(Object obj, String fieldName, char value) {
+ try {
+ findField(obj.getClass(), fieldName).setChar(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setDoubleField(Object obj, String fieldName, double value) {
+ try {
+ findField(obj.getClass(), fieldName).setDouble(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setFloatField(Object obj, String fieldName, float value) {
+ try {
+ findField(obj.getClass(), fieldName).setFloat(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setIntField(Object obj, String fieldName, int value) {
+ try {
+ findField(obj.getClass(), fieldName).setInt(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setLongField(Object obj, String fieldName, long value) {
+ try {
+ findField(obj.getClass(), fieldName).setLong(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static void setShortField(Object obj, String fieldName, short value) {
+ try {
+ findField(obj.getClass(), fieldName).setShort(obj, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ //#################################################################################################
+
+ /**
+ * Returns the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static Object getObjectField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).get(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * For inner classes, returns the surrounding instance, i.e. the {@code this} reference of the surrounding class.
+ */
+ public static Object getSurroundingThis(Object obj) {
+ return getObjectField(obj, "this$0");
+ }
+
+ /**
+ * Returns the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ public static boolean getBooleanField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).getBoolean(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static byte getByteField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).getByte(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static char getCharField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).getChar(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static double getDoubleField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).getDouble(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static float getFloatField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).getFloat(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static int getIntField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).getInt(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static long getLongField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).getLong(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
+ */
+ public static short getShortField(Object obj, String fieldName) {
+ try {
+ return findField(obj.getClass(), fieldName).getShort(obj);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ //#################################################################################################
+
+ /**
+ * Sets the value of a static object field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticObjectField(Class> clazz, String fieldName, Object value) {
+ try {
+ findField(clazz, fieldName).set(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code boolean} field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticBooleanField(Class> clazz, String fieldName, boolean value) {
+ try {
+ findField(clazz, fieldName).setBoolean(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code byte} field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticByteField(Class> clazz, String fieldName, byte value) {
+ try {
+ findField(clazz, fieldName).setByte(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code char} field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticCharField(Class> clazz, String fieldName, char value) {
+ try {
+ findField(clazz, fieldName).setChar(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code double} field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticDoubleField(Class> clazz, String fieldName, double value) {
+ try {
+ findField(clazz, fieldName).setDouble(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code float} field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticFloatField(Class> clazz, String fieldName, float value) {
+ try {
+ findField(clazz, fieldName).setFloat(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code int} field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticIntField(Class> clazz, String fieldName, int value) {
+ try {
+ findField(clazz, fieldName).setInt(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code long} field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticLongField(Class> clazz, String fieldName, long value) {
+ try {
+ findField(clazz, fieldName).setLong(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code short} field in the given class. See also {@link #findField}.
+ */
+ public static void setStaticShortField(Class> clazz, String fieldName, short value) {
+ try {
+ findField(clazz, fieldName).setShort(null, value);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ //#################################################################################################
+
+ /**
+ * Returns the value of a static object field in the given class. See also {@link #findField}.
+ */
+ public static Object getStaticObjectField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).get(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the value of a static {@code boolean} field in the given class. See also {@link #findField}.
+ */
+ public static boolean getStaticBooleanField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).getBoolean(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code byte} field in the given class. See also {@link #findField}.
+ */
+ public static byte getStaticByteField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).getByte(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code char} field in the given class. See also {@link #findField}.
+ */
+ public static char getStaticCharField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).getChar(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code double} field in the given class. See also {@link #findField}.
+ */
+ public static double getStaticDoubleField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).getDouble(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code float} field in the given class. See also {@link #findField}.
+ */
+ public static float getStaticFloatField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).getFloat(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code int} field in the given class. See also {@link #findField}.
+ */
+ public static int getStaticIntField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).getInt(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code long} field in the given class. See also {@link #findField}.
+ */
+ public static long getStaticLongField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).getLong(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the value of a static {@code short} field in the given class. See also {@link #findField}.
+ */
+ public static short getStaticShortField(Class> clazz, String fieldName) {
+ try {
+ return findField(clazz, fieldName).getShort(null);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ }
+ }
+
+ //#################################################################################################
+
+ /**
+ * Calls an instance or static method of the given object.
+ * The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
+ *
+ * @param obj The object instance. A class reference is not sufficient!
+ * @param methodName The method name.
+ * @param args The arguments for the method call.
+ * @throws NoSuchMethodError In case no suitable method was found.
+ * @throws InvocationTargetError In case an exception was thrown by the invoked method.
+ */
+ public static Object callMethod(Object obj, String methodName, Object... args) {
+ try {
+ return findMethodBestMatch(obj.getClass(), methodName, args).invoke(obj, args);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw new InvocationTargetError(e.getCause());
+ }
+ }
+
+ /**
+ * Calls an instance or static method of the given object.
+ * See {@link #callMethod(Object, String, Object...)}.
+ *
+ *
This variant allows you to specify parameter types, which can help in case there are multiple
+ * methods with the same name, especially if you call it with {@code null} parameters.
+ */
+ public static Object callMethod(Object obj, String methodName, Class>[] parameterTypes, Object... args) {
+ try {
+ return findMethodBestMatch(obj.getClass(), methodName, parameterTypes, args).invoke(obj, args);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw new InvocationTargetError(e.getCause());
+ }
+ }
+
+ /**
+ * Calls a static method of the given class.
+ * The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
+ *
+ * @param clazz The class reference.
+ * @param methodName The method name.
+ * @param args The arguments for the method call.
+ * @throws NoSuchMethodError In case no suitable method was found.
+ * @throws InvocationTargetError In case an exception was thrown by the invoked method.
+ */
+ public static Object callStaticMethod(Class> clazz, String methodName, Object... args) {
+ try {
+ return findMethodBestMatch(clazz, methodName, args).invoke(null, args);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw new InvocationTargetError(e.getCause());
+ }
+ }
+
+ /**
+ * Calls a static method of the given class.
+ * See {@link #callStaticMethod(Class, String, Object...)}.
+ *
+ *
This variant allows you to specify parameter types, which can help in case there are multiple
+ * methods with the same name, especially if you call it with {@code null} parameters.
+ */
+ public static Object callStaticMethod(Class> clazz, String methodName, Class>[] parameterTypes, Object... args) {
+ try {
+ return findMethodBestMatch(clazz, methodName, parameterTypes, args).invoke(null, args);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw new InvocationTargetError(e.getCause());
+ }
+ }
+
+ /**
+ * This class provides a wrapper for an exception thrown by a method invocation.
+ *
+ * @see #callMethod(Object, String, Object...)
+ * @see #callStaticMethod(Class, String, Object...)
+ * @see #newInstance(Class, Object...)
+ */
+ public static final class InvocationTargetError extends Error {
+ private static final long serialVersionUID = -1070936889459514628L;
+
+ /**
+ * @hide
+ */
+ public InvocationTargetError(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ //#################################################################################################
+
+ /**
+ * Creates a new instance of the given class.
+ * The constructor is resolved using {@link #findConstructorBestMatch(Class, Object...)}.
+ *
+ * @param clazz The class reference.
+ * @param args The arguments for the constructor call.
+ * @throws NoSuchMethodError In case no suitable constructor was found.
+ * @throws InvocationTargetError In case an exception was thrown by the invoked method.
+ * @throws InstantiationError In case the class cannot be instantiated.
+ */
+ public static Object newInstance(Class> clazz, Object... args) {
+ try {
+ return findConstructorBestMatch(clazz, args).newInstance(args);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw new InvocationTargetError(e.getCause());
+ } catch (InstantiationException e) {
+ throw new InstantiationError(e.getMessage());
+ }
+ }
+
+ /**
+ * Creates a new instance of the given class.
+ * See {@link #newInstance(Class, Object...)}.
+ *
+ *
This variant allows you to specify parameter types, which can help in case there are multiple
+ * constructors with the same name, especially if you call it with {@code null} parameters.
+ */
+ public static Object newInstance(Class> clazz, Class>[] parameterTypes, Object... args) {
+ try {
+ return findConstructorBestMatch(clazz, parameterTypes, args).newInstance(args);
+ } catch (IllegalAccessException e) {
+ // should not happen
+ XposedBridge.log(e);
+ throw new IllegalAccessError(e.getMessage());
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ throw new InvocationTargetError(e.getCause());
+ } catch (InstantiationException e) {
+ throw new InstantiationError(e.getMessage());
+ }
+ }
+
+ //#################################################################################################
+
+ /**
+ * Attaches any value to an object instance. This simulates adding an instance field.
+ * The value can be retrieved again with {@link #getAdditionalInstanceField}.
+ *
+ * @param obj The object instance for which the value should be stored.
+ * @param key The key in the value map for this object instance.
+ * @param value The value to store.
+ * @return The previously stored value for this instance/key combination, or {@code null} if there was none.
+ */
+ public static Object setAdditionalInstanceField(Object obj, String key, Object value) {
+ if (obj == null)
+ throw new NullPointerException("object must not be null");
+ if (key == null)
+ throw new NullPointerException("key must not be null");
+
+ HashMap objectFields;
+ synchronized (additionalFields) {
+ objectFields = additionalFields.get(obj);
+ if (objectFields == null) {
+ objectFields = new HashMap<>();
+ additionalFields.put(obj, objectFields);
+ }
+ }
+
+ synchronized (objectFields) {
+ return objectFields.put(key, value);
+ }
+ }
+
+ /**
+ * Returns a value which was stored with {@link #setAdditionalInstanceField}.
+ *
+ * @param obj The object instance for which the value has been stored.
+ * @param key The key in the value map for this object instance.
+ * @return The stored value for this instance/key combination, or {@code null} if there is none.
+ */
+ public static Object getAdditionalInstanceField(Object obj, String key) {
+ if (obj == null)
+ throw new NullPointerException("object must not be null");
+ if (key == null)
+ throw new NullPointerException("key must not be null");
+
+ HashMap objectFields;
+ synchronized (additionalFields) {
+ objectFields = additionalFields.get(obj);
+ if (objectFields == null)
+ return null;
+ }
+
+ synchronized (objectFields) {
+ return objectFields.get(key);
+ }
+ }
+
+ /**
+ * Removes and returns a value which was stored with {@link #setAdditionalInstanceField}.
+ *
+ * @param obj The object instance for which the value has been stored.
+ * @param key The key in the value map for this object instance.
+ * @return The previously stored value for this instance/key combination, or {@code null} if there was none.
+ */
+ public static Object removeAdditionalInstanceField(Object obj, String key) {
+ if (obj == null)
+ throw new NullPointerException("object must not be null");
+ if (key == null)
+ throw new NullPointerException("key must not be null");
+
+ HashMap objectFields;
+ synchronized (additionalFields) {
+ objectFields = additionalFields.get(obj);
+ if (objectFields == null)
+ return null;
+ }
+
+ synchronized (objectFields) {
+ return objectFields.remove(key);
+ }
+ }
+
+ /**
+ * Like {@link #setAdditionalInstanceField}, but the value is stored for the class of {@code obj}.
+ */
+ public static Object setAdditionalStaticField(Object obj, String key, Object value) {
+ return setAdditionalInstanceField(obj.getClass(), key, value);
+ }
+
+ /**
+ * Like {@link #getAdditionalInstanceField}, but the value is returned for the class of {@code obj}.
+ */
+ public static Object getAdditionalStaticField(Object obj, String key) {
+ return getAdditionalInstanceField(obj.getClass(), key);
+ }
+
+ /**
+ * Like {@link #removeAdditionalInstanceField}, but the value is removed and returned for the class of {@code obj}.
+ */
+ public static Object removeAdditionalStaticField(Object obj, String key) {
+ return removeAdditionalInstanceField(obj.getClass(), key);
+ }
+
+ /**
+ * Like {@link #setAdditionalInstanceField}, but the value is stored for {@code clazz}.
+ */
+ public static Object setAdditionalStaticField(Class> clazz, String key, Object value) {
+ return setAdditionalInstanceField(clazz, key, value);
+ }
+
+ /**
+ * Like {@link #setAdditionalInstanceField}, but the value is returned for {@code clazz}.
+ */
+ public static Object getAdditionalStaticField(Class> clazz, String key) {
+ return getAdditionalInstanceField(clazz, key);
+ }
+
+ /**
+ * Like {@link #setAdditionalInstanceField}, but the value is removed and returned for {@code clazz}.
+ */
+ public static Object removeAdditionalStaticField(Class> clazz, String key) {
+ return removeAdditionalInstanceField(clazz, key);
+ }
+
+ //#################################################################################################
+
+ /**
+ * Loads an asset from a resource object and returns the content as {@code byte} array.
+ *
+ * @param res The resources from which the asset should be loaded.
+ * @param path The path to the asset, as in {@link AssetManager#open}.
+ * @return The content of the asset.
+ */
+ public static byte[] assetAsByteArray(Resources res, String path) throws IOException {
+ return inputStreamToByteArray(res.getAssets().open(path));
+ }
+
+ /*package*/
+ static byte[] inputStreamToByteArray(InputStream is) throws IOException {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ byte[] temp = new byte[1024];
+ int read;
+
+ while ((read = is.read(temp)) > 0) {
+ buf.write(temp, 0, read);
+ }
+ is.close();
+ return buf.toByteArray();
+ }
+
+ /**
+ * Invokes the {@link Closeable#close()} method, ignoring IOExceptions.
+ */
+ /*package*/
+ static void closeSilently(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Returns the lowercase hex string representation of a file's MD5 hash sum.
+ */
+ public static String getMD5Sum(String file) throws IOException {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("MD5");
+ InputStream is = new FileInputStream(file);
+ byte[] buffer = new byte[8192];
+ int read;
+ while ((read = is.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+ is.close();
+ byte[] md5sum = digest.digest();
+ BigInteger bigInt = new BigInteger(1, md5sum);
+ return bigInt.toString(16);
+ } catch (NoSuchAlgorithmException e) {
+ return "";
+ }
+ }
+
+ //#################################################################################################
+
+ /**
+ * Increments the depth counter for the given method.
+ *
+ * The intention of the method depth counter is to keep track of the call depth for recursive
+ * methods, e.g. to override parameters only for the outer call. The Xposed framework uses this
+ * to load drawable replacements only once per call, even when multiple
+ * {@link Resources#getDrawable} variants call each other.
+ *
+ * @param method The method name. Should be prefixed with a unique, module-specific string.
+ * @return The updated depth.
+ */
+ public static int incrementMethodDepth(String method) {
+ return getMethodDepthCounter(method).get().incrementAndGet();
+ }
+
+ /**
+ * Decrements the depth counter for the given method.
+ * See {@link #incrementMethodDepth} for details.
+ *
+ * @param method The method name. Should be prefixed with a unique, module-specific string.
+ * @return The updated depth.
+ */
+ public static int decrementMethodDepth(String method) {
+ return getMethodDepthCounter(method).get().decrementAndGet();
+ }
+
+ /**
+ * Returns the current depth counter for the given method.
+ * See {@link #incrementMethodDepth} for details.
+ *
+ * @param method The method name. Should be prefixed with a unique, module-specific string.
+ * @return The updated depth.
+ */
+ public static int getMethodDepth(String method) {
+ return getMethodDepthCounter(method).get().get();
+ }
+
+ private static ThreadLocal getMethodDepthCounter(String method) {
+ synchronized (sMethodDepth) {
+ ThreadLocal counter = sMethodDepth.get(method);
+ if (counter == null) {
+ counter = new ThreadLocal() {
+ @Override
+ protected AtomicInteger initialValue() {
+ return new AtomicInteger();
+ }
+ };
+ sMethodDepth.put(method, counter);
+ }
+ return counter;
+ }
+ }
+
+ //#################################################################################################
+ // TODO helpers for view traversing
/*To make it easier, I will try and implement some more helpers:
- add view before/after existing view (I already mentioned that I think)
- get index of view in its parent
diff --git a/core/src/main/java/de/robv/android/xposed/XposedInit.java b/core/src/main/java/de/robv/android/xposed/XposedInit.java
index 7a3f55b7..47874cc5 100644
--- a/core/src/main/java/de/robv/android/xposed/XposedInit.java
+++ b/core/src/main/java/de/robv/android/xposed/XposedInit.java
@@ -20,6 +20,18 @@
package de.robv.android.xposed;
+import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
+import static de.robv.android.xposed.XposedBridge.hookAllMethods;
+import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
+import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
+import static de.robv.android.xposed.XposedBridge.sLoadedPackageCallbacks;
+import static de.robv.android.xposed.XposedHelpers.callMethod;
+import static de.robv.android.xposed.XposedHelpers.closeSilently;
+import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
+import static de.robv.android.xposed.XposedHelpers.getObjectField;
+import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType;
+import static de.robv.android.xposed.XposedHelpers.setStaticObjectField;
+
import android.annotation.SuppressLint;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
@@ -32,7 +44,8 @@ import android.os.Process;
import android.util.ArraySet;
import android.util.Log;
-import com.android.internal.os.ZygoteInit;
+import org.lsposed.lspd.nativebridge.NativeAPI;
+import org.lsposed.lspd.nativebridge.ResourcesHook;
import java.io.BufferedReader;
import java.io.File;
@@ -52,56 +65,14 @@ import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XCallback;
import hidden.HiddenApiBridge;
-import org.lsposed.lspd.nativebridge.NativeAPI;
-import org.lsposed.lspd.nativebridge.ResourcesHook;
-
-import static de.robv.android.xposed.XposedBridge.hookAllMethods;
-import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
-import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
-import static de.robv.android.xposed.XposedBridge.sLoadedPackageCallbacks;
-import static de.robv.android.xposed.XposedHelpers.callMethod;
-import static de.robv.android.xposed.XposedHelpers.closeSilently;
-import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
-import static de.robv.android.xposed.XposedHelpers.findClass;
-import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
-import static de.robv.android.xposed.XposedHelpers.getObjectField;
-import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType;
-import static de.robv.android.xposed.XposedHelpers.setStaticBooleanField;
-import static de.robv.android.xposed.XposedHelpers.setStaticLongField;
-import static de.robv.android.xposed.XposedHelpers.setStaticObjectField;
-import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
public final class XposedInit {
private static final String TAG = XposedBridge.TAG;
public static boolean startsSystemServer = false;
- private static final String startClassName = ""; // ed: no support for tool process anymore
public static volatile boolean disableResources = false;
- private XposedInit() {
- }
-
- /**
- * Hook some methods which we want to create an easier interface for developers.
- */
- /*package*/
- public static void initForZygote() throws Throwable {
- // TODO Are these still needed for us?
- // MIUI
- if (findFieldIfExists(ZygoteInit.class, "BOOT_START_TIME") != null) {
- setStaticLongField(ZygoteInit.class, "BOOT_START_TIME", XposedBridge.BOOT_START_TIME);
- }
- // Samsung
- Class> zygote = findClass("com.android.internal.os.Zygote", null);
- try {
- setStaticBooleanField(zygote, "isEnhancedZygoteASLREnabled", false);
- } catch (NoSuchFieldError ignored) {
- }
-
- hookResources();
- }
-
- private static void hookResources() throws Throwable {
+ public static void hookResources() throws Throwable {
if (!serviceClient.isResourcesHookEnabled() || disableResources) {
return;
}
@@ -242,7 +213,7 @@ public final class XposedInit {
}
}
- public static boolean loadModules(boolean callInitZygote) throws IOException {
+ public static boolean loadModules() throws IOException {
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
if (hasLoaded) {
return false;
@@ -256,7 +227,7 @@ public final class XposedInit {
newLoadedApk.add(apk);
} else {
loadedModules.add(apk); // temporarily add it for XSharedPreference
- boolean loadSuccess = loadModule(apk, callInitZygote);
+ boolean loadSuccess = loadModule(apk);
if (loadSuccess) {
newLoadedApk.add(apk);
}
@@ -326,7 +297,7 @@ public final class XposedInit {
}
- private static boolean initModule(ClassLoader mcl, String apk, boolean callInitZygote) {
+ private static boolean initModule(ClassLoader mcl, String apk) {
InputStream is = mcl.getResourceAsStream("assets/xposed_init");
if (is == null) {
return true;
@@ -359,9 +330,7 @@ public final class XposedInit {
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
(IXposedHookZygoteInit) moduleInstance, param));
- if (callInitZygote) {
- ((IXposedHookZygoteInit) moduleInstance).initZygote(param);
- }
+ ((IXposedHookZygoteInit) moduleInstance).initZygote(param);
}
if (moduleInstance instanceof IXposedHookLoadPackage)
@@ -390,7 +359,7 @@ public final class XposedInit {
* in assets/xposed_init.
*/
@SuppressLint("PrivateApi")
- private static boolean loadModule(String apk, boolean callInitZygote) {
+ private static boolean loadModule(String apk) {
Log.i(TAG, "Loading modules from " + apk);
if (!new File(apk).exists()) {
@@ -418,7 +387,7 @@ public final class XposedInit {
} catch (ClassNotFoundException ignored) {
}
- boolean res = initModule(mcl, apk, callInitZygote);
+ boolean res = initModule(mcl, apk);
res = res && initNativeModule(mcl, apk);
return res;
}
diff --git a/core/src/main/java/de/robv/android/xposed/callbacks/IXUnhook.java b/core/src/main/java/de/robv/android/xposed/callbacks/IXUnhook.java
index aa1c691e..e2fa76c9 100644
--- a/core/src/main/java/de/robv/android/xposed/callbacks/IXUnhook.java
+++ b/core/src/main/java/de/robv/android/xposed/callbacks/IXUnhook.java
@@ -33,13 +33,13 @@ import de.robv.android.xposed.IXposedHookZygoteInit;
* @param The class of the callback.
*/
public interface IXUnhook {
- /**
- * Returns the callback that has been registered.
- */
- T getCallback();
+ /**
+ * Returns the callback that has been registered.
+ */
+ T getCallback();
- /**
- * Removes the callback.
- */
- void unhook();
+ /**
+ * Removes the callback.
+ */
+ void unhook();
}
diff --git a/core/src/main/java/de/robv/android/xposed/callbacks/XC_InitPackageResources.java b/core/src/main/java/de/robv/android/xposed/callbacks/XC_InitPackageResources.java
index 980a134f..c46917c6 100644
--- a/core/src/main/java/de/robv/android/xposed/callbacks/XC_InitPackageResources.java
+++ b/core/src/main/java/de/robv/android/xposed/callbacks/XC_InitPackageResources.java
@@ -30,48 +30,55 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
* subclass.
*/
public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources {
- /**
- * Creates a new callback with default priority.
- * @hide
- */
- @SuppressWarnings("deprecation")
- public XC_InitPackageResources() {
- super();
- }
+ /**
+ * Creates a new callback with default priority.
+ *
+ * @hide
+ */
+ @SuppressWarnings("deprecation")
+ public XC_InitPackageResources() {
+ super();
+ }
- /**
- * Creates a new callback with a specific priority.
- *
- * @param priority See {@link XCallback#priority}.
- * @hide
- */
- public XC_InitPackageResources(int priority) {
- super(priority);
- }
+ /**
+ * Creates a new callback with a specific priority.
+ *
+ * @param priority See {@link XCallback#priority}.
+ * @hide
+ */
+ public XC_InitPackageResources(int priority) {
+ super(priority);
+ }
- /**
- * Wraps information about the resources being initialized.
- */
- public static final class InitPackageResourcesParam extends XCallback.Param {
- /** @hide */
- public InitPackageResourcesParam(CopyOnWriteSortedSet callbacks) {
- super(callbacks);
- }
+ /**
+ * Wraps information about the resources being initialized.
+ */
+ public static final class InitPackageResourcesParam extends XCallback.Param {
+ /**
+ * @hide
+ */
+ public InitPackageResourcesParam(CopyOnWriteSortedSet callbacks) {
+ super(callbacks);
+ }
- /** The name of the package for which resources are being loaded. */
- public String packageName;
+ /**
+ * The name of the package for which resources are being loaded.
+ */
+ public String packageName;
- /**
- * Reference to the resources that can be used for calls to
- * {@link XResources#setReplacement(String, String, String, Object)}.
- */
- public XResources res;
- }
+ /**
+ * Reference to the resources that can be used for calls to
+ * {@link XResources#setReplacement(String, String, String, Object)}.
+ */
+ public XResources res;
+ }
- /** @hide */
- @Override
- protected void call(Param param) throws Throwable {
- if (param instanceof InitPackageResourcesParam)
- handleInitPackageResources((InitPackageResourcesParam) param);
- }
+ /**
+ * @hide
+ */
+ @Override
+ protected void call(Param param) throws Throwable {
+ if (param instanceof InitPackageResourcesParam)
+ handleInitPackageResources((InitPackageResourcesParam) param);
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/callbacks/XC_LayoutInflated.java b/core/src/main/java/de/robv/android/xposed/callbacks/XC_LayoutInflated.java
index 016044ce..62026fea 100644
--- a/core/src/main/java/de/robv/android/xposed/callbacks/XC_LayoutInflated.java
+++ b/core/src/main/java/de/robv/android/xposed/callbacks/XC_LayoutInflated.java
@@ -31,89 +31,103 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
* and its variants.
*/
public abstract class XC_LayoutInflated extends XCallback {
- /**
- * Creates a new callback with default priority.
- */
- @SuppressWarnings("deprecation")
- public XC_LayoutInflated() {
- super();
- }
+ /**
+ * Creates a new callback with default priority.
+ */
+ @SuppressWarnings("deprecation")
+ public XC_LayoutInflated() {
+ super();
+ }
- /**
- * Creates a new callback with a specific priority.
- *
- * @param priority See {@link XCallback#priority}.
- */
- public XC_LayoutInflated(int priority) {
- super(priority);
- }
+ /**
+ * Creates a new callback with a specific priority.
+ *
+ * @param priority See {@link XCallback#priority}.
+ */
+ public XC_LayoutInflated(int priority) {
+ super(priority);
+ }
- /**
- * Wraps information about the inflated layout.
- */
- public static final class LayoutInflatedParam extends XCallback.Param {
- /** @hide */
- public LayoutInflatedParam(CopyOnWriteSortedSet callbacks) {
- super(callbacks);
- }
+ /**
+ * Wraps information about the inflated layout.
+ */
+ public static final class LayoutInflatedParam extends XCallback.Param {
+ /**
+ * @hide
+ */
+ public LayoutInflatedParam(CopyOnWriteSortedSet callbacks) {
+ super(callbacks);
+ }
- /** The view that has been created from the layout. */
- public View view;
+ /**
+ * The view that has been created from the layout.
+ */
+ public View view;
- /** Container with the ID and name of the underlying resource. */
- public ResourceNames resNames;
+ /**
+ * Container with the ID and name of the underlying resource.
+ */
+ public ResourceNames resNames;
- /** Directory from which the layout was actually loaded (e.g. "layout-sw600dp"). */
- public String variant;
+ /**
+ * Directory from which the layout was actually loaded (e.g. "layout-sw600dp").
+ */
+ public String variant;
- /** Resources containing the layout. */
- public XResources res;
- }
+ /**
+ * Resources containing the layout.
+ */
+ public XResources res;
+ }
- /** @hide */
- @Override
- protected void call(Param param) throws Throwable {
- if (param instanceof LayoutInflatedParam)
- handleLayoutInflated((LayoutInflatedParam) param);
- }
+ /**
+ * @hide
+ */
+ @Override
+ protected void call(Param param) throws Throwable {
+ if (param instanceof LayoutInflatedParam)
+ handleLayoutInflated((LayoutInflatedParam) param);
+ }
- /**
- * This method is called when the hooked layout has been inflated.
- *
- * @param liparam Information about the layout and the inflated view.
- * @throws Throwable Everything the callback throws is caught and logged.
- */
- public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable;
+ /**
+ * This method is called when the hooked layout has been inflated.
+ *
+ * @param liparam Information about the layout and the inflated view.
+ * @throws Throwable Everything the callback throws is caught and logged.
+ */
+ public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable;
- /**
- * An object with which the callback can be removed.
- */
- public class Unhook implements IXUnhook {
- private final String resDir;
- private final int id;
+ /**
+ * An object with which the callback can be removed.
+ */
+ public class Unhook implements IXUnhook {
+ private final String resDir;
+ private final int id;
- /** @hide */
- public Unhook(String resDir, int id) {
- this.resDir = resDir;
- this.id = id;
- }
+ /**
+ * @hide
+ */
+ public Unhook(String resDir, int id) {
+ this.resDir = resDir;
+ this.id = id;
+ }
- /**
- * Returns the resource ID of the hooked layout.
- */
- public int getId() {
- return id;
- }
+ /**
+ * Returns the resource ID of the hooked layout.
+ */
+ public int getId() {
+ return id;
+ }
- @Override
- public XC_LayoutInflated getCallback() {
- return XC_LayoutInflated.this;
- }
+ @Override
+ public XC_LayoutInflated getCallback() {
+ return XC_LayoutInflated.this;
+ }
- @Override
- public void unhook() {
- XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
- }
+ @Override
+ public void unhook() {
+ XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
+ }
- }
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/callbacks/XC_LoadPackage.java b/core/src/main/java/de/robv/android/xposed/callbacks/XC_LoadPackage.java
index 3accea63..5cd3a1a4 100644
--- a/core/src/main/java/de/robv/android/xposed/callbacks/XC_LoadPackage.java
+++ b/core/src/main/java/de/robv/android/xposed/callbacks/XC_LoadPackage.java
@@ -30,54 +30,69 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
* subclass.
*/
public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoadPackage {
- /**
- * Creates a new callback with default priority.
- * @hide
- */
- @SuppressWarnings("deprecation")
- public XC_LoadPackage() {
- super();
- }
+ /**
+ * Creates a new callback with default priority.
+ *
+ * @hide
+ */
+ @SuppressWarnings("deprecation")
+ public XC_LoadPackage() {
+ super();
+ }
- /**
- * Creates a new callback with a specific priority.
- *
- * @param priority See {@link XCallback#priority}.
- * @hide
- */
- public XC_LoadPackage(int priority) {
- super(priority);
- }
+ /**
+ * Creates a new callback with a specific priority.
+ *
+ * @param priority See {@link XCallback#priority}.
+ * @hide
+ */
+ public XC_LoadPackage(int priority) {
+ super(priority);
+ }
- /**
- * Wraps information about the app being loaded.
- */
- public static final class LoadPackageParam extends XCallback.Param {
- /** @hide */
- public LoadPackageParam(CopyOnWriteSortedSet callbacks) {
- super(callbacks);
- }
+ /**
+ * Wraps information about the app being loaded.
+ */
+ public static final class LoadPackageParam extends XCallback.Param {
+ /**
+ * @hide
+ */
+ public LoadPackageParam(CopyOnWriteSortedSet callbacks) {
+ super(callbacks);
+ }
- /** The name of the package being loaded. */
- public String packageName;
+ /**
+ * The name of the package being loaded.
+ */
+ public String packageName;
- /** The process in which the package is executed. */
- public String processName;
+ /**
+ * The process in which the package is executed.
+ */
+ public String processName;
- /** The ClassLoader used for this package. */
- public ClassLoader classLoader;
+ /**
+ * The ClassLoader used for this package.
+ */
+ public ClassLoader classLoader;
- /** More information about the application being loaded. */
- public ApplicationInfo appInfo;
+ /**
+ * More information about the application being loaded.
+ */
+ public ApplicationInfo appInfo;
- /** Set to {@code true} if this is the first (and main) application for this process. */
- public boolean isFirstApplication;
- }
+ /**
+ * Set to {@code true} if this is the first (and main) application for this process.
+ */
+ public boolean isFirstApplication;
+ }
- /** @hide */
- @Override
- protected void call(Param param) throws Throwable {
- if (param instanceof LoadPackageParam)
- handleLoadPackage((LoadPackageParam) param);
- }
+ /**
+ * @hide
+ */
+ @Override
+ protected void call(Param param) throws Throwable {
+ if (param instanceof LoadPackageParam)
+ handleLoadPackage((LoadPackageParam) param);
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/callbacks/XCallback.java b/core/src/main/java/de/robv/android/xposed/callbacks/XCallback.java
index 284a3080..8b80add1 100644
--- a/core/src/main/java/de/robv/android/xposed/callbacks/XCallback.java
+++ b/core/src/main/java/de/robv/android/xposed/callbacks/XCallback.java
@@ -27,147 +27,174 @@ import java.io.Serializable;
import de.robv.android.xposed.IModuleContext;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
+
import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
/**
* Base class for Xposed callbacks.
- *
+ *
* This class only keeps a priority for ordering multiple callbacks.
* The actual (abstract) callback methods are added by subclasses.
*/
public abstract class XCallback implements Comparable, IModuleContext {
- /**
- * Callback priority, higher number means earlier execution.
- *
- * This is usually set to {@link #PRIORITY_DEFAULT}. However, in case a certain callback should
- * be executed earlier or later a value between {@link #PRIORITY_HIGHEST} and {@link #PRIORITY_LOWEST}
- * can be set instead. The values are just for orientation though, Xposed doesn't enforce any
- * boundaries on the priority values.
- */
- public final int priority;
+ /**
+ * Callback priority, higher number means earlier execution.
+ *
+ *
This is usually set to {@link #PRIORITY_DEFAULT}. However, in case a certain callback should
+ * be executed earlier or later a value between {@link #PRIORITY_HIGHEST} and {@link #PRIORITY_LOWEST}
+ * can be set instead. The values are just for orientation though, Xposed doesn't enforce any
+ * boundaries on the priority values.
+ */
+ public final int priority;
- /** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */
- @Deprecated
- public XCallback() {
- this.priority = PRIORITY_DEFAULT;
- }
+ /**
+ * @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it!
+ */
+ @Deprecated
+ public XCallback() {
+ this.priority = PRIORITY_DEFAULT;
+ }
- /** @hide */
- public XCallback(int priority) {
- this.priority = priority;
- }
+ /**
+ * @hide
+ */
+ public XCallback(int priority) {
+ this.priority = priority;
+ }
- /**
- * Base class for Xposed callback parameters.
- */
- public static abstract class Param {
- /** @hide */
- public final Object[] callbacks;
- private Bundle extra;
+ /**
+ * Base class for Xposed callback parameters.
+ */
+ public static abstract class Param {
+ /**
+ * @hide
+ */
+ public final Object[] callbacks;
+ private Bundle extra;
- /** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */
- @Deprecated
- protected Param() {
- callbacks = null;
- }
+ /**
+ * @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it!
+ */
+ @Deprecated
+ protected Param() {
+ callbacks = null;
+ }
- /** @hide */
- protected Param(CopyOnWriteSortedSet extends XCallback> callbacks) {
- this.callbacks = callbacks.getSnapshot();
- }
+ /**
+ * @hide
+ */
+ protected Param(CopyOnWriteSortedSet extends XCallback> callbacks) {
+ this.callbacks = callbacks.getSnapshot();
+ }
- /**
- * This can be used to store any data for the scope of the callback.
- *
- *
Use this instead of instance variables, as it has a clear reference to e.g. each
- * separate call to a method, even when the same method is called recursively.
- *
- * @see #setObjectExtra
- * @see #getObjectExtra
- */
- public synchronized Bundle getExtra() {
- if (extra == null)
- extra = new Bundle();
- return extra;
- }
+ /**
+ * This can be used to store any data for the scope of the callback.
+ *
+ *
Use this instead of instance variables, as it has a clear reference to e.g. each
+ * separate call to a method, even when the same method is called recursively.
+ *
+ * @see #setObjectExtra
+ * @see #getObjectExtra
+ */
+ public synchronized Bundle getExtra() {
+ if (extra == null)
+ extra = new Bundle();
+ return extra;
+ }
- /**
- * Returns an object stored with {@link #setObjectExtra}.
- */
- public Object getObjectExtra(String key) {
- Serializable o = getExtra().getSerializable(key);
- if (o instanceof SerializeWrapper)
- return ((SerializeWrapper) o).object;
- return null;
- }
+ /**
+ * Returns an object stored with {@link #setObjectExtra}.
+ */
+ public Object getObjectExtra(String key) {
+ Serializable o = getExtra().getSerializable(key);
+ if (o instanceof SerializeWrapper)
+ return ((SerializeWrapper) o).object;
+ return null;
+ }
- /**
- * Stores any object for the scope of the callback. For data types that support it, use
- * the {@link Bundle} returned by {@link #getExtra} instead.
- */
- public void setObjectExtra(String key, Object o) {
- getExtra().putSerializable(key, new SerializeWrapper(o));
- }
+ /**
+ * Stores any object for the scope of the callback. For data types that support it, use
+ * the {@link Bundle} returned by {@link #getExtra} instead.
+ */
+ public void setObjectExtra(String key, Object o) {
+ getExtra().putSerializable(key, new SerializeWrapper(o));
+ }
- private static class SerializeWrapper implements Serializable {
- private static final long serialVersionUID = 1L;
- private final Object object;
- public SerializeWrapper(Object o) {
- object = o;
- }
- }
- }
+ private static class SerializeWrapper implements Serializable {
+ private static final long serialVersionUID = 1L;
+ private final Object object;
- /** @hide */
- public static void callAll(Param param) {
+ public SerializeWrapper(Object o) {
+ object = o;
+ }
+ }
+ }
- if (param instanceof XC_LoadPackage.LoadPackageParam) {
- // deopt methods in system apps or priv-apps, this would be not necessary
+ /**
+ * @hide
+ */
+ public static void callAll(Param param) {
+
+ if (param instanceof XC_LoadPackage.LoadPackageParam) {
+ // deopt methods in system apps or priv-apps, this would be not necessary
// only if we found out how to recompile their apks
- XC_LoadPackage.LoadPackageParam lpp = (XC_LoadPackage.LoadPackageParam) param;
- PrebuiltMethodsDeopter.deoptMethods(lpp.packageName, lpp.classLoader);
- }
+ XC_LoadPackage.LoadPackageParam lpp = (XC_LoadPackage.LoadPackageParam) param;
+ PrebuiltMethodsDeopter.deoptMethods(lpp.packageName, lpp.classLoader);
+ }
- if (param.callbacks == null)
- throw new IllegalStateException("This object was not created for use with callAll");
+ if (param.callbacks == null)
+ throw new IllegalStateException("This object was not created for use with callAll");
- for (int i = 0; i < param.callbacks.length; i++) {
- try {
- ((XCallback) param.callbacks[i]).call(param);
- } catch (Throwable t) { XposedBridge.log(t); }
- }
- }
+ for (int i = 0; i < param.callbacks.length; i++) {
+ try {
+ ((XCallback) param.callbacks[i]).call(param);
+ } catch (Throwable t) {
+ XposedBridge.log(t);
+ }
+ }
+ }
- /** @hide */
- protected void call(Param param) throws Throwable {}
+ /**
+ * @hide
+ */
+ protected void call(Param param) throws Throwable {
+ }
- @Override
- public String getApkPath() {
- return "";
- }
+ @Override
+ public String getApkPath() {
+ return "";
+ }
- /** @hide */
- @Override
- public int compareTo(XCallback other) {
- if (this == other)
- return 0;
+ /**
+ * @hide
+ */
+ @Override
+ public int compareTo(XCallback other) {
+ if (this == other)
+ return 0;
- // order descending by priority
- if (other.priority != this.priority)
- return other.priority - this.priority;
- // then randomly
- else if (System.identityHashCode(this) < System.identityHashCode(other))
- return -1;
- else
- return 1;
- }
+ // order descending by priority
+ if (other.priority != this.priority)
+ return other.priority - this.priority;
+ // then randomly
+ else if (System.identityHashCode(this) < System.identityHashCode(other))
+ return -1;
+ else
+ return 1;
+ }
- /** The default priority, see {@link #priority}. */
- public static final int PRIORITY_DEFAULT = 50;
+ /**
+ * The default priority, see {@link #priority}.
+ */
+ public static final int PRIORITY_DEFAULT = 50;
- /** Execute this callback late, see {@link #priority}. */
- public static final int PRIORITY_LOWEST = -10000;
+ /**
+ * Execute this callback late, see {@link #priority}.
+ */
+ public static final int PRIORITY_LOWEST = -10000;
- /** Execute this callback early, see {@link #priority}. */
- public static final int PRIORITY_HIGHEST = 10000;
+ /**
+ * Execute this callback early, see {@link #priority}.
+ */
+ public static final int PRIORITY_HIGHEST = 10000;
}
diff --git a/core/src/main/java/de/robv/android/xposed/services/BaseService.java b/core/src/main/java/de/robv/android/xposed/services/BaseService.java
index dfe47f11..7c69ee07 100644
--- a/core/src/main/java/de/robv/android/xposed/services/BaseService.java
+++ b/core/src/main/java/de/robv/android/xposed/services/BaseService.java
@@ -33,167 +33,178 @@ import de.robv.android.xposed.SELinuxHelper;
*
References to a concrete subclass should generally be retrieved from {@link SELinuxHelper}.
*/
public abstract class BaseService {
- /** Flag for {@link #checkFileAccess}: Read access. */
- public static final int R_OK = 4;
- /** Flag for {@link #checkFileAccess}: Write access. */
- public static final int W_OK = 2;
- /** Flag for {@link #checkFileAccess}: Executable access. */
- public static final int X_OK = 1;
- /** Flag for {@link #checkFileAccess}: File/directory exists. */
- public static final int F_OK = 0;
+ /**
+ * Flag for {@link #checkFileAccess}: Read access.
+ */
+ public static final int R_OK = 4;
+ /**
+ * Flag for {@link #checkFileAccess}: Write access.
+ */
+ public static final int W_OK = 2;
+ /**
+ * Flag for {@link #checkFileAccess}: Executable access.
+ */
+ public static final int X_OK = 1;
+ /**
+ * Flag for {@link #checkFileAccess}: File/directory exists.
+ */
+ public static final int F_OK = 0;
- /**
- * Checks whether the services accesses files directly (instead of using IPC).
- *
- * @return {@code true} in case direct access is possible.
- */
- public boolean hasDirectFileAccess() {
- return false;
- }
+ /**
+ * Checks whether the services accesses files directly (instead of using IPC).
+ *
+ * @return {@code true} in case direct access is possible.
+ */
+ public boolean hasDirectFileAccess() {
+ return false;
+ }
- /**
- * Check whether a file is accessible. SELinux might enforce stricter checks.
- *
- * @param filename The absolute path of the file to check.
- * @param mode The mode for POSIX's {@code access()} function.
- * @return The result of the {@code access()} function.
- */
- public abstract boolean checkFileAccess(String filename, int mode);
+ /**
+ * Check whether a file is accessible. SELinux might enforce stricter checks.
+ *
+ * @param filename The absolute path of the file to check.
+ * @param mode The mode for POSIX's {@code access()} function.
+ * @return The result of the {@code access()} function.
+ */
+ public abstract boolean checkFileAccess(String filename, int mode);
- /**
- * Check whether a file exists.
- *
- * @param filename The absolute path of the file to check.
- * @return The result of the {@code access()} function.
- */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- public boolean checkFileExists(String filename) {
- return checkFileAccess(filename, F_OK);
- }
+ /**
+ * Check whether a file exists.
+ *
+ * @param filename The absolute path of the file to check.
+ * @return The result of the {@code access()} function.
+ */
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ public boolean checkFileExists(String filename) {
+ return checkFileAccess(filename, F_OK);
+ }
- /**
- * Determine the size and modification time of a file.
- *
- * @param filename The absolute path of the file to check.
- * @return A {@link FileResult} object holding the result.
- * @throws IOException In case an error occurred while retrieving the information.
- */
- public abstract FileResult statFile(String filename) throws IOException;
+ /**
+ * Determine the size and modification time of a file.
+ *
+ * @param filename The absolute path of the file to check.
+ * @return A {@link FileResult} object holding the result.
+ * @throws IOException In case an error occurred while retrieving the information.
+ */
+ public abstract FileResult statFile(String filename) throws IOException;
- /**
- * Determine the size time of a file.
- *
- * @param filename The absolute path of the file to check.
- * @return The file size.
- * @throws IOException In case an error occurred while retrieving the information.
- */
- public long getFileSize(String filename) throws IOException {
- return statFile(filename).size;
- }
+ /**
+ * Determine the size time of a file.
+ *
+ * @param filename The absolute path of the file to check.
+ * @return The file size.
+ * @throws IOException In case an error occurred while retrieving the information.
+ */
+ public long getFileSize(String filename) throws IOException {
+ return statFile(filename).size;
+ }
- /**
- * Determine the size time of a file.
- *
- * @param filename The absolute path of the file to check.
- * @return The file modification time.
- * @throws IOException In case an error occurred while retrieving the information.
- */
- public long getFileModificationTime(String filename) throws IOException {
- return statFile(filename).mtime;
- }
+ /**
+ * Determine the size time of a file.
+ *
+ * @param filename The absolute path of the file to check.
+ * @return The file modification time.
+ * @throws IOException In case an error occurred while retrieving the information.
+ */
+ public long getFileModificationTime(String filename) throws IOException {
+ return statFile(filename).mtime;
+ }
- /**
- * Read a file into memory.
- *
- * @param filename The absolute path of the file to read.
- * @return A {@code byte} array with the file content.
- * @throws IOException In case an error occurred while reading the file.
- */
- public abstract byte[] readFile(String filename) throws IOException;
+ /**
+ * Read a file into memory.
+ *
+ * @param filename The absolute path of the file to read.
+ * @return A {@code byte} array with the file content.
+ * @throws IOException In case an error occurred while reading the file.
+ */
+ public abstract byte[] readFile(String filename) throws IOException;
- /**
- * Read a file into memory, but only if it has changed since the last time.
- *
- * @param filename The absolute path of the file to read.
- * @param previousSize File size of last read.
- * @param previousTime File modification time of last read.
- * @return A {@link FileResult} object holding the result.
- *
The {@link FileResult#content} field might be {@code null} if the file
- * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
- * @throws IOException In case an error occurred while reading the file.
- */
- public abstract FileResult readFile(String filename, long previousSize, long previousTime) throws IOException;
+ /**
+ * Read a file into memory, but only if it has changed since the last time.
+ *
+ * @param filename The absolute path of the file to read.
+ * @param previousSize File size of last read.
+ * @param previousTime File modification time of last read.
+ * @return A {@link FileResult} object holding the result.
+ *
The {@link FileResult#content} field might be {@code null} if the file
+ * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
+ * @throws IOException In case an error occurred while reading the file.
+ */
+ public abstract FileResult readFile(String filename, long previousSize, long previousTime) throws IOException;
- /**
- * Read a file into memory, optionally only if it has changed since the last time.
- *
- * @param filename The absolute path of the file to read.
- * @param offset Number of bytes to skip at the beginning of the file.
- * @param length Number of bytes to read (0 means read to end of file).
- * @param previousSize Optional: File size of last read.
- * @param previousTime Optional: File modification time of last read.
- * @return A {@link FileResult} object holding the result.
- *
The {@link FileResult#content} field might be {@code null} if the file
- * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
- * @throws IOException In case an error occurred while reading the file.
- */
- public abstract FileResult readFile(String filename, int offset, int length,
- long previousSize, long previousTime) throws IOException;
+ /**
+ * Read a file into memory, optionally only if it has changed since the last time.
+ *
+ * @param filename The absolute path of the file to read.
+ * @param offset Number of bytes to skip at the beginning of the file.
+ * @param length Number of bytes to read (0 means read to end of file).
+ * @param previousSize Optional: File size of last read.
+ * @param previousTime Optional: File modification time of last read.
+ * @return A {@link FileResult} object holding the result.
+ *
The {@link FileResult#content} field might be {@code null} if the file
+ * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
+ * @throws IOException In case an error occurred while reading the file.
+ */
+ public abstract FileResult readFile(String filename, int offset, int length,
+ long previousSize, long previousTime) throws IOException;
- /**
- * Get a stream to the file content.
- * Depending on the service, it may or may not be read completely into memory.
- *
- * @param filename The absolute path of the file to read.
- * @return An {@link InputStream} to the file content.
- * @throws IOException In case an error occurred while reading the file.
- */
- public InputStream getFileInputStream(String filename) throws IOException {
- return new ByteArrayInputStream(readFile(filename));
- }
+ /**
+ * Get a stream to the file content.
+ * Depending on the service, it may or may not be read completely into memory.
+ *
+ * @param filename The absolute path of the file to read.
+ * @return An {@link InputStream} to the file content.
+ * @throws IOException In case an error occurred while reading the file.
+ */
+ public InputStream getFileInputStream(String filename) throws IOException {
+ return new ByteArrayInputStream(readFile(filename));
+ }
- /**
- * Get a stream to the file content, but only if it has changed since the last time.
- * Depending on the service, it may or may not be read completely into memory.
- *
- * @param filename The absolute path of the file to read.
- * @param previousSize Optional: File size of last read.
- * @param previousTime Optional: File modification time of last read.
- * @return A {@link FileResult} object holding the result.
- *
The {@link FileResult#stream} field might be {@code null} if the file
- * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
- * @throws IOException In case an error occurred while reading the file.
- */
- public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
- FileResult result = readFile(filename, previousSize, previousTime);
- if (result.content == null)
- return result;
- return new FileResult(new ByteArrayInputStream(result.content), result.size, result.mtime);
- }
+ /**
+ * Get a stream to the file content, but only if it has changed since the last time.
+ * Depending on the service, it may or may not be read completely into memory.
+ *
+ * @param filename The absolute path of the file to read.
+ * @param previousSize Optional: File size of last read.
+ * @param previousTime Optional: File modification time of last read.
+ * @return A {@link FileResult} object holding the result.
+ *
The {@link FileResult#stream} field might be {@code null} if the file
+ * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
+ * @throws IOException In case an error occurred while reading the file.
+ */
+ public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
+ FileResult result = readFile(filename, previousSize, previousTime);
+ if (result.content == null)
+ return result;
+ return new FileResult(new ByteArrayInputStream(result.content), result.size, result.mtime);
+ }
- // ----------------------------------------------------------------------------
- /*package*/ BaseService() {}
+ // ----------------------------------------------------------------------------
+ /*package*/ BaseService() {
+ }
- /*package*/ static void ensureAbsolutePath(String filename) {
- if (!filename.startsWith("/")) {
- throw new IllegalArgumentException("Only absolute filenames are allowed: " + filename);
- }
- }
+ /*package*/
+ static void ensureAbsolutePath(String filename) {
+ if (!filename.startsWith("/")) {
+ throw new IllegalArgumentException("Only absolute filenames are allowed: " + filename);
+ }
+ }
- /*package*/ static void throwCommonIOException(int errno, String errorMsg, String filename, String defaultText) throws IOException {
- switch (errno) {
- case 1: // EPERM
- case 13: // EACCES
- throw new FileNotFoundException(errorMsg != null ? errorMsg : "Permission denied: " + filename);
- case 2: // ENOENT
- throw new FileNotFoundException(errorMsg != null ? errorMsg : "No such file or directory: " + filename);
- case 12: // ENOMEM
- throw new OutOfMemoryError(errorMsg);
- case 21: // EISDIR
- throw new FileNotFoundException(errorMsg != null ? errorMsg : "Is a directory: " + filename);
- default:
- throw new IOException(errorMsg != null ? errorMsg : "Error " + errno + defaultText + filename);
- }
- }
+ /*package*/
+ static void throwCommonIOException(int errno, String errorMsg, String filename, String defaultText) throws IOException {
+ switch (errno) {
+ case 1: // EPERM
+ case 13: // EACCES
+ throw new FileNotFoundException(errorMsg != null ? errorMsg : "Permission denied: " + filename);
+ case 2: // ENOENT
+ throw new FileNotFoundException(errorMsg != null ? errorMsg : "No such file or directory: " + filename);
+ case 12: // ENOMEM
+ throw new OutOfMemoryError(errorMsg);
+ case 21: // EISDIR
+ throw new FileNotFoundException(errorMsg != null ? errorMsg : "Is a directory: " + filename);
+ default:
+ throw new IOException(errorMsg != null ? errorMsg : "Error " + errno + defaultText + filename);
+ }
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/services/DirectAccessService.java b/core/src/main/java/de/robv/android/xposed/services/DirectAccessService.java
index 0b01749a..a8421c85 100644
--- a/core/src/main/java/de/robv/android/xposed/services/DirectAccessService.java
+++ b/core/src/main/java/de/robv/android/xposed/services/DirectAccessService.java
@@ -26,108 +26,110 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-/** @hide */
+/**
+ * @hide
+ */
public final class DirectAccessService extends BaseService {
- @Override
- public boolean hasDirectFileAccess() {
- return true;
- }
+ @Override
+ public boolean hasDirectFileAccess() {
+ return true;
+ }
- @SuppressWarnings("RedundantIfStatement")
- @Override
- public boolean checkFileAccess(String filename, int mode) {
- File file = new File(filename);
- if (mode == F_OK && !file.exists()) return false;
- if ((mode & R_OK) != 0 && !file.canRead()) return false;
- if ((mode & W_OK) != 0 && !file.canWrite()) return false;
- if ((mode & X_OK) != 0 && !file.canExecute()) return false;
- return true;
- }
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean checkFileAccess(String filename, int mode) {
+ File file = new File(filename);
+ if (mode == F_OK && !file.exists()) return false;
+ if ((mode & R_OK) != 0 && !file.canRead()) return false;
+ if ((mode & W_OK) != 0 && !file.canWrite()) return false;
+ if ((mode & X_OK) != 0 && !file.canExecute()) return false;
+ return true;
+ }
- @Override
- public boolean checkFileExists(String filename) {
- return new File(filename).exists();
- }
+ @Override
+ public boolean checkFileExists(String filename) {
+ return new File(filename).exists();
+ }
- @Override
- public FileResult statFile(String filename) throws IOException {
- File file = new File(filename);
- return new FileResult(file.length(), file.lastModified());
- }
+ @Override
+ public FileResult statFile(String filename) throws IOException {
+ File file = new File(filename);
+ return new FileResult(file.length(), file.lastModified());
+ }
- @Override
- public byte[] readFile(String filename) throws IOException {
- File file = new File(filename);
- byte content[] = new byte[(int)file.length()];
- FileInputStream fis = new FileInputStream(file);
- fis.read(content);
- fis.close();
- return content;
- }
+ @Override
+ public byte[] readFile(String filename) throws IOException {
+ File file = new File(filename);
+ byte content[] = new byte[(int) file.length()];
+ FileInputStream fis = new FileInputStream(file);
+ fis.read(content);
+ fis.close();
+ return content;
+ }
- @Override
- public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
- File file = new File(filename);
- long size = file.length();
- long time = file.lastModified();
- if (previousSize == size && previousTime == time)
- return new FileResult(size, time);
- return new FileResult(readFile(filename), size, time);
- }
+ @Override
+ public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
+ File file = new File(filename);
+ long size = file.length();
+ long time = file.lastModified();
+ if (previousSize == size && previousTime == time)
+ return new FileResult(size, time);
+ return new FileResult(readFile(filename), size, time);
+ }
- @Override
- public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
- File file = new File(filename);
- long size = file.length();
- long time = file.lastModified();
- if (previousSize == size && previousTime == time)
- return new FileResult(size, time);
+ @Override
+ public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
+ File file = new File(filename);
+ long size = file.length();
+ long time = file.lastModified();
+ if (previousSize == size && previousTime == time)
+ return new FileResult(size, time);
- // Shortcut for the simple case
- if (offset <= 0 && length <= 0)
- return new FileResult(readFile(filename), size, time);
+ // Shortcut for the simple case
+ if (offset <= 0 && length <= 0)
+ return new FileResult(readFile(filename), size, time);
- // Check range
- if (offset > 0 && offset >= size) {
- throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename);
- } else if (offset < 0) {
- offset = 0;
- }
+ // Check range
+ if (offset > 0 && offset >= size) {
+ throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename);
+ } else if (offset < 0) {
+ offset = 0;
+ }
- if (length > 0 && (offset + length) > size) {
- throw new IllegalArgumentException("Length " + length + " is out of range for " + filename);
- } else if (length <= 0) {
- length = (int) (size - offset);
- }
+ if (length > 0 && (offset + length) > size) {
+ throw new IllegalArgumentException("Length " + length + " is out of range for " + filename);
+ } else if (length <= 0) {
+ length = (int) (size - offset);
+ }
- byte content[] = new byte[length];
- FileInputStream fis = new FileInputStream(file);
- fis.skip(offset);
- fis.read(content);
- fis.close();
- return new FileResult(content, size, time);
- }
+ byte content[] = new byte[length];
+ FileInputStream fis = new FileInputStream(file);
+ fis.skip(offset);
+ fis.read(content);
+ fis.close();
+ return new FileResult(content, size, time);
+ }
- /**
- * {@inheritDoc}
- *
This implementation returns a BufferedInputStream instead of loading the file into memory.
- */
- @Override
- public InputStream getFileInputStream(String filename) throws IOException {
- return new BufferedInputStream(new FileInputStream(filename), 16*1024);
- }
+ /**
+ * {@inheritDoc}
+ *
This implementation returns a BufferedInputStream instead of loading the file into memory.
+ */
+ @Override
+ public InputStream getFileInputStream(String filename) throws IOException {
+ return new BufferedInputStream(new FileInputStream(filename), 16 * 1024);
+ }
- /**
- * {@inheritDoc}
- *
This implementation returns a BufferedInputStream instead of loading the file into memory.
- */
- @Override
- public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
- File file = new File(filename);
- long size = file.length();
- long time = file.lastModified();
- if (previousSize == size && previousTime == time)
- return new FileResult(size, time);
- return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16*1024), size, time);
- }
+ /**
+ * {@inheritDoc}
+ *
This implementation returns a BufferedInputStream instead of loading the file into memory.
+ */
+ @Override
+ public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
+ File file = new File(filename);
+ long size = file.length();
+ long time = file.lastModified();
+ if (previousSize == size && previousTime == time)
+ return new FileResult(size, time);
+ return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16 * 1024), size, time);
+ }
}
diff --git a/core/src/main/java/de/robv/android/xposed/services/FileResult.java b/core/src/main/java/de/robv/android/xposed/services/FileResult.java
index 5a7f80f1..415f5cb9 100644
--- a/core/src/main/java/de/robv/android/xposed/services/FileResult.java
+++ b/core/src/main/java/de/robv/android/xposed/services/FileResult.java
@@ -26,55 +26,65 @@ import java.io.InputStream;
* Holder for the result of a {@link BaseService#readFile} or {@link BaseService#statFile} call.
*/
public final class FileResult {
- /** File content, might be {@code null} if the file wasn't read. */
- public final byte[] content;
- /** File input stream, might be {@code null} if the file wasn't read. */
- public final InputStream stream;
- /** File size. */
- public final long size;
- /** File last modification time. */
- public final long mtime;
+ /**
+ * File content, might be {@code null} if the file wasn't read.
+ */
+ public final byte[] content;
+ /**
+ * File input stream, might be {@code null} if the file wasn't read.
+ */
+ public final InputStream stream;
+ /**
+ * File size.
+ */
+ public final long size;
+ /**
+ * File last modification time.
+ */
+ public final long mtime;
- /*package*/ FileResult(long size, long mtime) {
- this.content = null;
- this.stream = null;
- this.size = size;
- this.mtime = mtime;
- }
+ /*package*/ FileResult(long size, long mtime) {
+ this.content = null;
+ this.stream = null;
+ this.size = size;
+ this.mtime = mtime;
+ }
- /*package*/ FileResult(byte[] content, long size, long mtime) {
- this.content = content;
- this.stream = null;
- this.size = size;
- this.mtime = mtime;
- }
+ /*package*/ FileResult(byte[] content, long size, long mtime) {
+ this.content = content;
+ this.stream = null;
+ this.size = size;
+ this.mtime = mtime;
+ }
- /*package*/ FileResult(InputStream stream, long size, long mtime) {
- this.content = null;
- this.stream = stream;
- this.size = size;
- this.mtime = mtime;
- }
+ /*package*/ FileResult(InputStream stream, long size, long mtime) {
+ this.content = null;
+ this.stream = stream;
+ this.size = size;
+ this.mtime = mtime;
+ }
- /** @hide */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder("{");
- if (content != null) {
- sb.append("content.length: ");
- sb.append(content.length);
- sb.append(", ");
- }
- if (stream != null) {
- sb.append("stream: ");
- sb.append(stream.toString());
- sb.append(", ");
- }
- sb.append("size: ");
- sb.append(size);
- sb.append(", mtime: ");
- sb.append(mtime);
- sb.append("}");
- return sb.toString();
- }
+ /**
+ * @hide
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("{");
+ if (content != null) {
+ sb.append("content.length: ");
+ sb.append(content.length);
+ sb.append(", ");
+ }
+ if (stream != null) {
+ sb.append("stream: ");
+ sb.append(stream.toString());
+ sb.append(", ");
+ }
+ sb.append("size: ");
+ sb.append(size);
+ sb.append(", mtime: ");
+ sb.append(mtime);
+ sb.append("}");
+ return sb.toString();
+ }
}
diff --git a/core/src/main/java/org/lsposed/lspd/core/Main.java b/core/src/main/java/org/lsposed/lspd/core/Main.java
index 9ee95bcd..3e13cd16 100644
--- a/core/src/main/java/org/lsposed/lspd/core/Main.java
+++ b/core/src/main/java/org/lsposed/lspd/core/Main.java
@@ -79,15 +79,15 @@ public class Main {
// Initialize the Xposed framework
try {
startBootstrapHook(isSystem, appDataDir);
- XposedInit.initForZygote();
+ XposedInit.hookResources();
} catch (Throwable t) {
Utils.logE("error during Xposed initialization", t);
}
}
- private static void loadModulesSafely(boolean callInitZygote) {
+ private static void loadModulesSafely() {
try {
- XposedInit.loadModules(callInitZygote);
+ XposedInit.loadModules();
} catch (Exception exception) {
Utils.logE("error loading module list", exception);
}
@@ -101,7 +101,7 @@ public class Main {
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
installBootstrapHooks(isSystem, appDataDir);
Utils.logI("Loading modules for " + niceName);
- loadModulesSafely(true);
+ loadModulesSafely();
}
public static void forkAndSpecializePost(String appDataDir, String niceName, IBinder binder) {
diff --git a/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java
index 3ac3556f..17352000 100644
--- a/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java
+++ b/core/src/main/java/org/lsposed/lspd/hooker/HandleBindAppHooker.java
@@ -48,7 +48,7 @@ import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
// normal process initialization (for new Activity, Service, BroadcastReceiver etc.)
public class HandleBindAppHooker extends XC_MethodHook {
- String appDataDir = null;
+ String appDataDir;
public HandleBindAppHooker(String appDataDir) {
this.appDataDir = appDataDir;
diff --git a/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java
index 86265522..02b7ed42 100644
--- a/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java
+++ b/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java
@@ -20,8 +20,6 @@
package org.lsposed.lspd.hooker;
-import android.os.Build;
-
import org.lsposed.lspd.util.Hookers;
import de.robv.android.xposed.XC_MethodHook;
@@ -60,7 +58,7 @@ public class StartBootstrapServicesHooker extends XC_MethodHook {
}
try {
- String className = "com.android.server.pm." + (Build.VERSION.SDK_INT >= 23 ? "PackageDexOptimizer" : "PackageManagerService");
+ String className = "com.android.server.pm.PackageDexOptimizer";
findAndHookMethod(className, SystemMainHooker.systemServerCL,
"dexEntryExists", String.class,
XC_MethodReplacement.returnConstant(true));