[core] Remove useless codes (#505)

* [core] Replace tabs with spaces

* [core] Remove useless codes
This commit is contained in:
tehcneko 2021-04-24 14:59:35 +08:00 committed by GitHub
parent 86304f11ce
commit d7897b67d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 3080 additions and 2874 deletions

View File

@ -28,21 +28,29 @@ package de.robv.android.xposed;
* implemented anymore. * implemented anymore.
*/ */
public interface IXposedHookCmdInit extends IXposedMod { public interface IXposedHookCmdInit extends IXposedMod {
/** /**
* Called very early during startup of a command-line tool. * 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. * @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; */
void initCmdApp(StartupParam startupParam) throws Throwable;
/** Data holder for {@link #initCmdApp}. */ /**
final class StartupParam { * Data holder for {@link #initCmdApp}.
/*package*/ StartupParam() {} */
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;
}
} }

View File

@ -33,33 +33,35 @@ import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResou
* registering it as a callback automatically. * registering it as a callback automatically.
*/ */
public interface IXposedHookInitPackageResources extends IXposedMod { public interface IXposedHookInitPackageResources extends IXposedMod {
/** /**
* This method is called when resources for an app are being initialized. * 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. * Modules can call special methods of the {@link XResources} class in order to replace resources.
* *
* @param resparam Information about the resources. * @param resparam Information about the resources.
* @throws Throwable Everything the callback throws is caught and logged. * @throws Throwable Everything the callback throws is caught and logged.
*/ */
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable; void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
/** @hide */ /**
final class Wrapper extends XC_InitPackageResources { * @hide
private final IXposedHookInitPackageResources instance; */
private final String apkPath; final class Wrapper extends XC_InitPackageResources {
private final IXposedHookInitPackageResources instance;
private final String apkPath;
public Wrapper(IXposedHookInitPackageResources instance, String apkPath) { public Wrapper(IXposedHookInitPackageResources instance, String apkPath) {
this.instance = instance; this.instance = instance;
this.apkPath = apkPath; this.apkPath = apkPath;
} }
@Override @Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable { public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
instance.handleInitPackageResources(resparam); instance.handleInitPackageResources(resparam);
} }
@Override @Override
public String getApkPath() { public String getApkPath() {
return apkPath; return apkPath;
} }
} }
} }

View File

@ -33,33 +33,36 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
* registering it as a callback automatically. * registering it as a callback automatically.
*/ */
public interface IXposedHookLoadPackage extends IXposedMod { public interface IXposedHookLoadPackage extends IXposedMod {
/** /**
* This method is called when an app is loaded. It's called very early, even before * This method is called when an app is loaded. It's called very early, even before
* {@link Application#onCreate} is called. * {@link Application#onCreate} is called.
* Modules can set up their app-specific hooks here. * Modules can set up their app-specific hooks here.
* *
* @param lpparam Information about the app. * @param lpparam Information about the app.
* @throws Throwable Everything the callback throws is caught and logged. * @throws Throwable Everything the callback throws is caught and logged.
*/ */
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable; void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
/** @hide */ /**
final class Wrapper extends XC_LoadPackage { * @hide
private final IXposedHookLoadPackage instance; */
private final String apkPath; final class Wrapper extends XC_LoadPackage {
private final IXposedHookLoadPackage instance;
private final String apkPath;
public Wrapper(IXposedHookLoadPackage instance, String apkPath) { public Wrapper(IXposedHookLoadPackage instance, String apkPath) {
this.instance = instance; this.instance = instance;
this.apkPath = apkPath; this.apkPath = apkPath;
} }
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
instance.handleLoadPackage(lpparam);
}
@Override @Override
public String getApkPath() { public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
return apkPath; instance.handleLoadPackage(lpparam);
} }
}
@Override
public String getApkPath() {
return apkPath;
}
}
} }

View File

@ -35,16 +35,20 @@ import de.robv.android.xposed.callbacks.XCallback;
* <p>If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead. * <p>If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead.
*/ */
public interface IXposedHookZygoteInit extends IXposedMod { public interface IXposedHookZygoteInit extends IXposedMod {
/** /**
* Called very early during startup of Zygote. * 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. * @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; */
void initZygote(StartupParam startupParam) throws Throwable;
/** Data holder for {@link #initZygote}. */ /**
final class StartupParam extends XCallback.Param { * Data holder for {@link #initZygote}.
/*package*/ StartupParam() {} */
final class StartupParam extends XCallback.Param {
/*package*/ StartupParam() {
}
/** /**
* @param callbacks * @param callbacks
@ -54,15 +58,17 @@ public interface IXposedHookZygoteInit extends IXposedMod {
super(callbacks); 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 * Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
* process that starts the system_server. * process that starts the system_server.
*/ */
public boolean startsSystemServer; public boolean startsSystemServer;
} }
/** /**
* @hide * @hide
@ -83,9 +89,9 @@ public interface IXposedHookZygoteInit extends IXposedMod {
instance.initZygote(this.startupParam); instance.initZygote(this.startupParam);
} }
@Override @Override
public String getApkPath() { public String getApkPath() {
return startupParam.modulePath; return startupParam.modulePath;
} }
} }
} }

View File

@ -20,5 +20,8 @@
package de.robv.android.xposed; 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 {
}

View File

@ -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. * A helper to work with (or without) SELinux, abstracting much of its big complexity.
*/ */
public final class SELinuxHelper { public final class SELinuxHelper {
private SELinuxHelper() {} private SELinuxHelper() {
}
/** /**
* Determines whether SELinux is disabled or enabled. * Determines whether SELinux is disabled or enabled.
* *
* @return A boolean indicating whether SELinux is enabled. * @return A boolean indicating whether SELinux is enabled.
*/ */
public static boolean isSELinuxEnabled() { public static boolean isSELinuxEnabled() {
// lsp: always enabled // lsp: always enabled
return true; return true;
} }
/** /**
* Determines whether SELinux is permissive or enforcing. * Determines whether SELinux is permissive or enforcing.
* *
* @return A boolean indicating whether SELinux is enforcing. * @return A boolean indicating whether SELinux is enforcing.
*/ */
public static boolean isSELinuxEnforced() { public static boolean isSELinuxEnforced() {
// lsp: always enforcing // lsp: always enforcing
return true; return true;
} }
/** /**
* Gets the security context of the current process. * Gets the security context of the current process.
* *
* @return A String representing the security context of the current process. * @return A String representing the security context of the current process.
*/ */
public static String getContext() { public static String getContext() {
return null; return null;
} }
/** /**
* Retrieve the service to be used when accessing files in {@code /data/data/*}. * Retrieve the service to be used when accessing files in {@code /data/data/*}.
* *
* <p class="caution"><strong>IMPORTANT:</strong> If you call this from the Zygote process, * <p class="caution"><strong>IMPORTANT:</strong> If you call this from the Zygote process,
* don't re-use the result in different process! * don't re-use the result in different process!
* *
* @return An instance of the service. * @return An instance of the service.
*/ */
public static BaseService getAppDataFileService() { public static BaseService getAppDataFileService() {
return sServiceAppDataFile; return sServiceAppDataFile;
} }
private static final BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly private static final BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
} }

View File

@ -32,157 +32,175 @@ import de.robv.android.xposed.callbacks.XCallback;
* {@link #beforeHookedMethod} and/or {@link #afterHookedMethod}. * {@link #beforeHookedMethod} and/or {@link #afterHookedMethod}.
*/ */
public abstract class XC_MethodHook extends XCallback { public abstract class XC_MethodHook extends XCallback {
/** /**
* Creates a new callback with default priority. * Creates a new callback with default priority.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public XC_MethodHook() { public XC_MethodHook() {
super(); super();
} }
/** /**
* Creates a new callback with a specific priority. * Creates a new callback with a specific priority.
* *
* <p class="note">Note that {@link #afterHookedMethod} will be called in reversed order, i.e. * <p class="note">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 * 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. * final control over the return value. {@link #beforeHookedMethod} is called as usual, i.e.
* highest priority first. * highest priority first.
* *
* @param priority See {@link XCallback#priority}. * @param priority See {@link XCallback#priority}.
*/ */
public XC_MethodHook(int priority) { public XC_MethodHook(int priority) {
super(priority); super(priority);
} }
/** /**
* Called before the invocation of the method. * Called before the invocation of the method.
* *
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable} * <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to prevent the original method from being called. * to prevent the original method from being called.
* *
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary. * <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
* *
* @param param Information about the method call. * @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged. * @throws Throwable Everything the callback throws is caught and logged.
*/ */
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {} protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
}
public void callBeforeHookedMethod(MethodHookParam param) throws Throwable { public void callBeforeHookedMethod(MethodHookParam param) throws Throwable {
beforeHookedMethod(param); beforeHookedMethod(param);
} }
/** /**
* Called after the invocation of the method. * Called after the invocation of the method.
* *
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable} * <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to modify the return value of the original method. * to modify the return value of the original method.
* *
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary. * <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
* *
* @param param Information about the method call. * @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged. * @throws Throwable Everything the callback throws is caught and logged.
*/ */
protected void afterHookedMethod(MethodHookParam param) throws Throwable {} protected void afterHookedMethod(MethodHookParam param) throws Throwable {
}
public void callAfterHookedMethod(MethodHookParam param) throws Throwable { public void callAfterHookedMethod(MethodHookParam param) throws Throwable {
afterHookedMethod(param); afterHookedMethod(param);
} }
/** /**
* Wraps information about the method call and allows to influence it. * Wraps information about the method call and allows to influence it.
*/ */
public static final class MethodHookParam extends XCallback.Param { public static final class MethodHookParam extends XCallback.Param {
/** @hide */ /**
@SuppressWarnings("deprecation") * @hide
public MethodHookParam() { */
super(); @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 Object result = null;
private Throwable throwable = null; private Throwable throwable = null;
public boolean returnEarly = false; public boolean returnEarly = false;
/** Returns the result of the method call. */ /**
public Object getResult() { * Returns the result of the method call.
return result; */
} public Object getResult() {
return result;
}
/** /**
* Modify the result of the method call. * Modify the result of the method call.
* *
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method. * <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
*/ */
public void setResult(Object result) { public void setResult(Object result) {
this.result = result; this.result = result;
this.throwable = null; this.throwable = null;
this.returnEarly = true; this.returnEarly = true;
} }
/** Returns the {@link Throwable} thrown by the method, or {@code null}. */ /**
public Throwable getThrowable() { * Returns the {@link Throwable} thrown by the method, or {@code null}.
return throwable; */
} public Throwable getThrowable() {
return throwable;
}
/** Returns true if an exception was thrown by the method. */ /**
public boolean hasThrowable() { * Returns true if an exception was thrown by the method.
return throwable != null; */
} public boolean hasThrowable() {
return throwable != null;
}
/** /**
* Modify the exception thrown of the method call. * Modify the exception thrown of the method call.
* *
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method. * <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
*/ */
public void setThrowable(Throwable throwable) { public void setThrowable(Throwable throwable) {
this.throwable = throwable; this.throwable = throwable;
this.result = null; this.result = null;
this.returnEarly = true; this.returnEarly = true;
} }
/** Returns the result of the method call, or throws the Throwable caused by it. */ /**
public Object getResultOrThrowable() throws Throwable { * Returns the result of the method call, or throws the Throwable caused by it.
if (throwable != null) */
throw throwable; public Object getResultOrThrowable() throws Throwable {
return result; if (throwable != null)
} throw throwable;
} return result;
}
}
/** /**
* An object with which the method/constructor can be unhooked. * An object with which the method/constructor can be unhooked.
*/ */
public class Unhook implements IXUnhook<XC_MethodHook> { public class Unhook implements IXUnhook<XC_MethodHook> {
private final Member hookMethod; private final Member hookMethod;
/*package*/ Unhook(Member hookMethod) { /*package*/ Unhook(Member hookMethod) {
this.hookMethod = hookMethod; this.hookMethod = hookMethod;
} }
/** /**
* Returns the method/constructor that has been hooked. * Returns the method/constructor that has been hooked.
*/ */
public Member getHookedMethod() { public Member getHookedMethod() {
return hookMethod; return hookMethod;
} }
@Override @Override
public XC_MethodHook getCallback() { public XC_MethodHook getCallback() {
return XC_MethodHook.this; return XC_MethodHook.this;
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void unhook() { public void unhook() {
XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this); XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
} }
} }
} }

