Commit 95189eff authored by swift_gan's avatar swift_gan Committed by swift_gan

init

parent 8eed3a00
include ':app', ':hooklib', ':hookers', ':annotation' include ':app', ':hooklib', ':hookers', ':annotation', ':xposedcompat'
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.linkedin.dexmaker:dexmaker:2.21.0'
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.swift.sandhook.xposedcompat" />
package com.swift.sandhook.xposedcompat;
public class XposedCompat {
}
package de.robv.android.xposed;
import android.os.Environment;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;
import static de.robv.android.xposed.XposedHelpers.inputStreamToByteArray;
/**
* Helper class which can create a very simple .dex file, containing only a class definition
* with a super class (no methods, fields, ...).
*/
/*package*/ class DexCreator {
public static File DALVIK_CACHE = new File(Environment.getDataDirectory(), "dalvik-cache");
/** Returns the default dex file name for the class. */
public static File getDefaultFile(String childClz) {
return new File(DALVIK_CACHE, "xposed_" + childClz.substring(childClz.lastIndexOf('.') + 1) + ".dex");
}
/**
* Creates (or returns) the path to a dex file which defines the superclass of {@clz} as extending
* {@code realSuperClz}, which by itself must extend {@code topClz}.
*/
public static File ensure(String clz, Class<?> realSuperClz, Class<?> topClz) throws IOException {
if (!topClz.isAssignableFrom(realSuperClz)) {
throw new ClassCastException("Cannot initialize " + clz + " because " + realSuperClz + " does not extend " + topClz);
}
try {
return ensure("xposed.dummy." + clz + "SuperClass", realSuperClz);
} catch (IOException e) {
throw new IOException("Failed to create a superclass for " + clz, e);
}
}
/** Like {@link #ensure(File, String, String)}, just for the default dex file name. */
public static File ensure(String childClz, Class<?> superClz) throws IOException {
return ensure(getDefaultFile(childClz), childClz, superClz.getName());
}
/**
* Makes sure that the given file is a simple dex file containing the given classes.
* Creates the file if that's not the case.
*/
public static File ensure(File file, String childClz, String superClz) throws IOException {
// First check if a valid file exists.
try {
byte[] dex = inputStreamToByteArray(new FileInputStream(file));
if (matches(dex, childClz, superClz)) {
return file;
} else {
file.delete();
}
} catch (IOException e) {
file.delete();
}
// If not, create a new dex file.
byte[] dex = create(childClz, superClz);
FileOutputStream fos = new FileOutputStream(file);
fos.write(dex);
fos.close();
return file;
}
/**
* Checks whether the Dex file fits to the class names.
* Assumes that the file has been created with this class.
*/
public static boolean matches(byte[] dex, String childClz, String superClz) throws IOException {
boolean childFirst = childClz.compareTo(superClz) < 0;
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
int pos = 0xa0;
if (pos + childBytes.length + superBytes.length >= dex.length) {
return false;
}
for (byte b : childFirst ? childBytes : superBytes) {
if (dex[pos++] != b) {
return false;
}
}
for (byte b : childFirst ? superBytes: childBytes) {
if (dex[pos++] != b) {
return false;
}
}
return true;
}
/** Creates the byte array for the dex file. */
public static byte[] create(String childClz, String superClz) throws IOException {
boolean childFirst = childClz.compareTo(superClz) < 0;
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
int stringsSize = childBytes.length + superBytes.length;
int padding = -stringsSize & 3;
stringsSize += padding;
ByteArrayOutputStream out = new ByteArrayOutputStream();
// header
out.write("dex\n035\0".getBytes()); // magic
out.write(new byte[24]); // placeholder for checksum and signature
writeInt(out, 0xfc + stringsSize); // file size
writeInt(out, 0x70); // header size
writeInt(out, 0x12345678); // endian constant
writeInt(out, 0); // link size
writeInt(out, 0); // link offset
writeInt(out, 0xa4 + stringsSize); // map offset
writeInt(out, 2); // strings count
writeInt(out, 0x70); // strings offset
writeInt(out, 2); // types count
writeInt(out, 0x78); // types offset
writeInt(out, 0); // prototypes count
writeInt(out, 0); // prototypes offset
writeInt(out, 0); // fields count
writeInt(out, 0); // fields offset
writeInt(out, 0); // methods count
writeInt(out, 0); // methods offset
writeInt(out, 1); // classes count
writeInt(out, 0x80); // classes offset
writeInt(out, 0x5c + stringsSize); // data size
writeInt(out, 0xa0); // data offset
// string map
writeInt(out, 0xa0);
writeInt(out, 0xa0 + (childFirst ? childBytes.length : superBytes.length));
// types
writeInt(out, 0); // first type = first string
writeInt(out, 1); // second type = second string
// class definitions
writeInt(out, childFirst ? 0 : 1); // class to define = child type
writeInt(out, 1); // access flags = public
writeInt(out, childFirst ? 1 : 0); // super class = super type
writeInt(out, 0); // no interface
writeInt(out, -1); // no source file
writeInt(out, 0); // no annotations
writeInt(out, 0); // no class data
writeInt(out, 0); // no static values
// string data
out.write(childFirst ? childBytes : superBytes);
out.write(childFirst ? superBytes : childBytes);
out.write(new byte[padding]);
// annotations
writeInt(out, 0); // no items
// map
writeInt(out, 7); // items count
writeMapItem(out, 0, 1, 0); // header
writeMapItem(out, 1, 2, 0x70); // strings
writeMapItem(out, 2, 2, 0x78); // types
writeMapItem(out, 6, 1, 0x80); // classes
writeMapItem(out, 0x2002, 2, 0xa0); // string data
writeMapItem(out, 0x1003, 1, 0xa0 + stringsSize); // annotations
writeMapItem(out, 0x1000, 1, 0xa4 + stringsSize); // map list
byte[] buf = out.toByteArray();
updateSignature(buf);
updateChecksum(buf);
return buf;
}
private static void updateSignature(byte[] dex) {
// Update SHA-1 signature
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(dex, 32, dex.length - 32);
md.digest(dex, 12, 20);
} catch (NoSuchAlgorithmException | DigestException e) {
throw new RuntimeException(e);
}
}
private static void updateChecksum(byte[] dex) {
// Update Adler32 checksum
Adler32 a32 = new Adler32();
a32.update(dex, 12, dex.length - 12);
int chksum = (int) a32.getValue();
dex[8] = (byte) (chksum & 0xff);
dex[9] = (byte) (chksum >> 8 & 0xff);
dex[10] = (byte) (chksum >> 16 & 0xff);
dex[11] = (byte) (chksum >> 24 & 0xff);
}
private static void writeUleb128(OutputStream out, int value) throws IOException {
while (value > 0x7f) {
out.write((value & 0x7f) | 0x80);
value >>>= 7;
}
out.write(value);
}
private static void writeInt(OutputStream out, int value) throws IOException {
out.write(value);
out.write(value >> 8);
out.write(value >> 16);
out.write(value >> 24);
}
private static void writeMapItem(OutputStream out, int type, int count, int offset) throws IOException {
writeInt(out, type);
writeInt(out, count);
writeInt(out, offset);
}
private static byte[] stringToBytes(String s) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
writeUleb128(bytes, s.length());
// This isn't MUTF-8, but should be OK.
bytes.write(s.getBytes("UTF-8"));
bytes.write(0);
return bytes.toByteArray();
}
private DexCreator() {}
}
package de.robv.android.xposed;
public class GeneClass_Template {
public static java.lang.reflect.Member method;
public static de.robv.android.xposed.XposedBridge.AdditionalHookInfo tAdditionalInfoObj;
public static boolean backup(Object obj, int i) {
return false;
}
public static boolean hook(Object obj, int i) throws Throwable {
Throwable th;
if (!de.robv.android.xposed.XposedBridge.disableHooks) {
Object[] snapshot = tAdditionalInfoObj.callbacks.getSnapshot();
int length = snapshot.length;
if (length != 0) {
de.robv.android.xposed.XC_MethodHook.MethodHookParam methodHookParam = new de.robv.android.xposed.XC_MethodHook.MethodHookParam();
methodHookParam.method = method;
Object[] objArr = new Object[1];
methodHookParam.args = objArr;
methodHookParam.thisObject = obj;
objArr[0] = Integer.valueOf(i);
int i2 = 0;
do {
try {
((de.robv.android.xposed.XC_MethodHook) snapshot[i2]).callBeforeHookedMethod(methodHookParam);
if (methodHookParam.returnEarly) {
i2++;
break;
}
} catch (Throwable th2) {
de.robv.android.xposed.XposedBridge.log(th2);
methodHookParam.setResult(null);
methodHookParam.returnEarly = false;
}
i2++;
} while (i2 < length);
if (!methodHookParam.returnEarly) {
try {
methodHookParam.setResult(Boolean.valueOf(backup(obj, i)));
} catch (Throwable th3) {
methodHookParam.setThrowable(th3);
}
}
i2--;
do {
Object result = methodHookParam.getResult();
Throwable th2 = methodHookParam.getThrowable();
try {
((de.robv.android.xposed.XC_MethodHook) snapshot[i2]).callAfterHookedMethod(methodHookParam);
} catch (Throwable th4) {
de.robv.android.xposed.XposedBridge.log(th4);
if (th2 == null) {
methodHookParam.setResult(result);
} else {
methodHookParam.setThrowable(th2);
}
}
i2--;
} while (i2 >= 0);
if (!methodHookParam.hasThrowable()) {
return ((Boolean) methodHookParam.getResult()).booleanValue();
}
throw methodHookParam.getThrowable();
}
}
return backup(obj, i);
}
public static void setup(java.lang.reflect.Member member, de.robv.android.xposed.XposedBridge.AdditionalHookInfo additionalHookInfo) {
method = member;
tAdditionalInfoObj = additionalHookInfo;
}
}
\ No newline at end of file
package de.robv.android.xposed;
/**
* Hook the initialization of Java-based command-line tools (like pm).
*
* @hide Xposed no longer hooks command-line tools, therefore this interface shouldn't be
* implemented anymore.
*/
public interface IXposedHookCmdInit extends IXposedMod {
/**
* Called very early during startup of a command-line tool.
* @param startupParam Details about the module itself and the started process.
* @throws Throwable Everything is caught, but it will prevent further initialization of the module.
*/
void initCmdApp(StartupParam startupParam) throws Throwable;
/** Data holder for {@link #initCmdApp}. */
final class StartupParam {
/*package*/ StartupParam() {}
/** 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;
}
}
package de.robv.android.xposed;
import android.content.res.XResources;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;
/**
* Get notified when the resources for an app are initialized.
* In {@link #handleInitPackageResources}, resource replacements can be created.
*
* <p>This interface should be implemented by the module's main class. Xposed will take care of
* registering it as a callback automatically.
*/
public interface IXposedHookInitPackageResources extends IXposedMod {
/**
* This method is called when resources for an app are being initialized.
* Modules can call special methods of the {@link XResources} class in order to replace resources.
*
* @param resparam Information about the resources.
* @throws Throwable Everything the callback throws is caught and logged.
*/
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
/** @hide */
final class Wrapper extends XC_InitPackageResources {
private final IXposedHookInitPackageResources instance;
public Wrapper(IXposedHookInitPackageResources instance) {
this.instance = instance;
}
@Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
instance.handleInitPackageResources(resparam);
}
}
}
package de.robv.android.xposed;
import android.app.Application;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
/**
* Get notified when an app ("Android package") is loaded.
* This is especially useful to hook some app-specific methods.
*
* <p>This interface should be implemented by the module's main class. Xposed will take care of
* registering it as a callback automatically.
*/
public interface IXposedHookLoadPackage extends IXposedMod {
/**
* This method is called when an app is loaded. It's called very early, even before
* {@link Application#onCreate} is called.
* Modules can set up their app-specific hooks here.
*
* @param lpparam Information about the app.
* @throws Throwable Everything the callback throws is caught and logged.
*/
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
/** @hide */
final class Wrapper extends XC_LoadPackage {
private final IXposedHookLoadPackage instance;
public Wrapper(IXposedHookLoadPackage instance) {
this.instance = instance;
}
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
instance.handleLoadPackage(lpparam);
}
}
}
package de.robv.android.xposed;
/**
* Hook the initialization of Zygote process(es), from which all the apps are forked.
*
* <p>Implement this interface in your module's main class in order to be notified when Android is
* starting up. In {@link IXposedHookZygoteInit}, you can modify objects and place hooks that should
* be applied for every app. Only the Android framework/system classes are available at that point
* in time. Use {@code null} as class loader for {@link XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)}
* and its variants.
*
* <p>If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead.
*/
public interface IXposedHookZygoteInit extends IXposedMod {
/**
* Called very early during startup of Zygote.
* @param startupParam Details about the module itself and the started process.
* @throws Throwable everything is caught, but will prevent further initialization of the module.
*/
void initZygote(StartupParam startupParam) throws Throwable;
/** Data holder for {@link #initZygote}. */
final class StartupParam {
/*package*/ StartupParam() {}
/** The path to the module's APK. */
public String modulePath;
/**
* Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
* process that starts the system_server.
*/
public boolean startsSystemServer;
}
}
package de.robv.android.xposed;
/** Marker interface for Xposed modules. Cannot be implemented directly. */
/* package */ interface IXposedMod {}
package de.robv.android.xposed;
import android.os.SELinux;
import de.robv.android.xposed.services.BaseService;
import de.robv.android.xposed.services.DirectAccessService;
/**
* A helper to work with (or without) SELinux, abstracting much of its big complexity.
*/
public final class SELinuxHelper {
private SELinuxHelper() {}
/**
* Determines whether SELinux is disabled or enabled.
*
* @return A boolean indicating whether SELinux is enabled.
*/
public static boolean isSELinuxEnabled() {
return sIsSELinuxEnabled;
}
/**
* Determines whether SELinux is permissive or enforcing.
*
* @return A boolean indicating whether SELinux is enforcing.
*/
public static boolean isSELinuxEnforced() {
return sIsSELinuxEnabled && SELinux.isSELinuxEnforced();
}
/**
* Gets the security context of the current process.
*
* @return A String representing the security context of the current process.
*/
public static String getContext() {
return sIsSELinuxEnabled ? SELinux.getContext() : null;
}
/**
* 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,
* don't re-use the result in different process!
*
* @return An instance of the service.
*/
public static BaseService getAppDataFileService() {
if (sServiceAppDataFile != null)
return sServiceAppDataFile;
throw new UnsupportedOperationException();
}
// ----------------------------------------------------------------------------
private static boolean sIsSELinuxEnabled = false;
private static BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
/*package*/ static void initOnce() {
// ed: we assume all selinux policies have been added lively using magiskpolicy
// try {
// sIsSELinuxEnabled = SELinux.isSELinuxEnabled();
// } catch (NoClassDefFoundError ignored) {}
}
/*package*/ static void initForProcess(String packageName) {
// ed: sServiceAppDataFile has been initialized with default value
// if (sIsSELinuxEnabled) {
// if (packageName == null) { // Zygote
// sServiceAppDataFile = new ZygoteService();
// } else if (packageName.equals("android")) { //system_server
// sServiceAppDataFile = BinderService.getService(BinderService.TARGET_APP);
// } else { // app
// sServiceAppDataFile = new DirectAccessService();
// }
// } else {
// sServiceAppDataFile = new DirectAccessService();
// }
}
}
package de.robv.android.xposed;
import java.lang.reflect.Member;
import de.robv.android.xposed.callbacks.IXUnhook;
import de.robv.android.xposed.callbacks.XCallback;
/**
* Callback class for method hooks.
*
* <p>Usually, anonymous subclasses of this class are created which override
* {@link #beforeHookedMethod} and/or {@link #afterHookedMethod}.
*/
public abstract class XC_MethodHook extends XCallback {
/**
* Creates a new callback with default priority.
*/
@SuppressWarnings("deprecation")
public XC_MethodHook() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* <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
* final control over the return value. {@link #beforeHookedMethod} is called as usual, i.e.
* highest priority first.
*
* @param priority See {@link XCallback#priority}.
*/
public XC_MethodHook(int priority) {
super(priority);
}
/**
* Called before the invocation of the method.
*
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to prevent the original method from being called.
*
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged.
*/
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {}
public void callBeforeHookedMethod(MethodHookParam param) throws Throwable {
beforeHookedMethod(param);
}
/**
* Called after the invocation of the method.
*
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to modify the return value of the original method.
*
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged.
*/
protected void afterHookedMethod(MethodHookParam param) throws Throwable {}
public void callAfterHookedMethod(MethodHookParam param) throws Throwable {
afterHookedMethod(param);
}
/**
* Wraps information about the method call and allows to influence it.
*/
public static final class MethodHookParam extends XCallback.Param {
/** @hide */
@SuppressWarnings("deprecation")
public MethodHookParam() {
super();
}
/** The hooked method/constructor. */
public Member method;
/** The {@code this} reference for an instance method, or {@code null} for static methods. */
public Object thisObject;
/** Arguments to the method call. */
public Object[] args;
private Object result = null;
private Throwable throwable = null;
public boolean returnEarly = false;
/** Returns the result of the method call. */
public Object getResult() {
return result;
}
/**
* Modify the result of the method call.
*
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
*/
public void setResult(Object result) {
this.result = result;
this.throwable = null;
this.returnEarly = true;
}
/** Returns the {@link Throwable} thrown by the method, or {@code null}. */
public Throwable getThrowable() {
return throwable;
}
/** Returns true if an exception was thrown by the method. */
public boolean hasThrowable() {
return throwable != null;
}
/**
* Modify the exception thrown of the method call.
*
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
*/
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
this.result = null;
this.returnEarly = true;
}
/** Returns the result of the method call, or throws the Throwable caused by it. */
public Object getResultOrThrowable() throws Throwable {
if (throwable != null)
throw throwable;
return result;
}
}
/**
* An object with which the method/constructor can be unhooked.
*/
public class Unhook implements IXUnhook<XC_MethodHook> {
private final Member hookMethod;
/*package*/ Unhook(Member hookMethod) {
this.hookMethod = hookMethod;
}
/**
* Returns the method/constructor that has been hooked.
*/
public Member getHookedMethod() {
return hookMethod;
}
@Override
public XC_MethodHook getCallback() {
return XC_MethodHook.this;
}
@SuppressWarnings("deprecation")
@Override
public void unhook() {
XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
}
}
}
package de.robv.android.xposed;
import de.robv.android.xposed.callbacks.XCallback;
/**
* A special case of {@link XC_MethodHook} which completely replaces the original method.
*/
public abstract class XC_MethodReplacement extends XC_MethodHook {
/**
* Creates a new callback with default priority.
*/
public XC_MethodReplacement() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
*/
public XC_MethodReplacement(int priority) {
super(priority);
}
/** @hide */
@Override
protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
try {
Object result = replaceHookedMethod(param);
param.setResult(result);
} catch (Throwable t) {
param.setThrowable(t);
}
}
/** @hide */
@Override
@SuppressWarnings("EmptyMethod")
protected final void afterHookedMethod(MethodHookParam param) throws Throwable {}
/**
* Shortcut for replacing a method completely. Whatever is returned/thrown here is taken
* instead of the result of the original method (which will not be called).
*
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Anything that is thrown by the callback will be passed on to the original caller.
*/
@SuppressWarnings("UnusedParameters")
protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable;
/**
* Predefined callback that skips the method without replacements.
*/
public static final XC_MethodReplacement DO_NOTHING = new XC_MethodReplacement(PRIORITY_HIGHEST*2) {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return null;
}
};
/**
* Creates a callback which always returns a specific value.
*
* @param result The value that should be returned to callers of the hooked method.
*/
public static XC_MethodReplacement returnConstant(final Object result) {
return returnConstant(PRIORITY_DEFAULT, result);
}
/**
* Like {@link #returnConstant(Object)}, but allows to specify a priority for the callback.
*
* @param priority See {@link XCallback#priority}.
* @param result The value that should be returned to callers of the hooked method.
*/
public static XC_MethodReplacement returnConstant(int priority, final Object result) {
return new XC_MethodReplacement(priority) {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return result;
}
};
}
}
package de.robv.android.xposed;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import de.robv.android.xposed.services.FileResult;
/**
* This class is basically the same as SharedPreferencesImpl from AOSP, but
* read-only and without listeners support. Instead, it is made to be
* compatible with all ROMs.
*/
public final class XSharedPreferences implements SharedPreferences {
private static final String TAG = "XSharedPreferences";
private final File mFile;
private final String mFilename;
private Map<String, Object> mMap;
private boolean mLoaded = false;
private long mLastModified;
private long mFileSize;
/**
* Read settings from the specified file.
* @param prefFile The file to read the preferences from.
*/
public XSharedPreferences(File prefFile) {
mFile = prefFile;
mFilename = mFile.getAbsolutePath();
startLoadFromDisk();
}
/**
* Read settings from the default preferences for a package.
* These preferences are returned by {@link PreferenceManager#getDefaultSharedPreferences}.
* @param packageName The package name.
*/
public XSharedPreferences(String packageName) {
this(packageName, packageName + "_preferences");
}
/**
* Read settings from a custom preferences file for a package.
* These preferences are returned by {@link Context#getSharedPreferences(String, int)}.
* @param packageName The package name.
* @param prefFileName The file name without ".xml".
*/
public XSharedPreferences(String packageName, String prefFileName) {
mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml");
mFilename = mFile.getAbsolutePath();
startLoadFromDisk();
}
/**
* Tries to make the preferences file world-readable.
*
* <p><strong>Warning:</strong> This is only meant to work around permission "fix" functions that are part
* of some recoveries. It doesn't replace the need to open preferences with {@code MODE_WORLD_READABLE}
* in the module's UI code. Otherwise, Android will set stricter permissions again during the next save.
*
* <p>This will only work if executed as root (e.g. {@code initZygote()}) and only if SELinux is disabled.
*
* @return {@code true} in case the file could be made world-readable.
*/
@SuppressLint("SetWorldReadable")
public boolean makeWorldReadable() {
if (!SELinuxHelper.getAppDataFileService().hasDirectFileAccess())
return false; // It doesn't make much sense to make the file readable if we wouldn't be able to access it anyway.
if (!mFile.exists()) // Just in case - the file should never be created if it doesn't exist.
return false;
return mFile.setReadable(true, false);
}
/**
* Returns the file that is backing these preferences.
*
* <p><strong>Warning:</strong> The file might not be accessible directly.
*/
public File getFile() {
return mFile;
}
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("XSharedPreferences-load") {
@Override
public void run() {
synchronized (XSharedPreferences.this) {
loadFromDiskLocked();
}
}
}.start();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void loadFromDiskLocked() {
if (mLoaded) {
return;
}
Map map = null;
FileResult result = null;
try {
result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified);
if (result.stream != null) {
map = XmlUtils.readMapXml(result.stream);
result.stream.close();
} else {
// The file is unchanged, keep the current values
map = mMap;
}
} catch (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (FileNotFoundException ignored) {
// SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist
} catch (IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
if (result != null && result.stream != null) {
try {
result.stream.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
mLoaded = true;
if (map != null) {
mMap = map;
mLastModified = result.mtime;
mFileSize = result.size;
} else {
mMap = new HashMap<>();
}
notifyAll();
}
/**
* Reload the settings from file if they have changed.
*
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
*/
public synchronized void reload() {
if (hasFileChanged())
startLoadFromDisk();
}
/**
* Check whether the file has changed since the last time it has been loaded.
*
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
*/
public synchronized boolean hasFileChanged() {
try {
FileResult result = SELinuxHelper.getAppDataFileService().statFile(mFilename);
return mLastModified != result.mtime || mFileSize != result.size;
} catch (FileNotFoundException ignored) {
// SharedPreferencesImpl doesn't log anything in case the file doesn't exist
return true;
} catch (IOException e) {
Log.w(TAG, "hasFileChanged", e);
return true;
}
}
private void awaitLoadedLocked() {
while (!mLoaded) {
try {
wait();
} catch (InterruptedException unused) {
}
}
}
/** @hide */
@Override
public Map<String, ?> getAll() {
synchronized (this) {
awaitLoadedLocked();
return new HashMap<>(mMap);
}
}
/** @hide */
@Override
public String getString(String key, String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
@SuppressWarnings("unchecked")
public Set<String> getStringSet(String key, Set<String> defValues) {
synchronized (this) {
awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
}
/** @hide */
@Override
public int getInt(String key, int defValue) {
synchronized (this) {
awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
public long getLong(String key, long defValue) {
synchronized (this) {
awaitLoadedLocked();
Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
public float getFloat(String key, float defValue) {
synchronized (this) {
awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
public boolean getBoolean(String key, boolean defValue) {
synchronized (this) {
awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
public boolean contains(String key) {
synchronized (this) {
awaitLoadedLocked();
return mMap.containsKey(key);
}
}
/** @deprecated Not supported by this implementation. */
@Deprecated
@Override
public Editor edit() {
throw new UnsupportedOperationException("read-only implementation");
}
/** @deprecated Not supported by this implementation. */
@Deprecated
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
}
/** @deprecated Not supported by this implementation. */
@Deprecated
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
}
}
package de.robv.android.xposed.callbacks;
import de.robv.android.xposed.IXposedHookZygoteInit;
/**
* Interface for objects that can be used to remove callbacks.
*
* <p class="warning">Just like hooking methods etc., unhooking applies only to the current process.
* In other process (or when the app is removed from memory and then restarted), the hook will still
* be active. The Zygote process (see {@link IXposedHookZygoteInit}) is an exception, the hook won't
* be inherited by any future processes forked from it in the future.
*
* @param <T> The class of the callback.
*/
public interface IXUnhook<T> {
/**
* Returns the callback that has been registered.
*/
T getCallback();
/**
* Removes the callback.
*/
void unhook();
}
package de.robv.android.xposed.callbacks;
import android.content.res.XResources;
import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/**
* This class is only used for internal purposes, except for the {@link InitPackageResourcesParam}
* subclass.
*/
public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources {
/**
* Creates a new callback with default priority.
* @hide
*/
@SuppressWarnings("deprecation")
public XC_InitPackageResources() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
* @hide
*/
public XC_InitPackageResources(int priority) {
super(priority);
}
/**
* Wraps information about the resources being initialized.
*/
public static final class InitPackageResourcesParam extends XCallback.Param {
/** @hide */
public InitPackageResourcesParam(CopyOnWriteSortedSet<XC_InitPackageResources> callbacks) {
super(callbacks);
}
/** The name of the package for which resources are being loaded. */
public String packageName;
/**
* Reference to the resources that can be used for calls to
* {@link XResources#setReplacement(String, String, String, Object)}.
*/
public XResources res;
}
/** @hide */
@Override
protected void call(Param param) throws Throwable {
if (param instanceof InitPackageResourcesParam)
handleInitPackageResources((InitPackageResourcesParam) param);
}
}
package de.robv.android.xposed.callbacks;
import android.content.res.XResources;
import android.content.res.XResources.ResourceNames;
import android.view.View;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/**
* Callback for hooking layouts. Such callbacks can be passed to {@link XResources#hookLayout}
* and its variants.
*/
public abstract class XC_LayoutInflated extends XCallback {
/**
* Creates a new callback with default priority.
*/
@SuppressWarnings("deprecation")
public XC_LayoutInflated() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
*/
public XC_LayoutInflated(int priority) {
super(priority);
}
/**
* Wraps information about the inflated layout.
*/
public static final class LayoutInflatedParam extends XCallback.Param {
/** @hide */
public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) {
super(callbacks);
}
/** 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;
/** Directory from which the layout was actually loaded (e.g. "layout-sw600dp"). */
public String variant;
/** Resources containing the layout. */
public XResources res;
}
/** @hide */
@Override
protected void call(Param param) throws Throwable {
if (param instanceof LayoutInflatedParam)
handleLayoutInflated((LayoutInflatedParam) param);
}
/**
* This method is called when the hooked layout has been inflated.
*
* @param liparam Information about the layout and the inflated view.
* @throws Throwable Everything the callback throws is caught and logged.
*/
public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable;
/**
* An object with which the callback can be removed.
*/
public class Unhook implements IXUnhook<XC_LayoutInflated> {
private final String resDir;
private final int id;
/** @hide */
public Unhook(String resDir, int id) {
this.resDir = resDir;
this.id = id;
}
/**
* Returns the resource ID of the hooked layout.
*/
public int getId() {
return id;
}
@Override
public XC_LayoutInflated getCallback() {
return XC_LayoutInflated.this;
}
@Override
public void unhook() {
XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
}
}
}
package de.robv.android.xposed.callbacks;
import android.content.pm.ApplicationInfo;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/**
* This class is only used for internal purposes, except for the {@link LoadPackageParam}
* subclass.
*/
public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoadPackage {
/**
* Creates a new callback with default priority.
* @hide
*/
@SuppressWarnings("deprecation")
public XC_LoadPackage() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
* @hide
*/
public XC_LoadPackage(int priority) {
super(priority);
}
/**
* Wraps information about the app being loaded.
*/
public static final class LoadPackageParam extends XCallback.Param {
/** @hide */
public LoadPackageParam(CopyOnWriteSortedSet<XC_LoadPackage> callbacks) {
super(callbacks);
}
/** The name of the package being loaded. */
public String packageName;
/** The process in which the package is executed. */
public String processName;
/** The ClassLoader used for this package. */
public ClassLoader classLoader;
/** 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;
}
/** @hide */
@Override
protected void call(Param param) throws Throwable {
if (param instanceof LoadPackageParam)
handleLoadPackage((LoadPackageParam) param);
}
}
package de.robv.android.xposed.callbacks;
import android.os.Bundle;
import java.io.Serializable;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/**
* Base class for Xposed callbacks.
*
* This class only keeps a priority for ordering multiple callbacks.
* The actual (abstract) callback methods are added by subclasses.
*/
public abstract class XCallback implements Comparable<XCallback> {
/**
* Callback priority, higher number means earlier execution.
*
* <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}
* can be set instead. The values are just for orientation though, Xposed doesn't enforce any
* boundaries on the priority values.
*/
public final int priority;
/** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */
@Deprecated
public XCallback() {
this.priority = PRIORITY_DEFAULT;
}
/** @hide */
public XCallback(int priority) {
this.priority = priority;
}
/**
* Base class for Xposed callback parameters.
*/
public static abstract class Param {
/** @hide */
public final Object[] callbacks;
private Bundle extra;
/** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */
@Deprecated
protected Param() {
callbacks = null;
}
/** @hide */
protected Param(CopyOnWriteSortedSet<? extends XCallback> callbacks) {
this.callbacks = callbacks.getSnapshot();
}
/**
* 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
* separate call to a method, even when the same method is called recursively.
*
* @see #setObjectExtra
* @see #getObjectExtra
*/
public synchronized Bundle getExtra() {
if (extra == null)
extra = new Bundle();
return extra;
}
/**
* Returns an object stored with {@link #setObjectExtra}.
*/
public Object getObjectExtra(String key) {
Serializable o = getExtra().getSerializable(key);
if (o instanceof SerializeWrapper)
return ((SerializeWrapper) o).object;
return null;
}
/**
* Stores any object for the scope of the callback. For data types that support it, use
* the {@link Bundle} returned by {@link #getExtra} instead.
*/
public void setObjectExtra(String key, Object o) {
getExtra().putSerializable(key, new SerializeWrapper(o));
}
private static class SerializeWrapper implements Serializable {
private static final long serialVersionUID = 1L;
private final Object object;
public SerializeWrapper(Object o) {
object = o;
}
}
}
/** @hide */
public static void callAll(Param param) {
if (param.callbacks == null)
throw new IllegalStateException("This object was not created for use with callAll");
for (int i = 0; i < param.callbacks.length; i++) {
try {
((XCallback) param.callbacks[i]).call(param);
} catch (Throwable t) { XposedBridge.log(t); }
}
}
/** @hide */
protected void call(Param param) throws Throwable {}
/** @hide */
@Override
public int compareTo(XCallback other) {
if (this == other)
return 0;
// order descending by priority
if (other.priority != this.priority)
return other.priority - this.priority;
// then randomly
else if (System.identityHashCode(this) < System.identityHashCode(other))
return -1;
else
return 1;
}
/** 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 early, see {@link #priority}. */
public static final int PRIORITY_HIGHEST = 10000;
}
/**
* Contains the base classes for callbacks.
*
* <p>For historical reasons, {@link de.robv.android.xposed.XC_MethodHook} and
* {@link de.robv.android.xposed.XC_MethodReplacement} are directly in the
* {@code de.robv.android.xposed} package.
*/
package de.robv.android.xposed.callbacks;
package de.robv.android.xposed.services;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/** @hide */
public final class DirectAccessService extends BaseService {
@Override
public boolean hasDirectFileAccess() {
return true;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean checkFileAccess(String filename, int mode) {
File file = new File(filename);
if (mode == F_OK && !file.exists()) return false;
if ((mode & R_OK) != 0 && !file.canRead()) return false;
if ((mode & W_OK) != 0 && !file.canWrite()) return false;
if ((mode & X_OK) != 0 && !file.canExecute()) return false;
return true;
}
@Override
public boolean checkFileExists(String filename) {
return new File(filename).exists();
}
@Override
public FileResult statFile(String filename) throws IOException {
File file = new File(filename);
return new FileResult(file.length(), file.lastModified());
}
@Override
public byte[] readFile(String filename) throws IOException {
File file = new File(filename);
byte content[] = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
fis.read(content);
fis.close();
return content;
}
@Override
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
return new FileResult(readFile(filename), size, time);
}
@Override
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
// Shortcut for the simple case
if (offset <= 0 && length <= 0)
return new FileResult(readFile(filename), size, time);
// Check range
if (offset > 0 && offset >= size) {
throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename);
} else if (offset < 0) {
offset = 0;
}
if (length > 0 && (offset + length) > size) {
throw new IllegalArgumentException("Length " + length + " is out of range for " + filename);
} else if (length <= 0) {
length = (int) (size - offset);
}
byte content[] = new byte[length];
FileInputStream fis = new FileInputStream(file);
fis.skip(offset);
fis.read(content);
fis.close();
return new FileResult(content, size, time);
}
/**
* {@inheritDoc}
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
*/
@Override
public InputStream getFileInputStream(String filename) throws IOException {
return new BufferedInputStream(new FileInputStream(filename), 16*1024);
}
/**
* {@inheritDoc}
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
*/
@Override
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16*1024), size, time);
}
}
/**
* Contains file access services provided by the Xposed framework.
*/
package de.robv.android.xposed.services;
<resources>
<string name="app_name">xposedcompat</string>
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment