From d7897b67d9be068799a2a59f861dc0e3ede0bc78 Mon Sep 17 00:00:00 2001 From: tehcneko <7764726+tehcneko@users.noreply.github.com> Date: Sat, 24 Apr 2021 14:59:35 +0800 Subject: [PATCH] [core] Remove useless codes (#505) * [core] Replace tabs with spaces * [core] Remove useless codes --- .../android/xposed/IXposedHookCmdInit.java | 36 +- .../IXposedHookInitPackageResources.java | 52 +- .../xposed/IXposedHookLoadPackage.java | 55 +- .../android/xposed/IXposedHookZygoteInit.java | 50 +- .../de/robv/android/xposed/IXposedMod.java | 7 +- .../de/robv/android/xposed/SELinuxHelper.java | 75 +- .../de/robv/android/xposed/XC_MethodHook.java | 280 +- .../android/xposed/XC_MethodReplacement.java | 147 +- .../android/xposed/XSharedPreferences.java | 19 +- .../de/robv/android/xposed/XposedBridge.java | 653 ++-- .../de/robv/android/xposed/XposedHelpers.java | 3287 +++++++++-------- .../de/robv/android/xposed/XposedInit.java | 73 +- .../android/xposed/callbacks/IXUnhook.java | 16 +- .../callbacks/XC_InitPackageResources.java | 85 +- .../xposed/callbacks/XC_LayoutInflated.java | 156 +- .../xposed/callbacks/XC_LoadPackage.java | 99 +- .../android/xposed/callbacks/XCallback.java | 255 +- .../android/xposed/services/BaseService.java | 307 +- .../xposed/services/DirectAccessService.java | 184 +- .../android/xposed/services/FileResult.java | 104 +- .../main/java/org/lsposed/lspd/core/Main.java | 8 +- .../lspd/hooker/HandleBindAppHooker.java | 2 +- .../hooker/StartBootstrapServicesHooker.java | 4 +- 23 files changed, 3080 insertions(+), 2874 deletions(-) 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: - *

- * - * @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: + *

+ * + * @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 callbacks) { - this.callbacks = callbacks.getSnapshot(); - } + /** + * @hide + */ + protected Param(CopyOnWriteSortedSet 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));