View File

@ -26,83 +26,88 @@ import de.robv.android.xposed.callbacks.XCallback;
* A special case of {@link XC_MethodHook} which completely replaces the original method. * A special case of {@link XC_MethodHook} which completely replaces the original method.
*/ */
public abstract class XC_MethodReplacement extends XC_MethodHook { public abstract class XC_MethodReplacement extends XC_MethodHook {
/** /**
* Creates a new callback with default priority. * Creates a new callback with default priority.
*/ */
public XC_MethodReplacement() { public XC_MethodReplacement() {
super(); super();
} }
/** /**
* Creates a new callback with a specific priority. * Creates a new callback with a specific priority.
* *
* @param priority See {@link XCallback#priority}. * @param priority See {@link XCallback#priority}.
*/ */
public XC_MethodReplacement(int priority) { public XC_MethodReplacement(int priority) {
super(priority); super(priority);
} }
/** @hide */ /**
@Override * @hide
protected final void beforeHookedMethod(MethodHookParam param) throws Throwable { */
try { @Override
Object result = replaceHookedMethod(param); protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(result); try {
} catch (Throwable t) { Object result = replaceHookedMethod(param);
XposedBridge.log(t); param.setResult(result);
param.setThrowable(t); } catch (Throwable t) {
} XposedBridge.log(t);
} param.setThrowable(t);
}
}
/** @hide */ /**
@Override * @hide
@SuppressWarnings("EmptyMethod") */
protected final void afterHookedMethod(MethodHookParam param) throws Throwable {} @Override
@SuppressWarnings("EmptyMethod")
protected final void afterHookedMethod(MethodHookParam param) throws Throwable {
}
/** /**
* Shortcut for replacing a method completely. Whatever is returned/thrown here is taken * 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). * instead of the result of the original method (which will not be called).
* *
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary. * <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
* *
* @param param Information about the method call. * @param param Information about the method call.
* @throws Throwable Anything that is thrown by the callback will be passed on to the original caller. * @throws Throwable Anything that is thrown by the callback will be passed on to the original caller.
*/ */
@SuppressWarnings("UnusedParameters") @SuppressWarnings("UnusedParameters")
protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable; protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable;
/** /**
* Predefined callback that skips the method without replacements. * Predefined callback that skips the method without replacements.
*/ */
public static final XC_MethodReplacement DO_NOTHING = new XC_MethodReplacement(PRIORITY_HIGHEST*2) { public static final XC_MethodReplacement DO_NOTHING = new XC_MethodReplacement(PRIORITY_HIGHEST * 2) {
@Override @Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return null; return null;
} }
}; };
/** /**
* Creates a callback which always returns a specific value. * Creates a callback which always returns a specific value.
* *
* @param result The value that should be returned to callers of the hooked method. * @param result The value that should be returned to callers of the hooked method.
*/ */
public static XC_MethodReplacement returnConstant(final Object result) { public static XC_MethodReplacement returnConstant(final Object result) {
return returnConstant(PRIORITY_DEFAULT, result); return returnConstant(PRIORITY_DEFAULT, result);
} }
/** /**
* Like {@link #returnConstant(Object)}, but allows to specify a priority for the callback. * Like {@link #returnConstant(Object)}, but allows to specify a priority for the callback.
* *
* @param priority See {@link XCallback#priority}. * @param priority See {@link XCallback#priority}.
* @param result The value that should be returned to callers of the hooked method. * @param result The value that should be returned to callers of the hooked method.
*/ */
public static XC_MethodReplacement returnConstant(int priority, final Object result) { public static XC_MethodReplacement returnConstant(int priority, final Object result) {
return new XC_MethodReplacement(priority) { return new XC_MethodReplacement(priority) {
@Override @Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return result; return result;
} }
}; };
} }
} }

View File

@ -28,6 +28,7 @@ import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import com.android.internal.util.XmlUtils; import com.android.internal.util.XmlUtils;
import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.util.MetaDataReader; import org.lsposed.lspd.util.MetaDataReader;
@ -99,7 +100,8 @@ public final class XSharedPreferences implements SharedPreferences {
Path dir = (Path) key.watchable(); Path dir = (Path) key.watchable();
Path path = dir.resolve((Path) event.context()); Path path = dir.resolve((Path) event.context());
String pathStr = path.toString(); 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 // We react to both real and backup files due to rare race conditions
if (pathStr.endsWith(".bak")) { if (pathStr.endsWith(".bak")) {
if (kind != StandardWatchEventKinds.ENTRY_DELETE) { if (kind != StandardWatchEventKinds.ENTRY_DELETE) {
@ -114,7 +116,8 @@ public final class XSharedPreferences implements SharedPreferences {
try { try {
l.onSharedPreferenceChanged(data.mPrefs, null); l.onSharedPreferenceChanged(data.mPrefs, null);
} catch (Throwable t) { } 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) { if (newModule) {
mFile = new File(serviceClient.getPrefsPath( packageName ), prefFileName + ".xml"); mFile = new File(serviceClient.getPrefsPath(packageName), prefFileName + ".xml");
} else { } else {
mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml"); 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()) { if (sWatcherDaemon == null || !sWatcherDaemon.isAlive()) {
initWatcherDaemon(); 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) { } catch (AccessDeniedException accDeniedEx) {
if (BuildConfig.DEBUG) Log.e(TAG, "tryRegisterWatcher: access denied to " + path); if (BuildConfig.DEBUG) Log.e(TAG, "tryRegisterWatcher: access denied to " + path);
} catch (Exception e) { } catch (Exception e) {
@ -232,7 +236,8 @@ public final class XSharedPreferences implements SharedPreferences {
if (!atLeastOneValid) { if (!atLeastOneValid) {
try { try {
sWatcher.close(); sWatcher.close();
} catch (Exception ignore) { } } catch (Exception ignore) {
}
} }
} }
} }
@ -522,7 +527,7 @@ public final class XSharedPreferences implements SharedPreferences {
if (listener == null) if (listener == null)
throw new IllegalArgumentException("listener cannot be null"); throw new IllegalArgumentException("listener cannot be null");
synchronized(this) { synchronized (this) {
if (mListeners.put(listener, sContent) == null) { if (mListeners.put(listener, sContent) == null) {
tryRegisterWatcher(); tryRegisterWatcher();
} }
@ -537,7 +542,7 @@ public final class XSharedPreferences implements SharedPreferences {
*/ */
@Override @Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(this) { synchronized (this) {
if (mListeners.remove(listener) != null && mListeners.isEmpty()) { if (mListeners.remove(listener) != null && mListeners.isEmpty()) {
tryUnregisterWatcher(); tryUnregisterWatcher();
} }

View File

@ -20,11 +20,16 @@
package de.robv.android.xposed; package de.robv.android.xposed;
import static de.robv.android.xposed.XposedHelpers.setObjectField;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.util.Log; import android.util.Log;
import org.lsposed.lspd.BuildConfig; 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.AccessibleObject;
import java.lang.reflect.Executable; 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_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitZygote; import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XC_LoadPackage; 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 * 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") @SuppressWarnings("JniMissingFunction")
public final class XposedBridge { public final class XposedBridge {
/** /**
* The system class loader which can be used to locate Android framework classes. * The system class loader which can be used to locate Android framework classes.
* Application classes cannot be retrieved from it. * Application classes cannot be retrieved from it.
* *
* @see ClassLoader#getSystemClassLoader * @see ClassLoader#getSystemClassLoader
*/ */
public static final ClassLoader BOOTCLASSLOADER = XposedBridge.class.getClassLoader(); 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 * @deprecated Use {@link #getXposedVersion()} instead.
public static int XPOSED_BRIDGE_VERSION; */
@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. // built-in handlers
/*package*/ static long BOOT_START_TIME; private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new NoValuesHashMap<>();
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
/*package*/ static final CopyOnWriteSortedSet<XC_InitZygote> sInitZygoteCallbacks = new CopyOnWriteSortedSet<>();
private static final Object[] EMPTY_ARRAY = new Object[0]; private XposedBridge() {
}
// built-in handlers public static volatile ClassLoader dummyClassLoader = null;
private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new NoValuesHashMap<>();
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
/*package*/ static final CopyOnWriteSortedSet<XC_InitZygote> sInitZygoteCallbacks = new CopyOnWriteSortedSet<>();
private XposedBridge() {} public static void initXResources() {
public static volatile ClassLoader dummyClassLoader = null;
public static void initXResources() {
if (dummyClassLoader != null) { if (dummyClassLoader != null) {
return; return;
} }
try { try {
Resources res = Resources.getSystem(); Resources res = Resources.getSystem();
Class<?> resClass = res.getClass(); Class<?> resClass = res.getClass();
Class<?> taClass = TypedArray.class; Class<?> taClass = TypedArray.class;
try { try {
TypedArray ta = res.obtainTypedArray(res.getIdentifier( TypedArray ta = res.obtainTypedArray(res.getIdentifier(
"preloaded_drawables", "array", "android")); "preloaded_drawables", "array", "android"));
taClass = ta.getClass(); taClass = ta.getClass();
ta.recycle(); ta.recycle();
} catch (Resources.NotFoundException nfe) { } catch (Resources.NotFoundException nfe) {
XposedBridge.log(nfe); XposedBridge.log(nfe);
} }
ResourcesHook.removeFinalFlagNative(resClass); ResourcesHook.removeFinalFlagNative(resClass);
ResourcesHook.removeFinalFlagNative(taClass); ResourcesHook.removeFinalFlagNative(taClass);
ClassLoader myCL = XposedBridge.class.getClassLoader(); ClassLoader myCL = XposedBridge.class.getClassLoader();
dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass, taClass); dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass, taClass);
dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass"); dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass");
dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass"); dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
setObjectField(myCL, "parent", dummyClassLoader); setObjectField(myCL, "parent", dummyClassLoader);
} catch (Throwable throwable) { } catch (Throwable throwable) {
XposedBridge.log(throwable); XposedBridge.log(throwable);
XposedInit.disableResources = true; XposedInit.disableResources = true;
} }
} }
/** /**
* Returns the currently installed version of the Xposed framework. * Returns the currently installed version of the Xposed framework.
*/ */
public static int getXposedVersion() { public static int getXposedVersion() {
return BuildConfig.API_CODE; return BuildConfig.API_CODE;
} }
/** /**
* Writes a message to the Xposed modules log. * Writes a message to the Xposed modules log.
* *
* <p class="warning"><b>DON'T FLOOD THE LOG!!!</b> This is only meant for error logging. * <p class="warning"><b>DON'T FLOOD THE LOG!!!</b> This is only meant for error logging.
* If you want to write information/debug messages, use logcat. * If you want to write information/debug messages, use logcat.
* *
* @param text The log message. * @param text The log message.
*/ */
public synchronized static void log(String text) { public synchronized static void log(String text) {
Log.i(TAG, text); Log.i(TAG, text);
ModuleLogger.log(text, false); ModuleLogger.log(text, false);
} }
/** /**
* Logs a stack trace to the Xposed modules log. * Logs a stack trace to the Xposed modules log.
* *
* <p class="warning"><b>DON'T FLOOD THE LOG!!!</b> This is only meant for error logging. * <p class="warning"><b>DON'T FLOOD THE LOG!!!</b> This is only meant for error logging.
* If you want to write information/debug messages, use logcat. * If you want to write information/debug messages, use logcat.
* *
* @param t The Throwable object for the stack trace. * @param t The Throwable object for the stack trace.
*/ */
public synchronized static void log(Throwable t) { public synchronized static void log(Throwable t) {
String logStr = Log.getStackTraceString(t); String logStr = Log.getStackTraceString(t);
Log.e(TAG, logStr); Log.e(TAG, logStr);
ModuleLogger.log(logStr, true); ModuleLogger.log(logStr, true);
} }
/** /**
* Hook any method (or constructor) with the specified callback. See below for some wrappers * 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. * that make it easier to find a method/constructor in one step.
* *
* @param hookMethod The method to be hooked. * @param hookMethod The method to be hooked.
* @param callback The callback to be executed when the hooked method is called. * @param callback The callback to be executed when the hooked method is called.
* @return An object that can be used to remove the hook. * @return An object that can be used to remove the hook.
* * @see XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)
* @see XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...) * @see XposedHelpers#findAndHookMethod(Class, String, Object...)
* @see XposedHelpers#findAndHookMethod(Class, String, Object...) * @see #hookAllMethods
* @see #hookAllMethods * @see XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...)
* @see XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...) * @see XposedHelpers#findAndHookConstructor(Class, Object...)
* @see XposedHelpers#findAndHookConstructor(Class, Object...) * @see #hookAllConstructors
* @see #hookAllConstructors */
*/ public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) { if (!(hookMethod instanceof Executable)) {
if (!(hookMethod instanceof Executable)) { throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString()); }
} // No check interface because there may be default methods
// No check interface because there may be default methods
/*else if (hookMethod.getDeclaringClass().isInterface()) { /*else if (hookMethod.getDeclaringClass().isInterface()) {
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString()); throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
}*/ }*/
else if (Modifier.isAbstract(hookMethod.getModifiers())) { else if (Modifier.isAbstract(hookMethod.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString()); throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
} }
Executable targetMethod = (Executable) hookMethod; Executable targetMethod = (Executable) hookMethod;
if (callback == null) { if (callback == null) {
throw new IllegalArgumentException("callback should not be null!"); throw new IllegalArgumentException("callback should not be null!");
} }
boolean newMethod = false; boolean newMethod = false;
CopyOnWriteSortedSet<XC_MethodHook> callbacks; CopyOnWriteSortedSet<XC_MethodHook> callbacks;
synchronized (sHookedMethodCallbacks) { synchronized (sHookedMethodCallbacks) {
callbacks = sHookedMethodCallbacks.get(targetMethod); callbacks = sHookedMethodCallbacks.get(targetMethod);
if (callbacks == null) { if (callbacks == null) {
callbacks = new CopyOnWriteSortedSet<>(); callbacks = new CopyOnWriteSortedSet<>();
sHookedMethodCallbacks.put(targetMethod, callbacks); sHookedMethodCallbacks.put(targetMethod, callbacks);
newMethod = true; newMethod = true;
} }
} }
callbacks.add(callback); callbacks.add(callback);
if (newMethod) { if (newMethod) {
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks); AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
if (!YahfaHooker.shouldDelayHook(targetMethod)) { if (!YahfaHooker.shouldDelayHook(targetMethod)) {
YahfaHooker.hookMethod(targetMethod, additionalInfo); YahfaHooker.hookMethod(targetMethod, additionalInfo);
} else { } else {
PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo); PendingHooks.recordPendingMethod((Method) hookMethod, additionalInfo);
} }
} }
return callback.new Unhook(hookMethod); return callback.new Unhook(hookMethod);
} }
/** /**
* Removes the callback for a hooked method/constructor. * Removes the callback for a hooked method/constructor.
* *
* @deprecated Use {@link XC_MethodHook.Unhook#unhook} instead. An instance of the {@code Unhook} * @param hookMethod The method for which the callback should be removed.
* class is returned when you hook the method. * @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}
* @param hookMethod The method for which the callback should be removed. * class is returned when you hook the method.
* @param callback The reference to the callback as specified in {@link #hookMethod}. */
*/ @Deprecated
@Deprecated public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) { CopyOnWriteSortedSet<XC_MethodHook> callbacks;
CopyOnWriteSortedSet<XC_MethodHook> callbacks; synchronized (sHookedMethodCallbacks) {
synchronized (sHookedMethodCallbacks) { callbacks = sHookedMethodCallbacks.get(hookMethod);
callbacks = sHookedMethodCallbacks.get(hookMethod); if (callbacks == null)
if (callbacks == null) return;
return; }
} callbacks.remove(callback);
callbacks.remove(callback); }
}
/** /**
* Hooks all methods with a certain name that were declared in the specified class. Inherited * Hooks all methods with a certain name that were declared in the specified class. Inherited
* methods and constructors are not considered. For constructors, use * methods and constructors are not considered. For constructors, use
* {@link #hookAllConstructors} instead. * {@link #hookAllConstructors} instead.
* *
* @param hookClass The class to check for declared methods. * @param hookClass The class to check for declared methods.
* @param methodName The name of the method(s) to hook. * @param methodName The name of the method(s) to hook.
* @param callback The callback to be executed when the hooked methods are called. * @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. * @return A set containing one object for each found method which can be used to unhook it.
*/ */
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
public static Set<XC_MethodHook.Unhook> hookAllMethods(Class<?> hookClass, String methodName, XC_MethodHook callback) { public static Set<XC_MethodHook.Unhook> hookAllMethods(Class<?> hookClass, String methodName, XC_MethodHook callback) {
Set<XC_MethodHook.Unhook> unhooks = new HashSet<>(); Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();
for (Member method : hookClass.getDeclaredMethods()) for (Member method : hookClass.getDeclaredMethods())
if (method.getName().equals(methodName)) if (method.getName().equals(methodName))
unhooks.add(hookMethod(method, callback)); unhooks.add(hookMethod(method, callback));
return unhooks; return unhooks;
} }
/** /**
* Hook all constructors of the specified class. * Hook all constructors of the specified class.
* *
* @param hookClass The class to check for constructors. * @param hookClass The class to check for constructors.
* @param callback The callback to be executed when the hooked constructors are called. * @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. * @return A set containing one object for each found constructor which can be used to unhook it.
*/ */
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
public static Set<XC_MethodHook.Unhook> hookAllConstructors(Class<?> hookClass, XC_MethodHook callback) { public static Set<XC_MethodHook.Unhook> hookAllConstructors(Class<?> hookClass, XC_MethodHook callback) {
Set<XC_MethodHook.Unhook> unhooks = new HashSet<>(); Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();
for (Member constructor : hookClass.getDeclaredConstructors()) for (Member constructor : hookClass.getDeclaredConstructors())
unhooks.add(hookMethod(constructor, callback)); unhooks.add(hookMethod(constructor, callback));
return unhooks; return unhooks;
} }
/** /**
* Adds a callback to be executed when an app ("Android package") is loaded. * Adds a callback to be executed when an app ("Android package") is loaded.
* *
* <p class="note">You probably don't need to call this. Simply implement {@link IXposedHookLoadPackage} * <p class="note">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. * in your module class and Xposed will take care of registering it as a callback.
* *
* @param callback The callback to be executed. * @param callback The callback to be executed.
* @hide * @hide
*/ */
public static void hookLoadPackage(XC_LoadPackage callback) { public static void hookLoadPackage(XC_LoadPackage callback) {
synchronized (sLoadedPackageCallbacks) { synchronized (sLoadedPackageCallbacks) {
sLoadedPackageCallbacks.add(callback); sLoadedPackageCallbacks.add(callback);
} }
} }
public static void clearLoadedPackages() { public static void clearLoadedPackages() {
synchronized (sLoadedPackageCallbacks) { synchronized (sLoadedPackageCallbacks) {
sLoadedPackageCallbacks.clear(); sLoadedPackageCallbacks.clear();
} }
} }
/** /**
* Adds a callback to be executed when the resources for an app are initialized. * Adds a callback to be executed when the resources for an app are initialized.
* *
* <p class="note">You probably don't need to call this. Simply implement {@link IXposedHookInitPackageResources} * <p class="note">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. * in your module class and Xposed will take care of registering it as a callback.
* *
* @param callback The callback to be executed. * @param callback The callback to be executed.
* @hide * @hide
*/ */
public static void hookInitPackageResources(XC_InitPackageResources callback) { public static void hookInitPackageResources(XC_InitPackageResources callback) {
synchronized (sInitPackageResourcesCallbacks) { synchronized (sInitPackageResourcesCallbacks) {
sInitPackageResourcesCallbacks.add(callback); sInitPackageResourcesCallbacks.add(callback);
} }
} }
public static void hookInitZygote(XC_InitZygote callback) { public static void hookInitZygote(XC_InitZygote callback) {
synchronized (sInitZygoteCallbacks) { synchronized (sInitZygoteCallbacks) {
sInitZygoteCallbacks.add(callback); sInitZygoteCallbacks.add(callback);
} }
} }
/** /**
* Basically the same as {@link Method#invoke}, but calls the original method * 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. * as it was before the interception by Xposed. Also, access permissions are not checked.
* *
* <p class="caution">There are very few cases where this method is needed. A common mistake is * <p class="caution">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 * 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) * 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} * the method and call {@code param.setResult(null)} in {@link XC_MethodHook#beforeHookedMethod}
* if the original method should be skipped. * if the original method should be skipped.
* *
* @param method The method to be called. * @param method The method to be called.
* @param thisObject For non-static calls, the "this" pointer, otherwise {@code null}. * @param thisObject For non-static calls, the "this" pointer, otherwise {@code null}.
* @param args Arguments for the method call as Object[] array. * @param args Arguments for the method call as Object[] array.
* @return The result returned from the invoked method. * @return The result returned from the invoked method.
* @throws NullPointerException * @throws NullPointerException if {@code receiver == null} for a non-static method
* if {@code receiver == null} for a non-static method * @throws IllegalAccessException if this method is not accessible (see {@link AccessibleObject})
* @throws IllegalAccessException * @throws IllegalArgumentException if the number of arguments doesn't match the number of parameters, the receiver
* if this method is not accessible (see {@link AccessibleObject}) * is incompatible with the declaring class, or an argument could not be unboxed
* @throws IllegalArgumentException * or converted by a widening conversion to the corresponding parameter type
* if the number of arguments doesn't match the number of parameters, the receiver * @throws InvocationTargetException if an exception was thrown by the invoked method
* is incompatible with the declaring class, or an argument could not be unboxed */
* or converted by a widening conversion to the corresponding parameter type public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
* @throws InvocationTargetException throws Throwable {
* if an exception was thrown by the invoked method if (args == null) {
*/ args = EMPTY_ARRAY;
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) }
throws Throwable {
if (args == null) {
args = EMPTY_ARRAY;
}
if (!(method instanceof Executable)) { if (!(method instanceof Executable)) {
throw new IllegalArgumentException("method must be of type Method or Constructor"); 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<K,V> extends HashMap<K,V> { private static class NoValuesHashMap<K, V> extends HashMap<K, V> {
@Override @Override
public Collection values() { public Collection values() {
return Collections.EMPTY_LIST; return Collections.EMPTY_LIST;
} }
@Override @Override
public void clear() { public void clear() {
} }
@Override @Override
public Set<K> keySet() { public Set<K> keySet() {
return Collections.EMPTY_SET; return Collections.EMPTY_SET;
} }
@Override @Override
public Set<Entry<K, V>> entrySet() { public Set<Entry<K, V>> entrySet() {
return Collections.EMPTY_SET; return Collections.EMPTY_SET;
} }
@Override @Override
public int size() { public int size() {
return 0; return 0;
} }
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return true; return true;
} }
} }
/** @hide */ /**
public static final class CopyOnWriteSortedSet<E> { * @hide
private transient volatile Object[] elements = EMPTY_ARRAY; */
public static final class CopyOnWriteSortedSet<E> {
private transient volatile Object[] elements = EMPTY_ARRAY;
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
public synchronized boolean add(E e) { public synchronized boolean add(E e) {
int index = indexOf(e); int index = indexOf(e);
if (index >= 0) if (index >= 0)
return false; return false;
Object[] newElements = new Object[elements.length + 1]; Object[] newElements = new Object[elements.length + 1];
System.arraycopy(elements, 0, newElements, 0, elements.length); System.arraycopy(elements, 0, newElements, 0, elements.length);
newElements[elements.length] = e; newElements[elements.length] = e;
Arrays.sort(newElements); Arrays.sort(newElements);
elements = newElements; elements = newElements;
return true; return true;
} }
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
public synchronized boolean remove(E e) { public synchronized boolean remove(E e) {
int index = indexOf(e); int index = indexOf(e);
if (index == -1) if (index == -1)
return false; return false;
Object[] newElements = new Object[elements.length - 1]; Object[] newElements = new Object[elements.length - 1];
System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index, elements.length - index - 1); System.arraycopy(elements, index + 1, newElements, index, elements.length - index - 1);
elements = newElements; elements = newElements;
return true; return true;
} }
private int indexOf(Object o) { private int indexOf(Object o) {
for (int i = 0; i < elements.length; i++) { for (int i = 0; i < elements.length; i++) {
if (o.equals(elements[i])) if (o.equals(elements[i]))
return i; return i;
} }
return -1; return -1;
} }
public Object[] getSnapshot() { public Object[] getSnapshot() {
return elements; return elements;
} }
public synchronized void clear() { public synchronized void clear() {
elements = EMPTY_ARRAY; elements = EMPTY_ARRAY;
} }
} }
public static class AdditionalHookInfo { public static class AdditionalHookInfo {
public final CopyOnWriteSortedSet<XC_MethodHook> callbacks; public final CopyOnWriteSortedSet<XC_MethodHook> callbacks;
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks) { private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks) {
this.callbacks = callbacks; this.callbacks = callbacks;
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,18 @@
package de.robv.android.xposed; 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.annotation.SuppressLint;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.res.Resources; import android.content.res.Resources;
@ -32,7 +44,8 @@ import android.os.Process;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Log; 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.BufferedReader;
import java.io.File; 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.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XCallback; import de.robv.android.xposed.callbacks.XCallback;
import hidden.HiddenApiBridge; 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 { public final class XposedInit {
private static final String TAG = XposedBridge.TAG; private static final String TAG = XposedBridge.TAG;
public static boolean startsSystemServer = false; public static boolean startsSystemServer = false;
private static final String startClassName = ""; // ed: no support for tool process anymore
public static volatile boolean disableResources = false; public static volatile boolean disableResources = false;
private XposedInit() { public static void hookResources() throws Throwable {
}
/**
* 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 {
if (!serviceClient.isResourcesHookEnabled() || disableResources) { if (!serviceClient.isResourcesHookEnabled() || disableResources) {
return; 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); boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
if (hasLoaded) { if (hasLoaded) {
return false; return false;
@ -256,7 +227,7 @@ public final class XposedInit {
newLoadedApk.add(apk); newLoadedApk.add(apk);
} else { } else {
loadedModules.add(apk); // temporarily add it for XSharedPreference loadedModules.add(apk); // temporarily add it for XSharedPreference
boolean loadSuccess = loadModule(apk, callInitZygote); boolean loadSuccess = loadModule(apk);
if (loadSuccess) { if (loadSuccess) {
newLoadedApk.add(apk); 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"); InputStream is = mcl.getResourceAsStream("assets/xposed_init");
if (is == null) { if (is == null) {
return true; return true;
@ -359,9 +330,7 @@ public final class XposedInit {
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper( XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
(IXposedHookZygoteInit) moduleInstance, param)); (IXposedHookZygoteInit) moduleInstance, param));
if (callInitZygote) { ((IXposedHookZygoteInit) moduleInstance).initZygote(param);
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
}
} }
if (moduleInstance instanceof IXposedHookLoadPackage) if (moduleInstance instanceof IXposedHookLoadPackage)
@ -390,7 +359,7 @@ public final class XposedInit {
* in <code>assets/xposed_init</code>. * in <code>assets/xposed_init</code>.
*/ */
@SuppressLint("PrivateApi") @SuppressLint("PrivateApi")
private static boolean loadModule(String apk, boolean callInitZygote) { private static boolean loadModule(String apk) {
Log.i(TAG, "Loading modules from " + apk); Log.i(TAG, "Loading modules from " + apk);
if (!new File(apk).exists()) { if (!new File(apk).exists()) {
@ -418,7 +387,7 @@ public final class XposedInit {
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
} }
boolean res = initModule(mcl, apk, callInitZygote); boolean res = initModule(mcl, apk);
res = res && initNativeModule(mcl, apk); res = res && initNativeModule(mcl, apk);
return res; return res;
} }

View File

@ -33,13 +33,13 @@ import de.robv.android.xposed.IXposedHookZygoteInit;
* @param <T> The class of the callback. * @param <T> The class of the callback.
*/ */
public interface IXUnhook<T> { public interface IXUnhook<T> {
/** /**
* Returns the callback that has been registered. * Returns the callback that has been registered.
*/ */
T getCallback(); T getCallback();
/** /**
* Removes the callback. * Removes the callback.
*/ */
void unhook(); void unhook();
} }

View File

@ -30,48 +30,55 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
* subclass. * subclass.
*/ */
public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources { public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources {
/** /**
* Creates a new callback with default priority. * Creates a new callback with default priority.
* @hide *
*/ * @hide
@SuppressWarnings("deprecation") */
public XC_InitPackageResources() { @SuppressWarnings("deprecation")
super(); public XC_InitPackageResources() {
} super();
}
/** /**
* Creates a new callback with a specific priority. * Creates a new callback with a specific priority.
* *
* @param priority See {@link XCallback#priority}. * @param priority See {@link XCallback#priority}.
* @hide * @hide
*/ */
public XC_InitPackageResources(int priority) { public XC_InitPackageResources(int priority) {
super(priority); super(priority);
} }
/** /**
* Wraps information about the resources being initialized. * Wraps information about the resources being initialized.
*/ */
public static final class InitPackageResourcesParam extends XCallback.Param { public static final class InitPackageResourcesParam extends XCallback.Param {
/** @hide */ /**
public InitPackageResourcesParam(CopyOnWriteSortedSet<XC_InitPackageResources> callbacks) { * @hide
super(callbacks); */
} public InitPackageResourcesParam(CopyOnWriteSortedSet<XC_InitPackageResources> 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 * Reference to the resources that can be used for calls to
* {@link XResources#setReplacement(String, String, String, Object)}. * {@link XResources#setReplacement(String, String, String, Object)}.
*/ */
public XResources res; public XResources res;
} }
/** @hide */ /**
@Override * @hide
protected void call(Param param) throws Throwable { */
if (param instanceof InitPackageResourcesParam) @Override
handleInitPackageResources((InitPackageResourcesParam) param); protected void call(Param param) throws Throwable {
} if (param instanceof InitPackageResourcesParam)
handleInitPackageResources((InitPackageResourcesParam) param);
}
} }

View File

@ -31,89 +31,103 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
* and its variants. * and its variants.
*/ */
public abstract class XC_LayoutInflated extends XCallback { public abstract class XC_LayoutInflated extends XCallback {
/** /**
* Creates a new callback with default priority. * Creates a new callback with default priority.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public XC_LayoutInflated() { public XC_LayoutInflated() {
super(); super();
} }
/** /**
* Creates a new callback with a specific priority. * Creates a new callback with a specific priority.
* *
* @param priority See {@link XCallback#priority}. * @param priority See {@link XCallback#priority}.
*/ */
public XC_LayoutInflated(int priority) { public XC_LayoutInflated(int priority) {
super(priority); super(priority);
} }
/** /**
* Wraps information about the inflated layout. * Wraps information about the inflated layout.
*/ */
public static final class LayoutInflatedParam extends XCallback.Param { public static final class LayoutInflatedParam extends XCallback.Param {
/** @hide */ /**
public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) { * @hide
super(callbacks); */
} public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> 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 * @hide
protected void call(Param param) throws Throwable { */
if (param instanceof LayoutInflatedParam) @Override
handleLayoutInflated((LayoutInflatedParam) param); 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. * This method is called when the hooked layout has been inflated.
* *
* @param liparam Information about the layout and the inflated view. * @param liparam Information about the layout and the inflated view.
* @throws Throwable Everything the callback throws is caught and logged. * @throws Throwable Everything the callback throws is caught and logged.
*/ */
public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable; public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable;
/** /**
* An object with which the callback can be removed. * An object with which the callback can be removed.
*/ */
public class Unhook implements IXUnhook<XC_LayoutInflated> { public class Unhook implements IXUnhook<XC_LayoutInflated> {
private final String resDir; private final String resDir;
private final int id; private final int id;
/** @hide */ /**
public Unhook(String resDir, int id) { * @hide
this.resDir = resDir; */
this.id = id; public Unhook(String resDir, int id) {
} this.resDir = resDir;
this.id = id;
}
/** /**
* Returns the resource ID of the hooked layout. * Returns the resource ID of the hooked layout.
*/ */
public int getId() { public int getId() {
return id; return id;
} }
@Override @Override
public XC_LayoutInflated getCallback() { public XC_LayoutInflated getCallback() {
return XC_LayoutInflated.this; return XC_LayoutInflated.this;
} }
@Override @Override
public void unhook() { public void unhook() {
XResources.unhookLayout(resDir, id, XC_LayoutInflated.this); XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
} }
} }
} }

View File

@ -30,54 +30,69 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
* subclass. * subclass.
*/ */
public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoadPackage { public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoadPackage {
/** /**
* Creates a new callback with default priority. * Creates a new callback with default priority.
* @hide *
*/ * @hide
@SuppressWarnings("deprecation") */
public XC_LoadPackage() { @SuppressWarnings("deprecation")
super(); public XC_LoadPackage() {
} super();
}
/** /**
* Creates a new callback with a specific priority. * Creates a new callback with a specific priority.
* *
* @param priority See {@link XCallback#priority}. * @param priority See {@link XCallback#priority}.
* @hide * @hide
*/ */
public XC_LoadPackage(int priority) { public XC_LoadPackage(int priority) {
super(priority); super(priority);
} }
/** /**
* Wraps information about the app being loaded. * Wraps information about the app being loaded.
*/ */
public static final class LoadPackageParam extends XCallback.Param { public static final class LoadPackageParam extends XCallback.Param {
/** @hide */ /**
public LoadPackageParam(CopyOnWriteSortedSet<XC_LoadPackage> callbacks) { * @hide
super(callbacks); */
} public LoadPackageParam(CopyOnWriteSortedSet<XC_LoadPackage> 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 * @hide
protected void call(Param param) throws Throwable { */
if (param instanceof LoadPackageParam) @Override
handleLoadPackage((LoadPackageParam) param); protected void call(Param param) throws Throwable {
} if (param instanceof LoadPackageParam)
handleLoadPackage((LoadPackageParam) param);
}
} }

View File

@ -27,147 +27,174 @@ import java.io.Serializable;
import de.robv.android.xposed.IModuleContext; import de.robv.android.xposed.IModuleContext;
import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet; import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter; import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
/** /**
* Base class for Xposed callbacks. * Base class for Xposed callbacks.
* * <p>
* This class only keeps a priority for ordering multiple callbacks. * This class only keeps a priority for ordering multiple callbacks.
* The actual (abstract) callback methods are added by subclasses. * The actual (abstract) callback methods are added by subclasses.
*/ */
public abstract class XCallback implements Comparable<XCallback>, IModuleContext { public abstract class XCallback implements Comparable<XCallback>, IModuleContext {
/** /**
* Callback priority, higher number means earlier execution. * Callback priority, higher number means earlier execution.
* *
* <p>This is usually set to {@link #PRIORITY_DEFAULT}. However, in case a certain callback should * <p>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} * 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 * can be set instead. The values are just for orientation though, Xposed doesn't enforce any
* boundaries on the priority values. * boundaries on the priority values.
*/ */
public final int priority; public final int priority;
/** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */ /**
@Deprecated * @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it!
public XCallback() { */
this.priority = PRIORITY_DEFAULT; @Deprecated
} public XCallback() {
this.priority = PRIORITY_DEFAULT;
}
/** @hide */ /**
public XCallback(int priority) { * @hide
this.priority = priority; */
} public XCallback(int priority) {
this.priority = priority;
}
/** /**
* Base class for Xposed callback parameters. * Base class for Xposed callback parameters.
*/ */
public static abstract class Param { public static abstract class Param {
/** @hide */ /**
public final Object[] callbacks; * @hide
private Bundle extra; */
public final Object[] callbacks;
private Bundle extra;
/** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */ /**
@Deprecated * @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it!
protected Param() { */
callbacks = null; @Deprecated
} protected Param() {
callbacks = null;
}
/** @hide */ /**
protected Param(CopyOnWriteSortedSet<? extends XCallback> callbacks) { * @hide
this.callbacks = callbacks.getSnapshot(); */
} protected Param(CopyOnWriteSortedSet<? extends XCallback> callbacks) {
this.callbacks = callbacks.getSnapshot();
}
/** /**
* This can be used to store any data for the scope of the callback. * This can be used to store any data for the scope of the callback.
* *
* <p>Use this instead of instance variables, as it has a clear reference to e.g. each * <p>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. * separate call to a method, even when the same method is called recursively.
* *
* @see #setObjectExtra * @see #setObjectExtra
* @see #getObjectExtra * @see #getObjectExtra
*/ */
public synchronized Bundle getExtra() { public synchronized Bundle getExtra() {
if (extra == null) if (extra == null)
extra = new Bundle(); extra = new Bundle();
return extra; return extra;
} }
/** /**
* Returns an object stored with {@link #setObjectExtra}. * Returns an object stored with {@link #setObjectExtra}.
*/ */
public Object getObjectExtra(String key) { public Object getObjectExtra(String key) {
Serializable o = getExtra().getSerializable(key); Serializable o = getExtra().getSerializable(key);
if (o instanceof SerializeWrapper) if (o instanceof SerializeWrapper)
return ((SerializeWrapper) o).object; return ((SerializeWrapper) o).object;
return null; return null;
} }
/** /**
* Stores any object for the scope of the callback. For data types that support it, use * Stores any object for the scope of the callback. For data types that support it, use
* the {@link Bundle} returned by {@link #getExtra} instead. * the {@link Bundle} returned by {@link #getExtra} instead.
*/ */
public void setObjectExtra(String key, Object o) { public void setObjectExtra(String key, Object o) {
getExtra().putSerializable(key, new SerializeWrapper(o)); getExtra().putSerializable(key, new SerializeWrapper(o));
} }
private static class SerializeWrapper implements Serializable { private static class SerializeWrapper implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final Object object; private final Object object;
public SerializeWrapper(Object o) {
object = o;
}
}
}
/** @hide */ public SerializeWrapper(Object o) {
public static void callAll(Param param) { 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 // only if we found out how to recompile their apks
XC_LoadPackage.LoadPackageParam lpp = (XC_LoadPackage.LoadPackageParam) param; XC_LoadPackage.LoadPackageParam lpp = (XC_LoadPackage.LoadPackageParam) param;
PrebuiltMethodsDeopter.deoptMethods(lpp.packageName, lpp.classLoader); PrebuiltMethodsDeopter.deoptMethods(lpp.packageName, lpp.classLoader);
} }
if (param.callbacks == null) if (param.callbacks == null)
throw new IllegalStateException("This object was not created for use with callAll"); throw new IllegalStateException("This object was not created for use with callAll");
for (int i = 0; i < param.callbacks.length; i++) { for (int i = 0; i < param.callbacks.length; i++) {
try { try {
((XCallback) param.callbacks[i]).call(param); ((XCallback) param.callbacks[i]).call(param);
} catch (Throwable t) { XposedBridge.log(t); } } catch (Throwable t) {
} XposedBridge.log(t);
} }
}
}
/** @hide */ /**
protected void call(Param param) throws Throwable {} * @hide
*/
protected void call(Param param) throws Throwable {
}
@Override @Override
public String getApkPath() { public String getApkPath() {
return ""; return "";
} }
/** @hide */ /**
@Override * @hide
public int compareTo(XCallback other) { */
if (this == other) @Override
return 0; public int compareTo(XCallback other) {
if (this == other)
return 0;
// order descending by priority // order descending by priority
if (other.priority != this.priority) if (other.priority != this.priority)
return other.priority - this.priority; return other.priority - this.priority;
// then randomly // then randomly
else if (System.identityHashCode(this) < System.identityHashCode(other)) else if (System.identityHashCode(this) < System.identityHashCode(other))
return -1; return -1;
else else
return 1; 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;
} }

View File

@ -33,167 +33,178 @@ import de.robv.android.xposed.SELinuxHelper;
* <p>References to a concrete subclass should generally be retrieved from {@link SELinuxHelper}. * <p>References to a concrete subclass should generally be retrieved from {@link SELinuxHelper}.
*/ */
public abstract class BaseService { public abstract class BaseService {
/** Flag for {@link #checkFileAccess}: Read access. */ /**
public static final int R_OK = 4; * Flag for {@link #checkFileAccess}: Read access.
/** Flag for {@link #checkFileAccess}: Write access. */ */
public static final int W_OK = 2; public static final int R_OK = 4;
/** Flag for {@link #checkFileAccess}: Executable access. */ /**
public static final int X_OK = 1; * Flag for {@link #checkFileAccess}: Write access.
/** Flag for {@link #checkFileAccess}: File/directory exists. */ */
public static final int F_OK = 0; 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). * Checks whether the services accesses files directly (instead of using IPC).
* *
* @return {@code true} in case direct access is possible. * @return {@code true} in case direct access is possible.
*/ */
public boolean hasDirectFileAccess() { public boolean hasDirectFileAccess() {
return false; return false;
} }
/** /**
* Check whether a file is accessible. SELinux might enforce stricter checks. * Check whether a file is accessible. SELinux might enforce stricter checks.
* *
* @param filename The absolute path of the file to check. * @param filename The absolute path of the file to check.
* @param mode The mode for POSIX's {@code access()} function. * @param mode The mode for POSIX's {@code access()} function.
* @return The result of the {@code access()} function. * @return The result of the {@code access()} function.
*/ */
public abstract boolean checkFileAccess(String filename, int mode); public abstract boolean checkFileAccess(String filename, int mode);
/** /**
* Check whether a file exists. * Check whether a file exists.
* *
* @param filename The absolute path of the file to check. * @param filename The absolute path of the file to check.
* @return The result of the {@code access()} function. * @return The result of the {@code access()} function.
*/ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean checkFileExists(String filename) { public boolean checkFileExists(String filename) {
return checkFileAccess(filename, F_OK); return checkFileAccess(filename, F_OK);
} }
/** /**
* Determine the size and modification time of a file. * Determine the size and modification time of a file.
* *
* @param filename The absolute path of the file to check. * @param filename The absolute path of the file to check.
* @return A {@link FileResult} object holding the result. * @return A {@link FileResult} object holding the result.
* @throws IOException In case an error occurred while retrieving the information. * @throws IOException In case an error occurred while retrieving the information.
*/ */
public abstract FileResult statFile(String filename) throws IOException; public abstract FileResult statFile(String filename) throws IOException;
/** /**
* Determine the size time of a file. * Determine the size time of a file.
* *
* @param filename The absolute path of the file to check. * @param filename The absolute path of the file to check.
* @return The file size. * @return The file size.
* @throws IOException In case an error occurred while retrieving the information. * @throws IOException In case an error occurred while retrieving the information.
*/ */
public long getFileSize(String filename) throws IOException { public long getFileSize(String filename) throws IOException {
return statFile(filename).size; return statFile(filename).size;
} }
/** /**
* Determine the size time of a file. * Determine the size time of a file.
* *
* @param filename The absolute path of the file to check. * @param filename The absolute path of the file to check.
* @return The file modification time. * @return The file modification time.
* @throws IOException In case an error occurred while retrieving the information. * @throws IOException In case an error occurred while retrieving the information.
*/ */
public long getFileModificationTime(String filename) throws IOException { public long getFileModificationTime(String filename) throws IOException {
return statFile(filename).mtime; return statFile(filename).mtime;
} }
/** /**
* Read a file into memory. * Read a file into memory.
* *
* @param filename The absolute path of the file to read. * @param filename The absolute path of the file to read.
* @return A {@code byte} array with the file content. * @return A {@code byte} array with the file content.
* @throws IOException In case an error occurred while reading the file. * @throws IOException In case an error occurred while reading the file.
*/ */
public abstract byte[] readFile(String filename) throws IOException; public abstract byte[] readFile(String filename) throws IOException;
/** /**
* Read a file into memory, but only if it has changed since the last time. * 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 filename The absolute path of the file to read.
* @param previousSize File size of last read. * @param previousSize File size of last read.
* @param previousTime File modification time of last read. * @param previousTime File modification time of last read.
* @return A {@link FileResult} object holding the result. * @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#content} field might be {@code null} if the file * <p>The {@link FileResult#content} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid). * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file. * @throws IOException In case an error occurred while reading the file.
*/ */
public abstract FileResult readFile(String filename, long previousSize, long previousTime) throws IOException; 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. * 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 filename The absolute path of the file to read.
* @param offset Number of bytes to skip at the beginning of the file. * @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 length Number of bytes to read (0 means read to end of file).
* @param previousSize Optional: File size of last read. * @param previousSize Optional: File size of last read.
* @param previousTime Optional: File modification time of last read. * @param previousTime Optional: File modification time of last read.
* @return A {@link FileResult} object holding the result. * @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#content} field might be {@code null} if the file * <p>The {@link FileResult#content} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid). * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file. * @throws IOException In case an error occurred while reading the file.
*/ */
public abstract FileResult readFile(String filename, int offset, int length, public abstract FileResult readFile(String filename, int offset, int length,
long previousSize, long previousTime) throws IOException; long previousSize, long previousTime) throws IOException;
/** /**
* Get a stream to the file content. * Get a stream to the file content.
* Depending on the service, it may or may not be read completely into memory. * 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 filename The absolute path of the file to read.
* @return An {@link InputStream} to the file content. * @return An {@link InputStream} to the file content.
* @throws IOException In case an error occurred while reading the file. * @throws IOException In case an error occurred while reading the file.
*/ */
public InputStream getFileInputStream(String filename) throws IOException { public InputStream getFileInputStream(String filename) throws IOException {
return new ByteArrayInputStream(readFile(filename)); return new ByteArrayInputStream(readFile(filename));
} }
/** /**
* Get a stream to the file content, but only if it has changed since the last time. * 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. * 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 filename The absolute path of the file to read.
* @param previousSize Optional: File size of last read. * @param previousSize Optional: File size of last read.
* @param previousTime Optional: File modification time of last read. * @param previousTime Optional: File modification time of last read.
* @return A {@link FileResult} object holding the result. * @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#stream} field might be {@code null} if the file * <p>The {@link FileResult#stream} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid). * is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file. * @throws IOException In case an error occurred while reading the file.
*/ */
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException { public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
FileResult result = readFile(filename, previousSize, previousTime); FileResult result = readFile(filename, previousSize, previousTime);
if (result.content == null) if (result.content == null)
return result; return result;
return new FileResult(new ByteArrayInputStream(result.content), result.size, result.mtime); return new FileResult(new ByteArrayInputStream(result.content), result.size, result.mtime);
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/*package*/ BaseService() {} /*package*/ BaseService() {
}
/*package*/ static void ensureAbsolutePath(String filename) { /*package*/
if (!filename.startsWith("/")) { static void ensureAbsolutePath(String filename) {
throw new IllegalArgumentException("Only absolute filenames are allowed: " + 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 { /*package*/
switch (errno) { static void throwCommonIOException(int errno, String errorMsg, String filename, String defaultText) throws IOException {
case 1: // EPERM switch (errno) {
case 13: // EACCES case 1: // EPERM
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Permission denied: " + filename); case 13: // EACCES
case 2: // ENOENT throw new FileNotFoundException(errorMsg != null ? errorMsg : "Permission denied: " + filename);
throw new FileNotFoundException(errorMsg != null ? errorMsg : "No such file or directory: " + filename); case 2: // ENOENT
case 12: // ENOMEM throw new FileNotFoundException(errorMsg != null ? errorMsg : "No such file or directory: " + filename);
throw new OutOfMemoryError(errorMsg); case 12: // ENOMEM
case 21: // EISDIR throw new OutOfMemoryError(errorMsg);
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Is a directory: " + filename); case 21: // EISDIR
default: throw new FileNotFoundException(errorMsg != null ? errorMsg : "Is a directory: " + filename);
throw new IOException(errorMsg != null ? errorMsg : "Error " + errno + defaultText + filename); default:
} throw new IOException(errorMsg != null ? errorMsg : "Error " + errno + defaultText + filename);
} }
}
} }

View File

@ -26,108 +26,110 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/** @hide */ /**
* @hide
*/
public final class DirectAccessService extends BaseService { public final class DirectAccessService extends BaseService {
@Override @Override
public boolean hasDirectFileAccess() { public boolean hasDirectFileAccess() {
return true; return true;
} }
@SuppressWarnings("RedundantIfStatement") @SuppressWarnings("RedundantIfStatement")
@Override @Override
public boolean checkFileAccess(String filename, int mode) { public boolean checkFileAccess(String filename, int mode) {
File file = new File(filename); File file = new File(filename);
if (mode == F_OK && !file.exists()) return false; if (mode == F_OK && !file.exists()) return false;
if ((mode & R_OK) != 0 && !file.canRead()) return false; if ((mode & R_OK) != 0 && !file.canRead()) return false;
if ((mode & W_OK) != 0 && !file.canWrite()) return false; if ((mode & W_OK) != 0 && !file.canWrite()) return false;
if ((mode & X_OK) != 0 && !file.canExecute()) return false; if ((mode & X_OK) != 0 && !file.canExecute()) return false;
return true; return true;
} }
@Override @Override
public boolean checkFileExists(String filename) { public boolean checkFileExists(String filename) {
return new File(filename).exists(); return new File(filename).exists();
} }
@Override @Override
public FileResult statFile(String filename) throws IOException { public FileResult statFile(String filename) throws IOException {
File file = new File(filename); File file = new File(filename);
return new FileResult(file.length(), file.lastModified()); return new FileResult(file.length(), file.lastModified());
} }
@Override @Override
public byte[] readFile(String filename) throws IOException { public byte[] readFile(String filename) throws IOException {
File file = new File(filename); File file = new File(filename);
byte content[] = new byte[(int)file.length()]; byte content[] = new byte[(int) file.length()];
FileInputStream fis = new FileInputStream(file); FileInputStream fis = new FileInputStream(file);
fis.read(content); fis.read(content);
fis.close(); fis.close();
return content; return content;
} }
@Override @Override
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException { public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
File file = new File(filename); File file = new File(filename);
long size = file.length(); long size = file.length();
long time = file.lastModified(); long time = file.lastModified();
if (previousSize == size && previousTime == time) if (previousSize == size && previousTime == time)
return new FileResult(size, time); return new FileResult(size, time);
return new FileResult(readFile(filename), size, time); return new FileResult(readFile(filename), size, time);
} }
@Override @Override
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException { public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
File file = new File(filename); File file = new File(filename);
long size = file.length(); long size = file.length();
long time = file.lastModified(); long time = file.lastModified();
if (previousSize == size && previousTime == time) if (previousSize == size && previousTime == time)
return new FileResult(size, time); return new FileResult(size, time);
// Shortcut for the simple case // Shortcut for the simple case
if (offset <= 0 && length <= 0) if (offset <= 0 && length <= 0)
return new FileResult(readFile(filename), size, time); return new FileResult(readFile(filename), size, time);
// Check range // Check range
if (offset > 0 && offset >= size) { if (offset > 0 && offset >= size) {
throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename); throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename);
} else if (offset < 0) { } else if (offset < 0) {
offset = 0; offset = 0;
} }
if (length > 0 && (offset + length) > size) { if (length > 0 && (offset + length) > size) {
throw new IllegalArgumentException("Length " + length + " is out of range for " + filename); throw new IllegalArgumentException("Length " + length + " is out of range for " + filename);
} else if (length <= 0) { } else if (length <= 0) {
length = (int) (size - offset); length = (int) (size - offset);
} }
byte content[] = new byte[length]; byte content[] = new byte[length];
FileInputStream fis = new FileInputStream(file); FileInputStream fis = new FileInputStream(file);
fis.skip(offset); fis.skip(offset);
fis.read(content); fis.read(content);
fis.close(); fis.close();
return new FileResult(content, size, time); return new FileResult(content, size, time);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory. * <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
*/ */
@Override @Override
public InputStream getFileInputStream(String filename) throws IOException { public InputStream getFileInputStream(String filename) throws IOException {
return new BufferedInputStream(new FileInputStream(filename), 16*1024); return new BufferedInputStream(new FileInputStream(filename), 16 * 1024);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory. * <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
*/ */
@Override @Override
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException { public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
File file = new File(filename); File file = new File(filename);
long size = file.length(); long size = file.length();
long time = file.lastModified(); long time = file.lastModified();
if (previousSize == size && previousTime == time) if (previousSize == size && previousTime == time)
return new FileResult(size, time); return new FileResult(size, time);
return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16*1024), size, time); return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16 * 1024), size, time);
} }
} }

View File

@ -26,55 +26,65 @@ import java.io.InputStream;
* Holder for the result of a {@link BaseService#readFile} or {@link BaseService#statFile} call. * Holder for the result of a {@link BaseService#readFile} or {@link BaseService#statFile} call.
*/ */
public final class FileResult { public final class FileResult {
/** File content, might be {@code null} if the file wasn't read. */ /**
public final byte[] content; * File content, might be {@code null} if the file wasn't read.
/** File input stream, might be {@code null} if the file wasn't read. */ */
public final InputStream stream; public final byte[] content;
/** File size. */ /**
public final long size; * File input stream, might be {@code null} if the file wasn't read.
/** File last modification time. */ */
public final long mtime; public final InputStream stream;
/**
* File size.
*/
public final long size;
/**
* File last modification time.
*/
public final long mtime;
/*package*/ FileResult(long size, long mtime) { /*package*/ FileResult(long size, long mtime) {
this.content = null; this.content = null;
this.stream = null; this.stream = null;
this.size = size; this.size = size;
this.mtime = mtime; this.mtime = mtime;
} }
/*package*/ FileResult(byte[] content, long size, long mtime) { /*package*/ FileResult(byte[] content, long size, long mtime) {
this.content = content; this.content = content;
this.stream = null; this.stream = null;
this.size = size; this.size = size;
this.mtime = mtime; this.mtime = mtime;
} }
/*package*/ FileResult(InputStream stream, long size, long mtime) { /*package*/ FileResult(InputStream stream, long size, long mtime) {
this.content = null; this.content = null;
this.stream = stream; this.stream = stream;
this.size = size; this.size = size;
this.mtime = mtime; this.mtime = mtime;
} }
/** @hide */ /**
@Override * @hide
public String toString() { */
StringBuilder sb = new StringBuilder("{"); @Override
if (content != null) { public String toString() {
sb.append("content.length: "); StringBuilder sb = new StringBuilder("{");
sb.append(content.length); if (content != null) {
sb.append(", "); sb.append("content.length: ");
} sb.append(content.length);
if (stream != null) { sb.append(", ");
sb.append("stream: "); }
sb.append(stream.toString()); if (stream != null) {
sb.append(", "); sb.append("stream: ");
} sb.append(stream.toString());
sb.append("size: "); sb.append(", ");
sb.append(size); }
sb.append(", mtime: "); sb.append("size: ");
sb.append(mtime); sb.append(size);
sb.append("}"); sb.append(", mtime: ");
return sb.toString(); sb.append(mtime);
} sb.append("}");
return sb.toString();
}
} }

View File

@ -79,15 +79,15 @@ public class Main {
// Initialize the Xposed framework // Initialize the Xposed framework
try { try {
startBootstrapHook(isSystem, appDataDir); startBootstrapHook(isSystem, appDataDir);
XposedInit.initForZygote(); XposedInit.hookResources();
} catch (Throwable t) { } catch (Throwable t) {
Utils.logE("error during Xposed initialization", t); Utils.logE("error during Xposed initialization", t);
} }
} }
private static void loadModulesSafely(boolean callInitZygote) { private static void loadModulesSafely() {
try { try {
XposedInit.loadModules(callInitZygote); XposedInit.loadModules();
} catch (Exception exception) { } catch (Exception exception) {
Utils.logE("error loading module list", exception); Utils.logE("error loading module list", exception);
} }
@ -101,7 +101,7 @@ public class Main {
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
installBootstrapHooks(isSystem, appDataDir); installBootstrapHooks(isSystem, appDataDir);
Utils.logI("Loading modules for " + niceName); Utils.logI("Loading modules for " + niceName);
loadModulesSafely(true); loadModulesSafely();
} }
public static void forkAndSpecializePost(String appDataDir, String niceName, IBinder binder) { public static void forkAndSpecializePost(String appDataDir, String niceName, IBinder binder) {

View File

@ -48,7 +48,7 @@ import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
// normal process initialization (for new Activity, Service, BroadcastReceiver etc.) // normal process initialization (for new Activity, Service, BroadcastReceiver etc.)
public class HandleBindAppHooker extends XC_MethodHook { public class HandleBindAppHooker extends XC_MethodHook {
String appDataDir = null; String appDataDir;
public HandleBindAppHooker(String appDataDir) { public HandleBindAppHooker(String appDataDir) {
this.appDataDir = appDataDir; this.appDataDir = appDataDir;

View File

@ -20,8 +20,6 @@
package org.lsposed.lspd.hooker; package org.lsposed.lspd.hooker;
import android.os.Build;
import org.lsposed.lspd.util.Hookers; import org.lsposed.lspd.util.Hookers;
import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodHook;
@ -60,7 +58,7 @@ public class StartBootstrapServicesHooker extends XC_MethodHook {
} }
try { 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, findAndHookMethod(className, SystemMainHooker.systemServerCL,
"dexEntryExists", String.class, "dexEntryExists", String.class,
XC_MethodReplacement.returnConstant(true)); XC_MethodReplacement.returnConstant(true));