Commit 3df64c48 authored by Administrator's avatar Administrator

migrate code

parents
Pipeline #1410 canceled with stages
/build
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
# kdiff3 ignore
*.orig
# maven ignore
target/
# eclipse ignore
.settings/
.project
.classpath
# idea ignore
.idea/
*.ipr
*.iml
*.iws
# temp ignore
*.log
*.cache
*.diff
*.patch
*.tmp
# system ignore
.DS_Store
Thumbs.db
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "com.virjar.ratel.api.xposed"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
signingConfigs {
release {
storeFile rootProject.file('script/hermes_key')
storePassword "hermes"
keyAlias "hermes"
keyPassword "hermes"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
compileOnly project(':base-lib-ratel-api')
compileOnly 'com.rover12421.AndroidHideApi:android:1.24'
}
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# org.gradle.jvmargs=-Xmx1536m
org.gradle.jvmargs=-Xmx1536m -DsocksProxyHost=proxy.corp.qunar.com -DsocksProxyPort=10080
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Fri Oct 05 17:01:22 CST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
# 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"
xmlns:tools="http://schemas.android.com/tools"
package="com.virjar.ratel.api.xposed">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning" />
</manifest>
package android.app;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.IBinder;
import android.view.Display;
import java.lang.ref.WeakReference;
import java.util.Map;
import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import static de.robv.android.xposed.XposedHelpers.findClass;
import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
import static de.robv.android.xposed.XposedHelpers.findMethodExactIfExists;
import static de.robv.android.xposed.XposedHelpers.getObjectField;
import static de.robv.android.xposed.XposedHelpers.newInstance;
import static de.robv.android.xposed.XposedHelpers.setFloatField;
/**
* Contains various methods for information about the current app.
*
* <p>For historical reasons, this class is in the {@code android.app} package. It can't be moved
* without breaking compatibility with existing modules.
*/
public final class AndroidAppHelper {
private AndroidAppHelper() {}
private static final Class<?> CLASS_RESOURCES_KEY;
private static final boolean HAS_IS_THEMEABLE;
private static final boolean HAS_THEME_CONFIG_PARAMETER;
static {
CLASS_RESOURCES_KEY = (Build.VERSION.SDK_INT < 19) ?
findClass("android.app.ActivityThread$ResourcesKey", null)
: findClass("android.content.res.ResourcesKey", null);
HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null;
HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && Build.VERSION.SDK_INT >= 21
&& findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Map<Object, WeakReference> getResourcesMap(ActivityThread activityThread) {
if (Build.VERSION.SDK_INT >= 24) {
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
return (Map) getObjectField(resourcesManager, "mResourceImpls");
} else if (Build.VERSION.SDK_INT >= 19) {
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
return (Map) getObjectField(resourcesManager, "mActiveResources");
} else {
return (Map) getObjectField(activityThread, "mActiveResources");
}
}
/* For SDK 15 & 16 */
private static Object createResourcesKey(String resDir, float scale) {
try {
if (HAS_IS_THEMEABLE)
return newInstance(CLASS_RESOURCES_KEY, resDir, scale, false);
else
return newInstance(CLASS_RESOURCES_KEY, resDir, scale);
} catch (Throwable t) {
XposedBridge.log(t);
return null;
}
}
/* For SDK 17 & 18 & 23 */
private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
try {
if (HAS_THEME_CONFIG_PARAMETER)
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null);
else if (HAS_IS_THEMEABLE)
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false);
else
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale);
} catch (Throwable t) {
XposedBridge.log(t);
return null;
}
}
/* For SDK 19 - 22 */
private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale, IBinder token) {
try {
if (HAS_THEME_CONFIG_PARAMETER)
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null, token);
else if (HAS_IS_THEMEABLE)
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, token);
else
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, token);
} catch (Throwable t) {
XposedBridge.log(t);
return null;
}
}
/* For SDK 24+ */
private static Object createResourcesKey(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
try {
return newInstance(CLASS_RESOURCES_KEY, resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfiguration, compatInfo);
} catch (Throwable t) {
XposedBridge.log(t);
return null;
}
}
/** @hide */
public static void addActiveResource(String resDir, float scale, boolean isThemeable, Resources resources) {
addActiveResource(resDir, resources);
}
/** @hide */
public static void addActiveResource(String resDir, Resources resources) {
ActivityThread thread = ActivityThread.currentActivityThread();
if (thread == null) {
return;
}
Object resourcesKey;
if (Build.VERSION.SDK_INT >= 24) {
CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
setFloatField(compatInfo, "applicationScale", resources.hashCode());
resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo);
} else if (Build.VERSION.SDK_INT == 23) {
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
} else if (Build.VERSION.SDK_INT >= 19) {
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode(), null);
} else if (Build.VERSION.SDK_INT >= 17) {
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
} else {
resourcesKey = createResourcesKey(resDir, resources.hashCode());
}
if (resourcesKey != null) {
if (Build.VERSION.SDK_INT >= 24) {
Object resImpl = getObjectField(resources, "mResourcesImpl");
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
} else {
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resources));
}
}
}
/**
* Returns the name of the current process. It's usually the same as the main package name.
*/
public static String currentProcessName() {
String processName = ActivityThread.currentPackageName();
if (processName == null)
return "android";
return processName;
}
/**
* Returns information about the main application in the current process.
*
* <p>In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
* Keyguard which both have {@code android:process="com.android.systemui"} set in their
* manifest. In those cases, the first application that was initialized will be returned.
*/
public static ApplicationInfo currentApplicationInfo() {
ActivityThread am = ActivityThread.currentActivityThread();
if (am == null)
return null;
Object boundApplication = getObjectField(am, "mBoundApplication");
if (boundApplication == null)
return null;
return (ApplicationInfo) getObjectField(boundApplication, "appInfo");
}
/**
* Returns the Android package name of the main application in the current process.
*
* <p>In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
* Keyguard which both have {@code android:process="com.android.systemui"} set in their
* manifest. In those cases, the first application that was initialized will be returned.
*/
public static String currentPackageName() {
ApplicationInfo ai = currentApplicationInfo();
return (ai != null) ? ai.packageName : "android";
}
/**
* Returns the main {@link android.app.Application} object in the current process.
*
* <p>In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the
* Keyguard which both have {@code android:process="com.android.systemui"} set in their
* manifest. In those cases, the first application that was initialized will be returned.
*/
public static Application currentApplication() {
return ActivityThread.currentApplication();
}
/** @deprecated Use {@link XSharedPreferences} instead. */
@SuppressWarnings("UnusedParameters")
@Deprecated
public static SharedPreferences getSharedPreferencesForPackage(String packageName, String prefFileName, int mode) {
return new XSharedPreferences(packageName, prefFileName);
}
/** @deprecated Use {@link XSharedPreferences} instead. */
@Deprecated
public static SharedPreferences getDefaultSharedPreferencesForPackage(String packageName) {
return new XSharedPreferences(packageName);
}
/** @deprecated Use {@link XSharedPreferences#reload} instead. */
@Deprecated
public static void reloadSharedPreferencesIfNeeded(SharedPreferences pref) {
if (pref instanceof XSharedPreferences) {
((XSharedPreferences) pref).reload();
}
}
}
\ No newline at end of file
package de.robv.android.xposed;
import android.content.res.AssetManager;
import android.content.res.Resources;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipFile;
import dalvik.system.DexFile;
import external.org.apache.commons.lang3.ClassUtils;
import external.org.apache.commons.lang3.reflect.MemberUtils;
/**
* 改造自原始xposed,如果系统中存在xposed框架,那么我们会和系统已存xposed交互。两分xposed的实现会引发classloader不一致冲突。这个工具类可以避免classloader不一致冲突问题
*/
public final class ClassLoaderSafeXposedHelper {
private ClassLoaderSafeXposedHelper() {
}
private static final HashMap<ClassLoader, HashMap<String, Field>> fieldCache = new HashMap<>();
private static final HashMap<ClassLoader, HashMap<String, Method>> methodCache = new HashMap<>();
private static final HashMap<ClassLoader, HashMap<String, Constructor<?>>> constructorCache = new HashMap<>();
/**
* Look up a class with the specified class loader.
*
* <p>There are various allowed syntaxes for the class name, but it's recommended to use one of
* these:
* <ul>
* <li>{@code java.lang.String}
* <li>{@code java.lang.String[]} (array)
* <li>{@code android.app.ActivityThread.ResourcesKey}
* <li>{@code android.app.ActivityThread$ResourcesKey}
* </ul>
*
* @param className The class name in one of the formats mentioned above.
* @param classLoader The class loader, or {@code null} for the boot class loader.
* @return A reference to the class.
* @throws ClassNotFoundError In case the class was not found.
*/
public static Class<?> findClass(String className, ClassLoader classLoader) {
if (classLoader == null)
classLoader = XposedBridge.BOOTCLASSLOADER;
try {
return ClassUtils.getClass(classLoader, className, false);
} catch (ClassNotFoundException e) {
throw new ClassNotFoundError(e);
}
}
/**
* Look up and return a class if it exists.
* Like {@link #findClass}, but doesn't throw an exception if the class doesn't exist.
*
* @param className The class name.
* @param classLoader The class loader, or {@code null} for the boot class loader.
* @return A reference to the class, or {@code null} if it doesn't exist.
*/
public static Class<?> findClassIfExists(String className, ClassLoader classLoader) {
try {
return findClass(className, classLoader);
} catch (ClassNotFoundError e) {
return null;
}
}
/**
* Look up a field in a class and set it to accessible.
*
* @param clazz The class which either declares or inherits the field.
* @param fieldName The field name.
* @return A reference to the field.
* @throws NoSuchFieldError In case the field was not found.
*/
public static Field findField(Class<?> clazz, String fieldName) {
String fullFieldName = clazz.getName() + '#' + fieldName;
HashMap<String, Field> classLoaderFieldCache = fieldCache.get(clazz.getClassLoader());
if (classLoaderFieldCache == null) {
classLoaderFieldCache = new HashMap<>();
fieldCache.put(clazz.getClassLoader(), classLoaderFieldCache);
}
if (classLoaderFieldCache.containsKey(fullFieldName)) {
Field field = classLoaderFieldCache.get(fullFieldName);
if (field == null)
throw new NoSuchFieldError(fullFieldName);
return field;
}
try {
Field field = findFieldRecursiveImpl(clazz, fieldName);
field.setAccessible(true);
classLoaderFieldCache.put(fullFieldName, field);
return field;
} catch (NoSuchFieldException e) {
classLoaderFieldCache.put(fullFieldName, null);
throw new NoSuchFieldError(fullFieldName);
}
}
/**
* Look up and return a field if it exists.
* Like {@link #findField}, but doesn't throw an exception if the field doesn't exist.
*
* @param clazz The class which either declares or inherits the field.
* @param fieldName The field name.
* @return A reference to the field, or {@code null} if it doesn't exist.
*/
public static Field findFieldIfExists(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName);
} catch (NoSuchFieldError e) {
return null;
}
}
private static Field findFieldRecursiveImpl(Class<?> clazz, String fieldName) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
while (true) {
clazz = clazz.getSuperclass();
if (clazz == null || clazz.equals(Object.class))
break;
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException ignored) {
}
}
throw e;
}
}
/**
* Returns the first field of the given type in a class.
* Might be useful for Proguard'ed classes to identify fields with unique types.
*
* @param clazz The class which either declares or inherits the field.
* @param type The type of the field.
* @return A reference to the first field of the given type.
* @throws NoSuchFieldError In case no matching field was not found.
*/
public static Field findFirstFieldByExactType(Class<?> clazz, Class<?> type) {
Class<?> clz = clazz;
do {
for (Field field : clz.getDeclaredFields()) {
if (field.getType() == type) {
field.setAccessible(true);
return field;
}
}
} while ((clz = clz.getSuperclass()) != null);
throw new NoSuchFieldError("Field of type " + type.getName() + " in class " + clazz.getName());
}
/**
* Look up a method and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length - 1];
Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
/**
* Look up a method and hook it. The last argument must be the callback for the hook.
*
* <p>This combines calls to {@link #findMethodExact(Class, String, Object...)} and
* {@link XposedBridge#hookMethod}.
*
* <p class="warning">The method must be declared or overridden in the given class, inherited
* methods are not considered! That's because each method implementation exists only once in
* the memory, and when classes inherit it, they just get another reference to the implementation.
* Hooking a method therefore applies to all classes inheriting the same implementation. You
* have to expect that the hook applies to subclasses (unless they override the method), but you
* shouldn't have to worry about hooks applying to superclasses, hence this "limitation".
* There could be undesired or even dangerous hooks otherwise, e.g. if you hook
* {@code SomeClass.equals()} and that class doesn't override the {@code equals()} on some ROMs,
* making you hook {@code Object.equals()} instead.
*
* <p>There are two ways to specify the parameter types. If you already have a reference to the
* {@link Class}, use that. For Android framework classes, you can often use something like
* {@code String.class}. If you don't have the class reference, you can simply use the
* full class name as a string, e.g. {@code java.lang.String} or {@code com.example.MyClass}.
* It will be passed to {@link #findClass} with the same class loader that is used for the target
* method, see its documentation for the allowed notations.
*
* <p>Primitive types, such as {@code int}, can be specified using {@code int.class} (recommended)
* or {@code Integer.TYPE}. Note that {@code Integer.class} doesn't refer to {@code int} but to
* {@code Integer}, which is a normal class (boxed primitive). Therefore it must not be used when
* the method expects an {@code int} parameter - it has to be used for {@code Integer} parameters
* though, so check the method signature in detail.
*
* <p>As last argument to this method (after the list of target method parameters), you need
* to specify the callback that should be executed when the method is invoked. It's usually
* an anonymous subclass of {@link XC_MethodHook} or {@link XC_MethodReplacement}.
*
* <p><b>Example</b>
* <pre class="prettyprint">
* // In order to hook this method ...
* package com.example;
* public class SomeClass {
* public int doSomething(String s, int i, MyClass m) {
* ...
* }
* }
*
* // ... you can use this call:
* findAndHookMethod("com.example.SomeClass", lpparam.classLoader, String.class, int.class, "com.example.MyClass", new XC_MethodHook() {
* &#64;Override
* protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
* String oldText = (String) param.args[0];
* Log.d("MyModule", oldText);
*
* param.args[0] = "test";
* param.args[1] = 42; // auto-boxing is working here
* setBooleanField(param.args[2], "great", true);
*
* // This would not work (as MyClass can't be resolved at compile time):
* // MyClass myClass = (MyClass) param.args[2];
* // myClass.great = true;
* }
* });
* </pre>
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypesAndCallback The parameter types of the target method, plus the callback.
* @return An object which can be used to remove the callback again.
* @throws NoSuchMethodError In case the method was not found.
* @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
*/
public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
}
/**
* Look up a method in a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Method findMethodExact(Class<?> clazz, String methodName, Object... parameterTypes) {
return findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypes));
}
/**
* Look up and return a method if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Method findMethodExactIfExists(Class<?> clazz, String methodName, Object... parameterTypes) {
try {
return findMethodExact(clazz, methodName, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a method in a class and set it to accessible.
* The method must be declared or overridden in the given class.
*
* <p>See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} for details about
* the method and parameter type resolution.
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypes The parameter types of the target method.
* @return A reference to the method.
* @throws NoSuchMethodError In case the method was not found.
* @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
*/
public static Method findMethodExact(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
return findMethodExact(findClass(className, classLoader), methodName, getParameterClasses(classLoader, parameterTypes));
}
/**
* Look up and return a method if it exists.
* Like {@link #findMethodExact(String, ClassLoader, String, Object...)}, but doesn't throw an
* exception if the method doesn't exist.
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypes The parameter types of the target method.
* @return A reference to the method, or {@code null} if it doesn't exist.
*/
public static Method findMethodExactIfExists(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
try {
return findMethodExact(className, classLoader, methodName, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a method in a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*
* <p>This variant requires that you already have reference to all the parameter types.
*/
public synchronized static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact";
HashMap<String, Method> classLoaderMethodCache = methodCache.get(clazz.getClassLoader());
if (classLoaderMethodCache == null) {
classLoaderMethodCache = new HashMap<>();
methodCache.put(clazz.getClassLoader(), classLoaderMethodCache);
}
if (classLoaderMethodCache.containsKey(fullMethodName)) {
Method method = classLoaderMethodCache.get(fullMethodName);
if (method == null)
throw new NoSuchMethodError(fullMethodName);
return method;
}
try {
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
classLoaderMethodCache.put(fullMethodName, method);
return method;
} catch (NoSuchMethodException e) {
classLoaderMethodCache.put(fullMethodName, null);
throw new NoSuchMethodError(fullMethodName);
}
}
/**
* Returns an array of all methods declared/overridden in a class with the specified parameter types.
*
* <p>The return type is optional, it will not be compared if it is {@code null}.
* Use {@code void.class} if you want to search for methods returning nothing.
*
* @param clazz The class to look in.
* @param returnType The return type, or {@code null} (see above).
* @param parameterTypes The parameter types.
* @return An array with matching methods, all set to accessible already.
*/
public static Method[] findMethodsByExactParameters(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes) {
List<Method> result = new LinkedList<>();
for (Method method : clazz.getDeclaredMethods()) {
if (returnType != null && returnType != method.getReturnType())
continue;
Class<?>[] methodParameterTypes = method.getParameterTypes();
if (parameterTypes.length != methodParameterTypes.length)
continue;
boolean match = true;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != methodParameterTypes[i]) {
match = false;
break;
}
}
if (!match)
continue;
method.setAccessible(true);
result.add(method);
}
return result.toArray(new Method[result.size()]);
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>This does'nt only look for exact matches, but for the best match. All considered candidates
* must be compatible with the given parameter types, i.e. the parameters must be assignable
* to the method's formal parameters. Inherited methods are considered here.
*
* @param clazz The class which declares, inherits or overrides the method.
* @param methodName The method name.
* @param parameterTypes The types of the method's parameters.
* @return A reference to the best-matching method.
* @throws NoSuchMethodError In case no suitable method was found.
*/
public synchronized static Method findMethodBestMatch(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#bestmatch";
HashMap<String, Method> classLoaderMethodCache = methodCache.get(clazz.getClassLoader());
if (classLoaderMethodCache == null) {
classLoaderMethodCache = new HashMap<>();
methodCache.put(clazz.getClassLoader(), classLoaderMethodCache);
}
if (classLoaderMethodCache.containsKey(fullMethodName)) {
Method method = classLoaderMethodCache.get(fullMethodName);
if (method == null)
throw new NoSuchMethodError(fullMethodName);
return method;
}
try {
Method method = findMethodExact(clazz, methodName, parameterTypes);
classLoaderMethodCache.put(fullMethodName, method);
return method;
} catch (NoSuchMethodError ignored) {
}
Method bestMatch = null;
Class<?> clz = clazz;
boolean considerPrivateMethods = true;
do {
for (Method method : clz.getDeclaredMethods()) {
// don't consider private methods of superclasses
if (!considerPrivateMethods && Modifier.isPrivate(method.getModifiers()))
continue;
// compare name and parameters
if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
// get accessible version of method
if (bestMatch == null || MemberUtils.compareParameterTypes(
method.getParameterTypes(),
bestMatch.getParameterTypes(),
parameterTypes) < 0) {
bestMatch = method;
}
}
}
considerPrivateMethods = false;
} while ((clz = clz.getSuperclass()) != null);
if (bestMatch != null) {
bestMatch.setAccessible(true);
classLoaderMethodCache.put(fullMethodName, bestMatch);
return bestMatch;
} else {
NoSuchMethodError e = new NoSuchMethodError(fullMethodName);
classLoaderMethodCache.put(fullMethodName, null);
throw e;
}
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects.
*/
public static Method findMethodBestMatch(Class<?> clazz, String methodName, Object... args) {
return findMethodBestMatch(clazz, methodName, getParameterTypes(args));
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects. For any item that is
* {@code null}, the type is taken from {@code parameterTypes} instead.
*/
public static Method findMethodBestMatch(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object[] args) {
Class<?>[] argsClasses = null;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != null)
continue;
if (argsClasses == null)
argsClasses = getParameterTypes(args);
parameterTypes[i] = argsClasses[i];
}
return findMethodBestMatch(clazz, methodName, parameterTypes);
}
/**
* Returns an array with the classes of the given objects.
*/
public static Class<?>[] getParameterTypes(Object... args) {
Class<?>[] clazzes = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
clazzes[i] = (args[i] != null) ? args[i].getClass() : null;
}
return clazzes;
}
/**
* Retrieve classes from an array, where each element might either be a Class
* already, or a String with the full class name.
*/
private static Class<?>[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypesAndCallback) {
Class<?>[] parameterClasses = null;
for (int i = parameterTypesAndCallback.length - 1; i >= 0; i--) {
Object type = parameterTypesAndCallback[i];
if (type == null)
throw new ClassNotFoundError("parameter type must not be null", null);
// ignore trailing callback
if (type instanceof XC_MethodHook)
continue;
if (parameterClasses == null)
parameterClasses = new Class<?>[i + 1];
if (type instanceof Class)
parameterClasses[i] = (Class<?>) type;
else if (type instanceof String)
parameterClasses[i] = findClass((String) type, classLoader);
else
throw new ClassNotFoundError("parameter type must either be specified as Class or String", null);
}
// if there are no arguments for the method
if (parameterClasses == null)
parameterClasses = new Class<?>[0];
return parameterClasses;
}
/**
* Returns an array of the given classes.
*/
public static Class<?>[] getClassesAsArray(Class<?>... clazzes) {
return clazzes;
}
private static String getParametersString(Class<?>... clazzes) {
StringBuilder sb = new StringBuilder("(");
boolean first = true;
for (Class<?> clazz : clazzes) {
if (first)
first = false;
else
sb.append(",");
if (clazz != null)
sb.append(clazz.getCanonicalName());
else
sb.append("null");
}
sb.append(")");
return sb.toString();
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(Class<?> clazz, Object... parameterTypes) {
return findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypes));
}
/**
* Look up and return a constructor if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExactIfExists(Class<?> clazz, Object... parameterTypes) {
try {
return findConstructorExact(clazz, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(String className, ClassLoader classLoader, Object... parameterTypes) {
return findConstructorExact(findClass(className, classLoader), getParameterClasses(classLoader, parameterTypes));
}
/**
* Look up and return a constructor if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExactIfExists(String className, ClassLoader classLoader, Object... parameterTypes) {
try {
return findConstructorExact(className, classLoader, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(Class<?> clazz, Class<?>... parameterTypes) {
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact";
HashMap<String, Constructor<?>> classLoaderConstructorCache = constructorCache.get(clazz.getClassLoader());
if (classLoaderConstructorCache == null) {
classLoaderConstructorCache = new HashMap<>();
constructorCache.put(clazz.getClassLoader(), classLoaderConstructorCache);
}
if (classLoaderConstructorCache.containsKey(fullConstructorName)) {
Constructor<?> constructor = classLoaderConstructorCache.get(fullConstructorName);
if (constructor == null)
throw new NoSuchMethodError(fullConstructorName);
return constructor;
}
try {
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
classLoaderConstructorCache.put(fullConstructorName, constructor);
return constructor;
} catch (NoSuchMethodException e) {
classLoaderConstructorCache.put(fullConstructorName, null);
throw new NoSuchMethodError(fullConstructorName);
}
}
/**
* Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length - 1];
Constructor<?> m = findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
/**
* Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) {
return findAndHookConstructor(findClass(className, classLoader), parameterTypesAndCallback);
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>... parameterTypes) {
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#bestmatch";
HashMap<String, Constructor<?>> classLoaderConstructorCache = constructorCache.get(clazz.getClassLoader());
if (classLoaderConstructorCache == null) {
classLoaderConstructorCache = new HashMap<>();
constructorCache.put(clazz.getClassLoader(), classLoaderConstructorCache);
}
if (classLoaderConstructorCache.containsKey(fullConstructorName)) {
Constructor<?> constructor = classLoaderConstructorCache.get(fullConstructorName);
if (constructor == null)
throw new NoSuchMethodError(fullConstructorName);
return constructor;
}
try {
Constructor<?> constructor = findConstructorExact(clazz, parameterTypes);
classLoaderConstructorCache.put(fullConstructorName, constructor);
return constructor;
} catch (NoSuchMethodError ignored) {
}
Constructor<?> bestMatch = null;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
// compare name and parameters
if (ClassUtils.isAssignable(parameterTypes, constructor.getParameterTypes(), true)) {
// get accessible version of method
if (bestMatch == null || MemberUtils.compareParameterTypes(
constructor.getParameterTypes(),
bestMatch.getParameterTypes(),
parameterTypes) < 0) {
bestMatch = constructor;
}
}
}
if (bestMatch != null) {
bestMatch.setAccessible(true);
classLoaderConstructorCache.put(fullConstructorName, bestMatch);
return bestMatch;
} else {
NoSuchMethodError e = new NoSuchMethodError(fullConstructorName);
classLoaderConstructorCache.put(fullConstructorName, null);
throw e;
}
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Object... args) {
return findConstructorBestMatch(clazz, getParameterTypes(args));
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects. For any item that is
* {@code null}, the type is taken from {@code parameterTypes} instead.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>[] parameterTypes, Object[] args) {
Class<?>[] argsClasses = null;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != null)
continue;
if (argsClasses == null)
argsClasses = getParameterTypes(args);
parameterTypes[i] = argsClasses[i];
}
return findConstructorBestMatch(clazz, parameterTypes);
}
/**
* Thrown when a class loader is unable to find a class. Unlike {@link ClassNotFoundException},
* callers are not forced to explicitly catch this. If uncaught, the error will be passed to the
* next caller in the stack.
*/
public static final class ClassNotFoundError extends Error {
private static final long serialVersionUID = -1070936889459514628L;
/**
* @hide
*/
public ClassNotFoundError(Throwable cause) {
super(cause);
}
/**
* @hide
*/
public ClassNotFoundError(String detailMessage, Throwable cause) {
super(detailMessage, cause);
}
}
/**
* Returns the index of the first parameter declared with the given type.
*
* @throws NoSuchFieldError if there is no parameter with that type.
* @hide
*/
public static int getFirstParameterIndexByType(Member method, Class<?> type) {
Class<?>[] classes = (method instanceof Method) ?
((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
for (int i = 0; i < classes.length; i++) {
if (classes[i] == type) {
return i;
}
}
throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
}
/**
* Returns the index of the parameter declared with the given type, ensuring that there is exactly one such parameter.
*
* @throws NoSuchFieldError if there is no or more than one parameter with that type.
* @hide
*/
public static int getParameterIndexByType(Member method, Class<?> type) {
Class<?>[] classes = (method instanceof Method) ?
((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
int idx = -1;
for (int i = 0; i < classes.length; i++) {
if (classes[i] == type) {
if (idx == -1) {
idx = i;
} else {
throw new NoSuchFieldError("More than one parameter of type " + type + " found in " + method);
}
}
}
if (idx != -1) {
return idx;
} else {
throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
}
}
//#################################################################################################
/**
* Sets the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setObjectField(Object obj, String fieldName, Object value) {
try {
findField(obj.getClass(), fieldName).set(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setBooleanField(Object obj, String fieldName, boolean value) {
try {
findField(obj.getClass(), fieldName).setBoolean(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setByteField(Object obj, String fieldName, byte value) {
try {
findField(obj.getClass(), fieldName).setByte(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setCharField(Object obj, String fieldName, char value) {
try {
findField(obj.getClass(), fieldName).setChar(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setDoubleField(Object obj, String fieldName, double value) {
try {
findField(obj.getClass(), fieldName).setDouble(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setFloatField(Object obj, String fieldName, float value) {
try {
findField(obj.getClass(), fieldName).setFloat(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setIntField(Object obj, String fieldName, int value) {
try {
findField(obj.getClass(), fieldName).setInt(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setLongField(Object obj, String fieldName, long value) {
try {
findField(obj.getClass(), fieldName).setLong(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static void setShortField(Object obj, String fieldName, short value) {
try {
findField(obj.getClass(), fieldName).setShort(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/**
* Returns the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static Object getObjectField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).get(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* For inner classes, returns the surrounding instance, i.e. the {@code this} reference of the surrounding class.
*/
public static Object getSurroundingThis(Object obj) {
return getObjectField(obj, "this$0");
}
/**
* Returns the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean getBooleanField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getBoolean(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Returns the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static byte getByteField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getByte(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Returns the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static char getCharField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getChar(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Returns the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static double getDoubleField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getDouble(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Returns the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static float getFloatField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getFloat(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Returns the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static int getIntField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getInt(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Returns the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static long getLongField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getLong(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Returns the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}.
*/
public static short getShortField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getShort(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/**
* Sets the value of a static object field in the given class. See also {@link #findField}.
*/
public static void setStaticObjectField(Class<?> clazz, String fieldName, Object value) {
try {
findField(clazz, fieldName).set(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code boolean} field in the given class. See also {@link #findField}.
*/
public static void setStaticBooleanField(Class<?> clazz, String fieldName, boolean value) {
try {
findField(clazz, fieldName).setBoolean(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code byte} field in the given class. See also {@link #findField}.
*/
public static void setStaticByteField(Class<?> clazz, String fieldName, byte value) {
try {
findField(clazz, fieldName).setByte(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code char} field in the given class. See also {@link #findField}.
*/
public static void setStaticCharField(Class<?> clazz, String fieldName, char value) {
try {
findField(clazz, fieldName).setChar(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code double} field in the given class. See also {@link #findField}.
*/
public static void setStaticDoubleField(Class<?> clazz, String fieldName, double value) {
try {
findField(clazz, fieldName).setDouble(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code float} field in the given class. See also {@link #findField}.
*/
public static void setStaticFloatField(Class<?> clazz, String fieldName, float value) {
try {
findField(clazz, fieldName).setFloat(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code int} field in the given class. See also {@link #findField}.
*/
public static void setStaticIntField(Class<?> clazz, String fieldName, int value) {
try {
findField(clazz, fieldName).setInt(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code long} field in the given class. See also {@link #findField}.
*/
public static void setStaticLongField(Class<?> clazz, String fieldName, long value) {
try {
findField(clazz, fieldName).setLong(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code short} field in the given class. See also {@link #findField}.
*/
public static void setStaticShortField(Class<?> clazz, String fieldName, short value) {
try {
findField(clazz, fieldName).setShort(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/**
* Returns the value of a static object field in the given class. See also {@link #findField}.
*/
public static Object getStaticObjectField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).get(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Returns the value of a static {@code boolean} field in the given class. See also {@link #findField}.
*/
public static boolean getStaticBooleanField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getBoolean(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code byte} field in the given class. See also {@link #findField}.
*/
public static byte getStaticByteField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getByte(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code char} field in the given class. See also {@link #findField}.
*/
public static char getStaticCharField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getChar(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code double} field in the given class. See also {@link #findField}.
*/
public static double getStaticDoubleField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getDouble(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code float} field in the given class. See also {@link #findField}.
*/
public static float getStaticFloatField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getFloat(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code int} field in the given class. See also {@link #findField}.
*/
public static int getStaticIntField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getInt(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code long} field in the given class. See also {@link #findField}.
*/
public static long getStaticLongField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getLong(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/**
* Sets the value of a static {@code short} field in the given class. See also {@link #findField}.
*/
public static short getStaticShortField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getShort(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/**
* Calls an instance or static method of the given object.
* The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
*
* @param obj The object instance. A class reference is not sufficient!
* @param methodName The method name.
* @param args The arguments for the method call.
* @throws NoSuchMethodError In case no suitable method was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
*/
public static Object callMethod(Object obj, String methodName, Object... args) {
try {
return findMethodBestMatch(obj.getClass(), methodName, args).invoke(obj, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls an instance or static method of the given object.
* See {@link #callMethod(Object, String, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* methods with the same name, especially if you call it with {@code null} parameters.
*/
public static Object callMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args) {
try {
return findMethodBestMatch(obj.getClass(), methodName, parameterTypes, args).invoke(obj, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls a static method of the given class.
* The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
*
* @param clazz The class reference.
* @param methodName The method name.
* @param args The arguments for the method call.
* @throws NoSuchMethodError In case no suitable method was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
*/
public static Object callStaticMethod(Class<?> clazz, String methodName, Object... args) {
try {
return findMethodBestMatch(clazz, methodName, args).invoke(null, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls a static method of the given class.
* See {@link #callStaticMethod(Class, String, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* methods with the same name, especially if you call it with {@code null} parameters.
*/
public static Object callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) {
try {
return findMethodBestMatch(clazz, methodName, parameterTypes, args).invoke(null, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* This class provides a wrapper for an exception thrown by a method invocation.
*
* @see #callMethod(Object, String, Object...)
* @see #callStaticMethod(Class, String, Object...)
* @see #newInstance(Class, Object...)
*/
public static final class InvocationTargetError extends Error {
private static final long serialVersionUID = -1070936889459514628L;
/**
* @hide
*/
public InvocationTargetError(Throwable cause) {
super(cause);
}
}
//#################################################################################################
/**
* Creates a new instance of the given class.
* The constructor is resolved using {@link #findConstructorBestMatch(Class, Object...)}.
*
* @param clazz The class reference.
* @param args The arguments for the constructor call.
* @throws NoSuchMethodError In case no suitable constructor was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
* @throws InstantiationError In case the class cannot be instantiated.
*/
public static Object newInstance(Class<?> clazz, Object... args) {
try {
return findConstructorBestMatch(clazz, args).newInstance(args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
} catch (InstantiationException e) {
throw new InstantiationError(e.getMessage());
}
}
/**
* Creates a new instance of the given class.
* See {@link #newInstance(Class, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* constructors with the same name, especially if you call it with {@code null} parameters.
*/
public static Object newInstance(Class<?> clazz, Class<?>[] parameterTypes, Object... args) {
try {
return findConstructorBestMatch(clazz, parameterTypes, args).newInstance(args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
} catch (InstantiationException e) {
throw new InstantiationError(e.getMessage());
}
}
//#################################################################################################
//#################################################################################################
/**
* Loads an asset from a resource object and returns the content as {@code byte} array.
*
* @param res The resources from which the asset should be loaded.
* @param path The path to the asset, as in {@link AssetManager#open}.
* @return The content of the asset.
*/
public static byte[] assetAsByteArray(Resources res, String path) throws IOException {
return inputStreamToByteArray(res.getAssets().open(path));
}
/*package*/
static byte[] inputStreamToByteArray(InputStream is) throws IOException {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
byte[] temp = new byte[1024];
int read;
while ((read = is.read(temp)) > 0) {
buf.write(temp, 0, read);
}
is.close();
return buf.toByteArray();
}
/**
* Invokes the {@link Closeable#close()} method, ignoring IOExceptions.
*/
/*package*/
static void closeSilently(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ignored) {
}
}
}
/**
* Invokes the {@link DexFile#close()} method, ignoring IOExceptions.
*/
/*package*/
static void closeSilently(DexFile dexFile) {
if (dexFile != null) {
try {
dexFile.close();
} catch (IOException ignored) {
}
}
}
/**
* Invokes the {@link ZipFile#close()} method, ignoring IOExceptions.
*/
/*package*/
static void closeSilently(ZipFile zipFile) {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException ignored) {
}
}
}
/**
* Returns the lowercase hex string representation of a file's MD5 hash sum.
*/
public static String getMD5Sum(String file) throws IOException {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
InputStream is = new FileInputStream(file);
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
is.close();
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
return bigInt.toString(16);
} catch (NoSuchAlgorithmException e) {
return "";
}
}
//#################################################################################################
/*package*/
static boolean fileContains(File file, String str) throws IOException {
// There are certainly more efficient algorithms (e.g. Boyer-Moore used in grep),
// but the naive approach should be sufficient here.
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
String line;
while ((line = in.readLine()) != null) {
if (line.contains(str)) {
return true;
}
}
return false;
} finally {
closeSilently(in);
}
}
//#################################################################################################
/**
* Returns the method that is overridden by the given method.
* It returns {@code null} if the method doesn't override another method or if that method is
* abstract, i.e. if this is the first implementation in the hierarchy.
*/
/*package*/
static Method getOverriddenMethod(Method method) {
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers)) {
return null;
}
String name = method.getName();
Class<?>[] parameters = method.getParameterTypes();
Class<?> clazz = method.getDeclaringClass().getSuperclass();
while (clazz != null) {
try {
Method superMethod = clazz.getDeclaredMethod(name, parameters);
modifiers = superMethod.getModifiers();
if (!Modifier.isPrivate(modifiers) && !Modifier.isAbstract(modifiers)) {
return superMethod;
} else {
return null;
}
} catch (NoSuchMethodException ignored) {
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* Returns all methods which this class overrides.
*/
/*package*/
static Set<Method> getOverriddenMethods(Class<?> clazz) {
Set<Method> methods = new HashSet<>();
for (Method method : clazz.getDeclaredMethods()) {
Method overridden = getOverriddenMethod(method);
if (overridden != null) {
methods.add(overridden);
}
}
return methods;
}
//#################################################################################################
// TODO helpers for view traversing
/*To make it easier, I will try and implement some more helpers:
- add view before/after existing view (I already mentioned that I think)
- get index of view in its parent
- get next/previous sibling (maybe with an optional argument "type", that might be ImageView.class and gives you the next sibling that is an ImageView)?
- get next/previous element (similar to the above, but would also work if the next element has a different parent, it would just go up the hierarchy and then down again until it finds a matching element)
- find the first child that is an instance of a specified class
- find all (direct or indirect) children of a specified class
*/
}
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*/public 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;
/**
* 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 com.virjar.ratel.api.xposed.IRXposedHookInitPackageResources;
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>
* <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, IRXposedHookInitPackageResources {
/**
* This method is called when resources for an app are being initialized.
* Modules can call special methods of the 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 com.virjar.ratel.api.xposed.IRXposedHookLoadPackage;
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, IRXposedHookLoadPackage {
/**
* 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;
import com.virjar.ratel.api.xposed.IRXposedHookZygoteInit;
/**
* 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, IRXposedHookZygoteInit {
/**
* Data holder for {@link #initZygote}.
*/
class StartupParam extends IRXposedHookZygoteInit.StartupParam {
public StartupParam() {
}
}
}
package de.robv.android.xposed;
import com.virjar.ratel.api.xposed.IRXposedMod;
/**
* Marker interface for Xposed modules. Cannot be implemented directly.
*/
/* package */ public interface IXposedMod extends IRXposedMod {
}
package de.robv.android.xposed;
import com.virjar.ratel.api.rposed.RC_MethodHook;
public class XC2RC_MethodHook extends RC_MethodHook {
private XC_MethodHook delegate;
XC2RC_MethodHook(XC_MethodHook delegate) {
super(delegate.priority);
this.delegate = delegate;
}
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
XC_MethodHook.MethodHookParam methodHookParam = new XC_MethodHook.MethodHookParam();
methodHookParam.method = param.method;
methodHookParam.thisObject = param.thisObject;
methodHookParam.args = param.args;
XposedHelpers.setObjectField(methodHookParam, "result", param.getResult());
XposedHelpers.setObjectField(methodHookParam, "throwable", param.getThrowable());
XposedHelpers.setBooleanField(methodHookParam, "returnEarly",
XposedHelpers.getBooleanField(param, "returnEarly"));
delegate.callBeforeHookedMethod(methodHookParam);
param.args = methodHookParam.args;
XposedHelpers.setObjectField(param, "result", methodHookParam.getResult());
XposedHelpers.setObjectField(param, "throwable", methodHookParam.getThrowable());
XposedHelpers.setBooleanField(param, "returnEarly", XposedHelpers.getBooleanField(methodHookParam, "returnEarly"));
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XC_MethodHook.MethodHookParam methodHookParam = new XC_MethodHook.MethodHookParam();
methodHookParam.method = param.method;
methodHookParam.thisObject = param.thisObject;
methodHookParam.args = param.args;
XposedHelpers.setObjectField(methodHookParam, "result", param.getResult());
XposedHelpers.setObjectField(methodHookParam, "throwable", param.getThrowable());
XposedHelpers.setBooleanField(methodHookParam, "returnEarly",
XposedHelpers.getBooleanField(param, "returnEarly"));
delegate.callAfterHookedMethod(methodHookParam);
param.args = methodHookParam.args;
XposedHelpers.setObjectField(param, "result", methodHookParam.getResult());
XposedHelpers.setObjectField(param, "throwable", methodHookParam.getThrowable());
XposedHelpers.setBooleanField(param, "returnEarly", XposedHelpers.getBooleanField(methodHookParam, "returnEarly"));
}
@Override
public String toString() {
return "XC2RC_MethodHook{" +
"delegate=" + delegate +
'}';
}
}
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>
* <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>
* <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>
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to prevent the original method from being called.
* <p>
* <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>
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to modify the return value of the original method.
* <p>
* <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 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>
* <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>
* <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*/
public 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 com.virjar.ratel.api.RatelToolKit;
import java.io.File;
import java.util.Map;
import java.util.Set;
/**
* 请注意,由于在Android较高版本上文件权限限制问题,XSharedPreferences可能无法使用。建议尽量避免使用XSharedPreferences,使用ContentProvider替代
*/
public class XSharedPreferences implements SharedPreferences {
private SharedPreferences sharedPreferences;
private File file;
/**
* Read settings from the specified file.
*
* @param prefFile The file to read the preferences from.
*/
public XSharedPreferences(File prefFile) {
String fileName = "ratel_" + prefFile.getName() + "_preferences.xml";
sharedPreferences = RatelToolKit.sContext.getSharedPreferences(fileName, Context.MODE_PRIVATE);
file = new File(Environment.getDataDirectory(), "data/" + RatelToolKit.sContext.getPackageName() + "/shared_prefs/" + fileName);
}
/**
* 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) {
String fileName = "ratel_" + prefFileName + "_preferences.xml";
sharedPreferences = RatelToolKit.sContext.getSharedPreferences(fileName, Context.MODE_PRIVATE);
file = new File(Environment.getDataDirectory(), "data/" + RatelToolKit.sContext.getPackageName() + "/shared_prefs/" + fileName);
}
/**
* 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() {
return true;
}
/**
* Returns the file that is backing these preferences.
*
* <p><strong>Warning:</strong> The file might not be accessible directly.
*/
public File getFile() {
return file;
}
/**
* 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() {
}
@Override
public Map<String, ?> getAll() {
return sharedPreferences.getAll();
}
@Override
public String getString(String key, String defValue) {
return sharedPreferences.getString(key, defValue);
}
@Override
public Set<String> getStringSet(String key, Set<String> defValues) {
return sharedPreferences.getStringSet(key, defValues);
}
@Override
public int getInt(String key, int defValue) {
return sharedPreferences.getInt(key, defValue);
}
@Override
public long getLong(String key, long defValue) {
return sharedPreferences.getLong(key, defValue);
}
@Override
public float getFloat(String key, float defValue) {
return sharedPreferences.getFloat(key, defValue);
}
@Override
public boolean getBoolean(String key, boolean defValue) {
return sharedPreferences.getBoolean(key, defValue);
}
@Override
public boolean contains(String key) {
return sharedPreferences.contains(key);
}
@Override
public Editor edit() {
return sharedPreferences.edit();
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
}
}
package de.robv.android.xposed;
import android.util.Log;
import com.virjar.ratel.api.RatelToolKit;
import com.virjar.ratel.api.rposed.RC_MethodHook;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* This class contains most of Xposed's central logic, such as initialization and callbacks used by
* the native side. It also includes methods to add new hooks.
*/
@SuppressWarnings("JniMissingFunction")
public final class XposedBridge {
/**
* The system class loader which can be used to locate Android framework classes.
* Application classes cannot be retrieved from it.
*/
public static final ClassLoader BOOTCLASSLOADER = XposedBridge.class.getClassLoader();
/**
* @hide
*/
public static final String TAG = "RATEL-Xposed-Bridge";
/**
* @deprecated Use {@link #getXposedVersion()} instead.
*/
@Deprecated
public static int XPOSED_BRIDGE_VERSION;
/*package*/ static boolean isZygote = true; // ed: RuntimeInit.main() tool process not supported yet
private static int runtime = 2; // ed: only support art
private static final int RUNTIME_DALVIK = 1;
private static final int RUNTIME_ART = 2;
public static boolean disableHooks = false;
// This field is set "magically" on MIUI.
/*package*/ static long BOOT_START_TIME;
private static final Object[] EMPTY_ARRAY = new Object[0];
private XposedBridge() {
}
/**
* Called when native methods and other things are initialized, but before preloading classes etc.
*
* @hide
*/
@SuppressWarnings("deprecation")
public static void main(String[] args) {
// ed: moved
}
/**
* @hide
*/
// protected static final class ToolEntryPoint {
// protected static void main(String[] args) {
// isZygote = false;
// XposedBridge.main(args);
// }
// }
private static void initXResources() throws IOException {
// ed: no support for now
}
/**
* Returns the currently installed version of the Xposed framework.
*/
public static int getXposedVersion() {
// ed: fixed value for now
return 90;
}
/**
* Writes a message to the Xposed error log.
*
* <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.
*
* @param text The log message.
*/
public synchronized static void log(String text) {
Log.i(TAG, text);
}
/**
* Logs a stack trace to the Xposed error log.
*
* <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.
*
* @param t The Throwable object for the stack trace.
*/
public synchronized static void log(Throwable t) {
Log.e(TAG, Log.getStackTraceString(t));
}
/**
* Hook any method (or constructor) with the specified callback. See below for some wrappers
* that make it easier to find a method/constructor in one step.
*
* @param hookMethod The method to be hooked.
* @param callback The callback to be executed when the hooked method is called.
* @return An object that can be used to remove the hook.
* @see XposedHelpers#findAndHookMethod(java.lang.String, ClassLoader, String, Object...)
* @see XposedHelpers#findAndHookMethod(Class, String, Object...)
* @see #hookAllMethods
* @see XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...)
* @see XposedHelpers#findAndHookConstructor(Class, Object...)
* @see #hookAllConstructors
*/
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
XC2RC_MethodHook xc2RC_methodHook = new XC2RC_MethodHook(callback);
unhookMap.put(callback, xc2RC_methodHook);
RatelToolKit.usedHookProvider.hookMethod(hookMethod, xc2RC_methodHook);
return callback.new Unhook(hookMethod);
}
private static Map<XC_MethodHook, XC2RC_MethodHook> unhookMap = new ConcurrentHashMap<>();
/**
* Removes the callback for a hooked method/constructor.
*
* @param hookMethod The method for which the callback should be removed.
* @param callback The reference to the callback as specified in {@link #hookMethod}.
* @deprecated Use {@link XC_MethodHook.Unhook#unhook} instead. An instance of the {@code Unhook}
* class is returned when you hook the method.
*/
@Deprecated
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
XC2RC_MethodHook xc2RC_methodHook = unhookMap.get(callback);
if (xc2RC_methodHook == null) {
return;
}
RatelToolKit.usedHookProvider.unhookMethod(hookMethod, xc2RC_methodHook);
}
/**
* Hooks all methods with a certain name that were declared in the specified class. Inherited
* methods and constructors are not considered. For constructors, use
* {@link #hookAllConstructors} instead.
*
* @param hookClass The class to check for declared methods.
* @param methodName The name of the method(s) to hook.
* @param callback The callback to be executed when the hooked methods are called.
* @return A set containing one object for each found method which can be used to unhook it.
*/
@SuppressWarnings("UnusedReturnValue")
public static Set<XC_MethodHook.Unhook> hookAllMethods(Class<?> hookClass, String methodName, XC_MethodHook callback) {
Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();
for (Member method : hookClass.getDeclaredMethods())
if (method.getName().equals(methodName))
unhooks.add(hookMethod(method, callback));
return unhooks;
}
/**
* Hook all constructors of the specified class.
*
* @param hookClass The class to check for constructors.
* @param callback The callback to be executed when the hooked constructors are called.
* @return A set containing one object for each found constructor which can be used to unhook it.
*/
@SuppressWarnings("UnusedReturnValue")
public static Set<XC_MethodHook.Unhook> hookAllConstructors(Class<?> hookClass, XC_MethodHook callback) {
Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();
for (Member constructor : hookClass.getDeclaredConstructors())
unhooks.add(hookMethod(constructor, callback));
return unhooks;
}
/**
* This method is called as a replacement for hooked methods.
*/
public static Object handleHookedMethod(Member method, long originalMethodId, Object additionalInfoObj,
Object thisObject, Object[] args) throws Throwable {
throw new UnsupportedOperationException();
}
/**
* 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}
* in your module class and Xposed will take care of registering it as a callback.
*
* @param callback The callback to be executed.
* @hide
*/
public static void hookLoadPackage(XC_LoadPackage callback) {
Log.w(RatelToolKit.TAG, "now support for hookLoadPackage");
}
public static void clearLoadedPackages() {
Log.w(RatelToolKit.TAG, "now support for clearLoadedPackages");
}
/**
* 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}
* in your module class and Xposed will take care of registering it as a callback.
*
* @param callback The callback to be executed.
* @hide
*/
public static void hookInitPackageResources(XC_InitPackageResources callback) {
Log.w(RatelToolKit.TAG, "now support for hookInitPackageResources");
// TODO not supported yet
// synchronized (sInitPackageResourcesCallbacks) {
// sInitPackageResourcesCallbacks.add(callback);
// }
}
public static void clearInitPackageResources() {
Log.w(RatelToolKit.TAG, "now support for clearInitPackageResources");
}
/**
* Intercept every call to the specified method and call a handler function instead.
*
* @param method The method to intercept
*/
private synchronized static void hookMethodNative(final Member method, Class<?> declaringClass,
int slot, final Object additionalInfoObj) {
throw new UnsupportedOperationException("not support");
}
private static Object invokeOriginalMethodNative(Member method,
Object thisObject, Object[] args)
throws Throwable {
return RatelToolKit.usedHookProvider.invokeOriginalMethod(method, thisObject, args);
}
/**
* 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.
*
* <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
* creates overhead and skips further hooks by other modules. Instead, just hook (don't replace)
* the method and call {@code param.setResult(null)} in {@link XC_MethodHook#beforeHookedMethod}
* if the original method should be skipped.
*
* @param method The method to be called.
* @param thisObject For non-static calls, the "this" pointer, otherwise {@code null}.
* @param args Arguments for the method call as Object[] array.
* @return The result returned from the invoked method.
* @throws NullPointerException if {@code receiver == null} for a non-static method
* @throws IllegalAccessException if this method is not accessible (see {@link AccessibleObject})
* @throws IllegalArgumentException if the number of arguments doesn't match the number of parameters, the receiver
* is incompatible with the declaring class, or an argument could not be unboxed
* or converted by a widening conversion to the corresponding parameter type
* @throws InvocationTargetException if an exception was thrown by the invoked method
*/
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
throws Throwable {
if (args == null) {
args = EMPTY_ARRAY;
}
return invokeOriginalMethodNative(method, thisObject, args);
}
/**
* @hide
*/
public static final class CopyOnWriteSortedSet<E> {
private transient volatile Object[] elements = EMPTY_ARRAY;
@SuppressWarnings("UnusedReturnValue")
public synchronized boolean add(E e) {
int index = indexOf(e);
if (index >= 0)
return false;
Object[] newElements = new Object[elements.length + 1];
System.arraycopy(elements, 0, newElements, 0, elements.length);
newElements[elements.length] = e;
Arrays.sort(newElements);
elements = newElements;
return true;
}
@SuppressWarnings("UnusedReturnValue")
public synchronized boolean remove(E e) {
int index = indexOf(e);
if (index == -1)
return false;
Object[] newElements = new Object[elements.length - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index, elements.length - index - 1);
elements = newElements;
return true;
}
private int indexOf(Object o) {
for (int i = 0; i < elements.length; i++) {
if (o.equals(elements[i]))
return i;
}
return -1;
}
public Object[] getSnapshot() {
return elements;
}
public synchronized void clear() {
elements = EMPTY_ARRAY;
}
}
// public static class AdditionalHookInfo {
// public final CopyOnWriteSortedSet<RC_MethodHook> callbacks;
// //TODO 貌似parameterTypes和returnType都没有意义,再观察一下
// public final Class<?>[] parameterTypes;
// public final Class<?> returnType;
//
// public AdditionalHookInfo(CopyOnWriteSortedSet<RC_MethodHook> callbacks, Class<?>[] parameterTypes, Class<?> returnType) {
// this.callbacks = callbacks;
// this.parameterTypes = parameterTypes;
// this.returnType = returnType;
// }
// }
}
package de.robv.android.xposed;
import android.content.res.AssetManager;
import android.content.res.Resources;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipFile;
import dalvik.system.DexFile;
import external.org.apache.commons.lang3.ClassUtils;
import external.org.apache.commons.lang3.reflect.MemberUtils;
/**
* Helpers that simplify hooking and calling methods/constructors, getting and settings fields, ...
*/
public final class XposedHelpers {
private XposedHelpers() {}
private static final HashMap<String, Field> fieldCache = new HashMap<>();
private static final HashMap<String, Method> methodCache = new HashMap<>();
private static final HashMap<String, Constructor<?>> constructorCache = new HashMap<>();
private static final WeakHashMap<Object, HashMap<String, Object>> additionalFields = new WeakHashMap<>();
private static final HashMap<String, ThreadLocal<AtomicInteger>> sMethodDepth = new HashMap<>();
/**
* Look up a class with the specified class loader.
*
* <p>There are various allowed syntaxes for the class name, but it's recommended to use one of
* these:
* <ul>
* <li>{@code java.lang.String}
* <li>{@code java.lang.String[]} (array)
* <li>{@code android.app.ActivityThread.ResourcesKey}
* <li>{@code android.app.ActivityThread$ResourcesKey}
* </ul>
*
* @param className The class name in one of the formats mentioned above.
* @param classLoader The class loader, or {@code null} for the boot class loader.
* @return A reference to the class.
* @throws ClassNotFoundError In case the class was not found.
*/
public static Class<?> findClass(String className, ClassLoader classLoader) {
if (classLoader == null)
classLoader = XposedBridge.BOOTCLASSLOADER;
try {
return ClassUtils.getClass(classLoader, className, false);
} catch (ClassNotFoundException e) {
throw new ClassNotFoundError(e);
}
}
/**
* Look up and return a class if it exists.
* Like {@link #findClass}, but doesn't throw an exception if the class doesn't exist.
*
* @param className The class name.
* @param classLoader The class loader, or {@code null} for the boot class loader.
* @return A reference to the class, or {@code null} if it doesn't exist.
*/
public static Class<?> findClassIfExists(String className, ClassLoader classLoader) {
try {
return findClass(className, classLoader);
} catch (ClassNotFoundError e) {
return null;
}
}
/**
* Look up a field in a class and set it to accessible.
*
* @param clazz The class which either declares or inherits the field.
* @param fieldName The field name.
* @return A reference to the field.
* @throws NoSuchFieldError In case the field was not found.
*/
public static Field findField(Class<?> clazz, String fieldName) {
String fullFieldName = clazz.getName() + '#' + fieldName;
if (fieldCache.containsKey(fullFieldName)) {
Field field = fieldCache.get(fullFieldName);
if (field == null)
throw new NoSuchFieldError(fullFieldName);
return field;
}
try {
Field field = findFieldRecursiveImpl(clazz, fieldName);
field.setAccessible(true);
fieldCache.put(fullFieldName, field);
return field;
} catch (NoSuchFieldException e) {
fieldCache.put(fullFieldName, null);
throw new NoSuchFieldError(fullFieldName);
}
}
/**
* Look up and return a field if it exists.
* Like {@link #findField}, but doesn't throw an exception if the field doesn't exist.
*
* @param clazz The class which either declares or inherits the field.
* @param fieldName The field name.
* @return A reference to the field, or {@code null} if it doesn't exist.
*/
public static Field findFieldIfExists(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName);
} catch (NoSuchFieldError e) {
return null;
}
}
private static Field findFieldRecursiveImpl(Class<?> clazz, String fieldName) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
while (true) {
clazz = clazz.getSuperclass();
if (clazz == null || clazz.equals(Object.class))
break;
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException ignored) {}
}
throw e;
}
}
/**
* Returns the first field of the given type in a class.
* Might be useful for Proguard'ed classes to identify fields with unique types.
*
* @param clazz The class which either declares or inherits the field.
* @param type The type of the field.
* @return A reference to the first field of the given type.
* @throws NoSuchFieldError In case no matching field was not found.
*/
public static Field findFirstFieldByExactType(Class<?> clazz, Class<?> type) {
Class<?> clz = clazz;
do {
for (Field field : clz.getDeclaredFields()) {
if (field.getType() == type) {
field.setAccessible(true);
return field;
}
}
} while ((clz = clz.getSuperclass()) != null);
throw new NoSuchFieldError("Field of type " + type.getName() + " in class " + clazz.getName());
}
/**
* Look up a method and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
/**
* Look up a method and hook it. The last argument must be the callback for the hook.
*
* <p>This combines calls to {@link #findMethodExact(Class, String, Object...)} and
* {@link XposedBridge#hookMethod}.
*
* <p class="warning">The method must be declared or overridden in the given class, inherited
* methods are not considered! That's because each method implementation exists only once in
* the memory, and when classes inherit it, they just get another reference to the implementation.
* Hooking a method therefore applies to all classes inheriting the same implementation. You
* have to expect that the hook applies to subclasses (unless they override the method), but you
* shouldn't have to worry about hooks applying to superclasses, hence this "limitation".
* There could be undesired or even dangerous hooks otherwise, e.g. if you hook
* {@code SomeClass.equals()} and that class doesn't override the {@code equals()} on some ROMs,
* making you hook {@code Object.equals()} instead.
*
* <p>There are two ways to specify the parameter types. If you already have a reference to the
* {@link Class}, use that. For Android framework classes, you can often use something like
* {@code String.class}. If you don't have the class reference, you can simply use the
* full class name as a string, e.g. {@code java.lang.String} or {@code com.example.MyClass}.
* It will be passed to {@link #findClass} with the same class loader that is used for the target
* method, see its documentation for the allowed notations.
*
* <p>Primitive types, such as {@code int}, can be specified using {@code int.class} (recommended)
* or {@code Integer.TYPE}. Note that {@code Integer.class} doesn't refer to {@code int} but to
* {@code Integer}, which is a normal class (boxed primitive). Therefore it must not be used when
* the method expects an {@code int} parameter - it has to be used for {@code Integer} parameters
* though, so check the method signature in detail.
*
* <p>As last argument to this method (after the list of target method parameters), you need
* to specify the callback that should be executed when the method is invoked. It's usually
* an anonymous subclass of {@link XC_MethodHook} or {@link XC_MethodReplacement}.
*
* <p><b>Example</b>
* <pre class="prettyprint">
* // In order to hook this method ...
* package com.example;
* public class SomeClass {
* public int doSomething(String s, int i, MyClass m) {
* ...
* }
* }
*
* // ... you can use this call:
* findAndHookMethod("com.example.SomeClass", lpparam.classLoader, String.class, int.class, "com.example.MyClass", new XC_MethodHook() {
* &#64;Override
* protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
* String oldText = (String) param.args[0];
* Log.d("MyModule", oldText);
*
* param.args[0] = "test";
* param.args[1] = 42; // auto-boxing is working here
* setBooleanField(param.args[2], "great", true);
*
* // This would not work (as MyClass can't be resolved at compile time):
* // MyClass myClass = (MyClass) param.args[2];
* // myClass.great = true;
* }
* });
* </pre>
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypesAndCallback The parameter types of the target method, plus the callback.
* @throws NoSuchMethodError In case the method was not found.
* @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
* @return An object which can be used to remove the callback again.
*/
public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
}
/**
* Look up a method in a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Method findMethodExact(Class<?> clazz, String methodName, Object... parameterTypes) {
return findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypes));
}
/**
* Look up and return a method if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Method findMethodExactIfExists(Class<?> clazz, String methodName, Object... parameterTypes) {
try {
return findMethodExact(clazz, methodName, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a method in a class and set it to accessible.
* The method must be declared or overridden in the given class.
*
* <p>See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} for details about
* the method and parameter type resolution.
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypes The parameter types of the target method.
* @throws NoSuchMethodError In case the method was not found.
* @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
* @return A reference to the method.
*/
public static Method findMethodExact(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
return findMethodExact(findClass(className, classLoader), methodName, getParameterClasses(classLoader, parameterTypes));
}
/**
* Look up and return a method if it exists.
* Like {@link #findMethodExact(String, ClassLoader, String, Object...)}, but doesn't throw an
* exception if the method doesn't exist.
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypes The parameter types of the target method.
* @return A reference to the method, or {@code null} if it doesn't exist.
*/
public static Method findMethodExactIfExists(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
try {
return findMethodExact(className, classLoader, methodName, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a method in a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*
* <p>This variant requires that you already have reference to all the parameter types.
*/
public static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact";
if (methodCache.containsKey(fullMethodName)) {
Method method = methodCache.get(fullMethodName);
if (method == null)
throw new NoSuchMethodError(fullMethodName);
return method;
}
try {
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
methodCache.put(fullMethodName, method);
return method;
} catch (NoSuchMethodException e) {
methodCache.put(fullMethodName, null);
throw new NoSuchMethodError(fullMethodName);
}
}
/**
* Returns an array of all methods declared/overridden in a class with the specified parameter types.
*
* <p>The return type is optional, it will not be compared if it is {@code null}.
* Use {@code void.class} if you want to search for methods returning nothing.
*
* @param clazz The class to look in.
* @param returnType The return type, or {@code null} (see above).
* @param parameterTypes The parameter types.
* @return An array with matching methods, all set to accessible already.
*/
public static Method[] findMethodsByExactParameters(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes) {
List<Method> result = new LinkedList<>();
for (Method method : clazz.getDeclaredMethods()) {
if (returnType != null && returnType != method.getReturnType())
continue;
Class<?>[] methodParameterTypes = method.getParameterTypes();
if (parameterTypes.length != methodParameterTypes.length)
continue;
boolean match = true;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != methodParameterTypes[i]) {
match = false;
break;
}
}
if (!match)
continue;
method.setAccessible(true);
result.add(method);
}
return result.toArray(new Method[result.size()]);
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>This does'nt only look for exact matches, but for the best match. All considered candidates
* must be compatible with the given parameter types, i.e. the parameters must be assignable
* to the method's formal parameters. Inherited methods are considered here.
*
* @param clazz The class which declares, inherits or overrides the method.
* @param methodName The method name.
* @param parameterTypes The types of the method's parameters.
* @return A reference to the best-matching method.
* @throws NoSuchMethodError In case no suitable method was found.
*/
public static Method findMethodBestMatch(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#bestmatch";
if (methodCache.containsKey(fullMethodName)) {
Method method = methodCache.get(fullMethodName);
if (method == null)
throw new NoSuchMethodError(fullMethodName);
return method;
}
try {
Method method = findMethodExact(clazz, methodName, parameterTypes);
methodCache.put(fullMethodName, method);
return method;
} catch (NoSuchMethodError ignored) {}
Method bestMatch = null;
Class<?> clz = clazz;
boolean considerPrivateMethods = true;
do {
for (Method method : clz.getDeclaredMethods()) {
// don't consider private methods of superclasses
if (!considerPrivateMethods && Modifier.isPrivate(method.getModifiers()))
continue;
// compare name and parameters
if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
// get accessible version of method
if (bestMatch == null || MemberUtils.compareParameterTypes(
method.getParameterTypes(),
bestMatch.getParameterTypes(),
parameterTypes) < 0) {
bestMatch = method;
}
}
}
considerPrivateMethods = false;
} while ((clz = clz.getSuperclass()) != null);
if (bestMatch != null) {
bestMatch.setAccessible(true);
methodCache.put(fullMethodName, bestMatch);
return bestMatch;
} else {
NoSuchMethodError e = new NoSuchMethodError(fullMethodName);
methodCache.put(fullMethodName, null);
throw e;
}
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects.
*/
public static Method findMethodBestMatch(Class<?> clazz, String methodName, Object... args) {
return findMethodBestMatch(clazz, methodName, getParameterTypes(args));
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects. For any item that is
* {@code null}, the type is taken from {@code parameterTypes} instead.
*/
public static Method findMethodBestMatch(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object[] args) {
Class<?>[] argsClasses = null;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != null)
continue;
if (argsClasses == null)
argsClasses = getParameterTypes(args);
parameterTypes[i] = argsClasses[i];
}
return findMethodBestMatch(clazz, methodName, parameterTypes);
}
/**
* Returns an array with the classes of the given objects.
*/
public static Class<?>[] getParameterTypes(Object... args) {
Class<?>[] clazzes = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
clazzes[i] = (args[i] != null) ? args[i].getClass() : null;
}
return clazzes;
}
/**
* Retrieve classes from an array, where each element might either be a Class
* already, or a String with the full class name.
*/
private static Class<?>[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypesAndCallback) {
Class<?>[] parameterClasses = null;
for (int i = parameterTypesAndCallback.length - 1; i >= 0; i--) {
Object type = parameterTypesAndCallback[i];
if (type == null)
throw new ClassNotFoundError("parameter type must not be null", null);
// ignore trailing callback
if (type instanceof XC_MethodHook)
continue;
if (parameterClasses == null)
parameterClasses = new Class<?>[i+1];
if (type instanceof Class)
parameterClasses[i] = (Class<?>) type;
else if (type instanceof String)
parameterClasses[i] = findClass((String) type, classLoader);
else
throw new ClassNotFoundError("parameter type must either be specified as Class or String", null);
}
// if there are no arguments for the method
if (parameterClasses == null)
parameterClasses = new Class<?>[0];
return parameterClasses;
}
/**
* Returns an array of the given classes.
*/
public static Class<?>[] getClassesAsArray(Class<?>... clazzes) {
return clazzes;
}
private static String getParametersString(Class<?>... clazzes) {
StringBuilder sb = new StringBuilder("(");
boolean first = true;
for (Class<?> clazz : clazzes) {
if (first)
first = false;
else
sb.append(",");
if (clazz != null)
sb.append(clazz.getCanonicalName());
else
sb.append("null");
}
sb.append(")");
return sb.toString();
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(Class<?> clazz, Object... parameterTypes) {
return findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypes));
}
/**
* Look up and return a constructor if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExactIfExists(Class<?> clazz, Object... parameterTypes) {
try {
return findConstructorExact(clazz, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(String className, ClassLoader classLoader, Object... parameterTypes) {
return findConstructorExact(findClass(className, classLoader), getParameterClasses(classLoader, parameterTypes));
}
/**
* Look up and return a constructor if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExactIfExists(String className, ClassLoader classLoader, Object... parameterTypes) {
try {
return findConstructorExact(className, classLoader, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(Class<?> clazz, Class<?>... parameterTypes) {
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact";
if (constructorCache.containsKey(fullConstructorName)) {
Constructor<?> constructor = constructorCache.get(fullConstructorName);
if (constructor == null)
throw new NoSuchMethodError(fullConstructorName);
return constructor;
}
try {
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
constructorCache.put(fullConstructorName, constructor);
return constructor;
} catch (NoSuchMethodException e) {
constructorCache.put(fullConstructorName, null);
throw new NoSuchMethodError(fullConstructorName);
}
}
/**
* Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
Constructor<?> m = findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
/**
* Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) {
return findAndHookConstructor(findClass(className, classLoader), parameterTypesAndCallback);
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>... parameterTypes) {
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#bestmatch";
if (constructorCache.containsKey(fullConstructorName)) {
Constructor<?> constructor = constructorCache.get(fullConstructorName);
if (constructor == null)
throw new NoSuchMethodError(fullConstructorName);
return constructor;
}
try {
Constructor<?> constructor = findConstructorExact(clazz, parameterTypes);
constructorCache.put(fullConstructorName, constructor);
return constructor;
} catch (NoSuchMethodError ignored) {}
Constructor<?> bestMatch = null;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
// compare name and parameters
if (ClassUtils.isAssignable(parameterTypes, constructor.getParameterTypes(), true)) {
// get accessible version of method
if (bestMatch == null || MemberUtils.compareParameterTypes(
constructor.getParameterTypes(),
bestMatch.getParameterTypes(),
parameterTypes) < 0) {
bestMatch = constructor;
}
}
}
if (bestMatch != null) {
bestMatch.setAccessible(true);
constructorCache.put(fullConstructorName, bestMatch);
return bestMatch;
} else {
NoSuchMethodError e = new NoSuchMethodError(fullConstructorName);
constructorCache.put(fullConstructorName, null);
throw e;
}
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Object... args) {
return findConstructorBestMatch(clazz, getParameterTypes(args));
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects. For any item that is
* {@code null}, the type is taken from {@code parameterTypes} instead.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>[] parameterTypes, Object[] args) {
Class<?>[] argsClasses = null;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != null)
continue;
if (argsClasses == null)
argsClasses = getParameterTypes(args);
parameterTypes[i] = argsClasses[i];
}
return findConstructorBestMatch(clazz, parameterTypes);
}
/**
* Thrown when a class loader is unable to find a class. Unlike {@link ClassNotFoundException},
* callers are not forced to explicitly catch this. If uncaught, the error will be passed to the
* next caller in the stack.
*/
public static final class ClassNotFoundError extends Error {
private static final long serialVersionUID = -1070936889459514628L;
/** @hide */
public ClassNotFoundError(Throwable cause) {
super(cause);
}
/** @hide */
public ClassNotFoundError(String detailMessage, Throwable cause) {
super(detailMessage, cause);
}
}
/**
* Returns the index of the first parameter declared with the given type.
*
* @throws NoSuchFieldError if there is no parameter with that type.
* @hide
*/
public static int getFirstParameterIndexByType(Member method, Class<?> type) {
Class<?>[] classes = (method instanceof Method) ?
((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
for (int i = 0 ; i < classes.length; i++) {
if (classes[i] == type) {
return i;
}
}
throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
}
/**
* Returns the index of the parameter declared with the given type, ensuring that there is exactly one such parameter.
*
* @throws NoSuchFieldError if there is no or more than one parameter with that type.
* @hide
*/
public static int getParameterIndexByType(Member method, Class<?> type) {
Class<?>[] classes = (method instanceof Method) ?
((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
int idx = -1;
for (int i = 0 ; i < classes.length; i++) {
if (classes[i] == type) {
if (idx == -1) {
idx = i;
} else {
throw new NoSuchFieldError("More than one parameter of type " + type + " found in " + method);
}
}
}
if (idx != -1) {
return idx;
} else {
throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
}
}
//#################################################################################################
/** Sets the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setObjectField(Object obj, String fieldName, Object value) {
try {
findField(obj.getClass(), fieldName).set(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setBooleanField(Object obj, String fieldName, boolean value) {
try {
findField(obj.getClass(), fieldName).setBoolean(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setByteField(Object obj, String fieldName, byte value) {
try {
findField(obj.getClass(), fieldName).setByte(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setCharField(Object obj, String fieldName, char value) {
try {
findField(obj.getClass(), fieldName).setChar(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setDoubleField(Object obj, String fieldName, double value) {
try {
findField(obj.getClass(), fieldName).setDouble(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setFloatField(Object obj, String fieldName, float value) {
try {
findField(obj.getClass(), fieldName).setFloat(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setIntField(Object obj, String fieldName, int value) {
try {
findField(obj.getClass(), fieldName).setInt(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setLongField(Object obj, String fieldName, long value) {
try {
findField(obj.getClass(), fieldName).setLong(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setShortField(Object obj, String fieldName, short value) {
try {
findField(obj.getClass(), fieldName).setShort(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/** Returns the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static Object getObjectField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).get(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** For inner classes, returns the surrounding instance, i.e. the {@code this} reference of the surrounding class. */
public static Object getSurroundingThis(Object obj) {
return getObjectField(obj, "this$0");
}
/** Returns the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean getBooleanField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getBoolean(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static byte getByteField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getByte(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static char getCharField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getChar(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static double getDoubleField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getDouble(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static float getFloatField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getFloat(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static int getIntField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getInt(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static long getLongField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getLong(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static short getShortField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getShort(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/** Sets the value of a static object field in the given class. See also {@link #findField}. */
public static void setStaticObjectField(Class<?> clazz, String fieldName, Object value) {
try {
findField(clazz, fieldName).set(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code boolean} field in the given class. See also {@link #findField}. */
public static void setStaticBooleanField(Class<?> clazz, String fieldName, boolean value) {
try {
findField(clazz, fieldName).setBoolean(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code byte} field in the given class. See also {@link #findField}. */
public static void setStaticByteField(Class<?> clazz, String fieldName, byte value) {
try {
findField(clazz, fieldName).setByte(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code char} field in the given class. See also {@link #findField}. */
public static void setStaticCharField(Class<?> clazz, String fieldName, char value) {
try {
findField(clazz, fieldName).setChar(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code double} field in the given class. See also {@link #findField}. */
public static void setStaticDoubleField(Class<?> clazz, String fieldName, double value) {
try {
findField(clazz, fieldName).setDouble(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code float} field in the given class. See also {@link #findField}. */
public static void setStaticFloatField(Class<?> clazz, String fieldName, float value) {
try {
findField(clazz, fieldName).setFloat(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code int} field in the given class. See also {@link #findField}. */
public static void setStaticIntField(Class<?> clazz, String fieldName, int value) {
try {
findField(clazz, fieldName).setInt(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code long} field in the given class. See also {@link #findField}. */
public static void setStaticLongField(Class<?> clazz, String fieldName, long value) {
try {
findField(clazz, fieldName).setLong(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code short} field in the given class. See also {@link #findField}. */
public static void setStaticShortField(Class<?> clazz, String fieldName, short value) {
try {
findField(clazz, fieldName).setShort(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/** Returns the value of a static object field in the given class. See also {@link #findField}. */
public static Object getStaticObjectField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).get(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a static {@code boolean} field in the given class. See also {@link #findField}. */
public static boolean getStaticBooleanField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getBoolean(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code byte} field in the given class. See also {@link #findField}. */
public static byte getStaticByteField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getByte(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code char} field in the given class. See also {@link #findField}. */
public static char getStaticCharField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getChar(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code double} field in the given class. See also {@link #findField}. */
public static double getStaticDoubleField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getDouble(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code float} field in the given class. See also {@link #findField}. */
public static float getStaticFloatField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getFloat(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code int} field in the given class. See also {@link #findField}. */
public static int getStaticIntField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getInt(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code long} field in the given class. See also {@link #findField}. */
public static long getStaticLongField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getLong(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code short} field in the given class. See also {@link #findField}. */
public static short getStaticShortField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getShort(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/**
* Calls an instance or static method of the given object.
* The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
*
* @param obj The object instance. A class reference is not sufficient!
* @param methodName The method name.
* @param args The arguments for the method call.
* @throws NoSuchMethodError In case no suitable method was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
*/
public static Object callMethod(Object obj, String methodName, Object... args) {
try {
return findMethodBestMatch(obj.getClass(), methodName, args).invoke(obj, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls an instance or static method of the given object.
* See {@link #callMethod(Object, String, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* methods with the same name, especially if you call it with {@code null} parameters.
*/
public static Object callMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args) {
try {
return findMethodBestMatch(obj.getClass(), methodName, parameterTypes, args).invoke(obj, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls a static method of the given class.
* The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
*
* @param clazz The class reference.
* @param methodName The method name.
* @param args The arguments for the method call.
* @throws NoSuchMethodError In case no suitable method was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
*/
public static Object callStaticMethod(Class<?> clazz, String methodName, Object... args) {
try {
return findMethodBestMatch(clazz, methodName, args).invoke(null, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls a static method of the given class.
* See {@link #callStaticMethod(Class, String, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* methods with the same name, especially if you call it with {@code null} parameters.
*/
public static Object callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) {
try {
return findMethodBestMatch(clazz, methodName, parameterTypes, args).invoke(null, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* This class provides a wrapper for an exception thrown by a method invocation.
*
* @see #callMethod(Object, String, Object...)
* @see #callStaticMethod(Class, String, Object...)
* @see #newInstance(Class, Object...)
*/
public static final class InvocationTargetError extends Error {
private static final long serialVersionUID = -1070936889459514628L;
/** @hide */
public InvocationTargetError(Throwable cause) {
super(cause);
}
}
//#################################################################################################
/**
* Creates a new instance of the given class.
* The constructor is resolved using {@link #findConstructorBestMatch(Class, Object...)}.
*
* @param clazz The class reference.
* @param args The arguments for the constructor call.
* @throws NoSuchMethodError In case no suitable constructor was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
* @throws InstantiationError In case the class cannot be instantiated.
*/
public static Object newInstance(Class<?> clazz, Object... args) {
try {
return findConstructorBestMatch(clazz, args).newInstance(args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
} catch (InstantiationException e) {
throw new InstantiationError(e.getMessage());
}
}
/**
* Creates a new instance of the given class.
* See {@link #newInstance(Class, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* constructors with the same name, especially if you call it with {@code null} parameters.
*/
public static Object newInstance(Class<?> clazz, Class<?>[] parameterTypes, Object... args) {
try {
return findConstructorBestMatch(clazz, parameterTypes, args).newInstance(args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
} catch (InstantiationException e) {
throw new InstantiationError(e.getMessage());
}
}
//#################################################################################################
/**
* Attaches any value to an object instance. This simulates adding an instance field.
* The value can be retrieved again with {@link #getAdditionalInstanceField}.
*
* @param obj The object instance for which the value should be stored.
* @param key The key in the value map for this object instance.
* @param value The value to store.
* @return The previously stored value for this instance/key combination, or {@code null} if there was none.
*/
public static Object setAdditionalInstanceField(Object obj, String key, Object value) {
if (obj == null)
throw new NullPointerException("object must not be null");
if (key == null)
throw new NullPointerException("key must not be null");
HashMap<String, Object> objectFields;
synchronized (additionalFields) {
objectFields = additionalFields.get(obj);
if (objectFields == null) {
objectFields = new HashMap<>();
additionalFields.put(obj, objectFields);
}
}
synchronized (objectFields) {
return objectFields.put(key, value);
}
}
/**
* Returns a value which was stored with {@link #setAdditionalInstanceField}.
*
* @param obj The object instance for which the value has been stored.
* @param key The key in the value map for this object instance.
* @return The stored value for this instance/key combination, or {@code null} if there is none.
*/
public static Object getAdditionalInstanceField(Object obj, String key) {
if (obj == null)
throw new NullPointerException("object must not be null");
if (key == null)
throw new NullPointerException("key must not be null");
HashMap<String, Object> objectFields;
synchronized (additionalFields) {
objectFields = additionalFields.get(obj);
if (objectFields == null)
return null;
}
synchronized (objectFields) {
return objectFields.get(key);
}
}
/**
* Removes and returns a value which was stored with {@link #setAdditionalInstanceField}.
*
* @param obj The object instance for which the value has been stored.
* @param key The key in the value map for this object instance.
* @return The previously stored value for this instance/key combination, or {@code null} if there was none.
*/
public static Object removeAdditionalInstanceField(Object obj, String key) {
if (obj == null)
throw new NullPointerException("object must not be null");
if (key == null)
throw new NullPointerException("key must not be null");
HashMap<String, Object> objectFields;
synchronized (additionalFields) {
objectFields = additionalFields.get(obj);
if (objectFields == null)
return null;
}
synchronized (objectFields) {
return objectFields.remove(key);
}
}
/** Like {@link #setAdditionalInstanceField}, but the value is stored for the class of {@code obj}. */
public static Object setAdditionalStaticField(Object obj, String key, Object value) {
return setAdditionalInstanceField(obj.getClass(), key, value);
}
/** Like {@link #getAdditionalInstanceField}, but the value is returned for the class of {@code obj}. */
public static Object getAdditionalStaticField(Object obj, String key) {
return getAdditionalInstanceField(obj.getClass(), key);
}
/** Like {@link #removeAdditionalInstanceField}, but the value is removed and returned for the class of {@code obj}. */
public static Object removeAdditionalStaticField(Object obj, String key) {
return removeAdditionalInstanceField(obj.getClass(), key);
}
/** Like {@link #setAdditionalInstanceField}, but the value is stored for {@code clazz}. */
public static Object setAdditionalStaticField(Class<?> clazz, String key, Object value) {
return setAdditionalInstanceField(clazz, key, value);
}
/** Like {@link #setAdditionalInstanceField}, but the value is returned for {@code clazz}. */
public static Object getAdditionalStaticField(Class<?> clazz, String key) {
return getAdditionalInstanceField(clazz, key);
}
/** Like {@link #setAdditionalInstanceField}, but the value is removed and returned for {@code clazz}. */
public static Object removeAdditionalStaticField(Class<?> clazz, String key) {
return removeAdditionalInstanceField(clazz, key);
}
//#################################################################################################
/**
* Loads an asset from a resource object and returns the content as {@code byte} array.
*
* @param res The resources from which the asset should be loaded.
* @param path The path to the asset, as in {@link AssetManager#open}.
* @return The content of the asset.
*/
public static byte[] assetAsByteArray(Resources res, String path) throws IOException {
return inputStreamToByteArray(res.getAssets().open(path));
}
/*package*/ static byte[] inputStreamToByteArray(InputStream is) throws IOException {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
byte[] temp = new byte[1024];
int read;
while ((read = is.read(temp)) > 0) {
buf.write(temp, 0, read);
}
is.close();
return buf.toByteArray();
}
/**
* Invokes the {@link Closeable#close()} method, ignoring IOExceptions.
*/
/*package*/ static void closeSilently(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ignored) {}
}
}
/**
* Invokes the {@link DexFile#close()} method, ignoring IOExceptions.
*/
/*package*/ static void closeSilently(DexFile dexFile) {
if (dexFile != null) {
try {
dexFile.close();
} catch (IOException ignored) {}
}
}
/**
* Invokes the {@link ZipFile#close()} method, ignoring IOExceptions.
*/
/*package*/ static void closeSilently(ZipFile zipFile) {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException ignored) {}
}
}
/**
* Returns the lowercase hex string representation of a file's MD5 hash sum.
*/
public static String getMD5Sum(String file) throws IOException {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
InputStream is = new FileInputStream(file);
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
is.close();
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
return bigInt.toString(16);
} catch (NoSuchAlgorithmException e) {
return "";
}
}
//#################################################################################################
/**
* Increments the depth counter for the given method.
*
* <p>The intention of the method depth counter is to keep track of the call depth for recursive
* methods, e.g. to override parameters only for the outer call. The Xposed framework uses this
* to load drawable replacements only once per call, even when multiple
* {@link Resources#getDrawable} variants call each other.
*
* @param method The method name. Should be prefixed with a unique, module-specific string.
* @return The updated depth.
*/
public static int incrementMethodDepth(String method) {
return getMethodDepthCounter(method).get().incrementAndGet();
}
/**
* Decrements the depth counter for the given method.
* See {@link #incrementMethodDepth} for details.
*
* @param method The method name. Should be prefixed with a unique, module-specific string.
* @return The updated depth.
*/
public static int decrementMethodDepth(String method) {
return getMethodDepthCounter(method).get().decrementAndGet();
}
/**
* Returns the current depth counter for the given method.
* See {@link #incrementMethodDepth} for details.
*
* @param method The method name. Should be prefixed with a unique, module-specific string.
* @return The updated depth.
*/
public static int getMethodDepth(String method) {
return getMethodDepthCounter(method).get().get();
}
private static ThreadLocal<AtomicInteger> getMethodDepthCounter(String method) {
synchronized (sMethodDepth) {
ThreadLocal<AtomicInteger> counter = sMethodDepth.get(method);
if (counter == null) {
counter = new ThreadLocal<AtomicInteger>() {
@Override
protected AtomicInteger initialValue() {
return new AtomicInteger();
}
};
sMethodDepth.put(method, counter);
}
return counter;
}
}
/*package*/ static boolean fileContains(File file, String str) throws IOException {
// There are certainly more efficient algorithms (e.g. Boyer-Moore used in grep),
// but the naive approach should be sufficient here.
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
String line;
while ((line = in.readLine()) != null) {
if (line.contains(str)) {
return true;
}
}
return false;
} finally {
closeSilently(in);
}
}
//#################################################################################################
/**
* Returns the method that is overridden by the given method.
* It returns {@code null} if the method doesn't override another method or if that method is
* abstract, i.e. if this is the first implementation in the hierarchy.
*/
/*package*/ static Method getOverriddenMethod(Method method) {
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers)) {
return null;
}
String name = method.getName();
Class<?>[] parameters = method.getParameterTypes();
Class<?> clazz = method.getDeclaringClass().getSuperclass();
while (clazz != null) {
try {
Method superMethod = clazz.getDeclaredMethod(name, parameters);
modifiers = superMethod.getModifiers();
if (!Modifier.isPrivate(modifiers) && !Modifier.isAbstract(modifiers)) {
return superMethod;
} else {
return null;
}
} catch (NoSuchMethodException ignored) {
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* Returns all methods which this class overrides.
*/
/*package*/ static Set<Method> getOverriddenMethods(Class<?> clazz) {
Set<Method> methods = new HashSet<>();
for (Method method : clazz.getDeclaredMethods()) {
Method overridden = getOverriddenMethod(method);
if (overridden != null) {
methods.add(overridden);
}
}
return methods;
}
//#################################################################################################
// TODO helpers for view traversing
/*To make it easier, I will try and implement some more helpers:
- add view before/after existing view (I already mentioned that I think)
- get index of view in its parent
- get next/previous sibling (maybe with an optional argument "type", that might be ImageView.class and gives you the next sibling that is an ImageView)?
- get next/previous element (similar to the above, but would also work if the next element has a different parent, it would just go up the hierarchy and then down again until it finds a matching element)
- find the first child that is an instance of a specified class
- find all (direct or indirect) children of a specified class
*/
}
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 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;
}
/** @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.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 Object[] callbacks;
public 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;
/**
* Contains the main classes of the Xposed framework.
*/
package de.robv.android.xposed;
package de.robv.android.xposed.services;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* General definition of a file access service provided by the Xposed framework.
*
* <p>References to a concrete subclass should generally be retrieved from {@link SELinuxHelper}.
*/
public abstract class BaseService {
/** Flag for {@link #checkFileAccess}: Read access. */
public static final int R_OK = 4;
/** Flag for {@link #checkFileAccess}: Write access. */
public static final int W_OK = 2;
/** Flag for {@link #checkFileAccess}: Executable access. */
public static final int X_OK = 1;
/** Flag for {@link #checkFileAccess}: File/directory exists. */
public static final int F_OK = 0;
/**
* Checks whether the services accesses files directly (instead of using IPC).
*
* @return {@code true} in case direct access is possible.
*/
public boolean hasDirectFileAccess() {
return false;
}
/**
* Check whether a file is accessible. SELinux might enforce stricter checks.
*
* @param filename The absolute path of the file to check.
* @param mode The mode for POSIX's {@code access()} function.
* @return The result of the {@code access()} function.
*/
public abstract boolean checkFileAccess(String filename, int mode);
/**
* Check whether a file exists.
*
* @param filename The absolute path of the file to check.
* @return The result of the {@code access()} function.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean checkFileExists(String filename) {
return checkFileAccess(filename, F_OK);
}
/**
* Determine the size and modification time of a file.
*
* @param filename The absolute path of the file to check.
* @return A {@link FileResult} object holding the result.
* @throws IOException In case an error occurred while retrieving the information.
*/
public abstract FileResult statFile(String filename) throws IOException;
/**
* Determine the size time of a file.
*
* @param filename The absolute path of the file to check.
* @return The file size.
* @throws IOException In case an error occurred while retrieving the information.
*/
public long getFileSize(String filename) throws IOException {
return statFile(filename).size;
}
/**
* Determine the size time of a file.
*
* @param filename The absolute path of the file to check.
* @return The file modification time.
* @throws IOException In case an error occurred while retrieving the information.
*/
public long getFileModificationTime(String filename) throws IOException {
return statFile(filename).mtime;
}
/**
* Read a file into memory.
*
* @param filename The absolute path of the file to read.
* @return A {@code byte} array with the file content.
* @throws IOException In case an error occurred while reading the file.
*/
public abstract byte[] readFile(String filename) throws IOException;
/**
* Read a file into memory, but only if it has changed since the last time.
*
* @param filename The absolute path of the file to read.
* @param previousSize File size of last read.
* @param previousTime File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#content} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public abstract FileResult readFile(String filename, long previousSize, long previousTime) throws IOException;
/**
* Read a file into memory, optionally only if it has changed since the last time.
*
* @param filename The absolute path of the file to read.
* @param offset Number of bytes to skip at the beginning of the file.
* @param length Number of bytes to read (0 means read to end of file).
* @param previousSize Optional: File size of last read.
* @param previousTime Optional: File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#content} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public abstract FileResult readFile(String filename, int offset, int length,
long previousSize, long previousTime) throws IOException;
/**
* Get a stream to the file content.
* Depending on the service, it may or may not be read completely into memory.
*
* @param filename The absolute path of the file to read.
* @return An {@link InputStream} to the file content.
* @throws IOException In case an error occurred while reading the file.
*/
public InputStream getFileInputStream(String filename) throws IOException {
return new ByteArrayInputStream(readFile(filename));
}
/**
* Get a stream to the file content, but only if it has changed since the last time.
* Depending on the service, it may or may not be read completely into memory.
*
* @param filename The absolute path of the file to read.
* @param previousSize Optional: File size of last read.
* @param previousTime Optional: File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#stream} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
FileResult result = readFile(filename, previousSize, previousTime);
if (result.content == null)
return result;
return new FileResult(new ByteArrayInputStream(result.content), result.size, result.mtime);
}
// ----------------------------------------------------------------------------
/*package*/ BaseService() {}
/*package*/ static void ensureAbsolutePath(String filename) {
if (!filename.startsWith("/")) {
throw new IllegalArgumentException("Only absolute filenames are allowed: " + filename);
}
}
/*package*/ static void throwCommonIOException(int errno, String errorMsg, String filename, String defaultText) throws IOException {
switch (errno) {
case 1: // EPERM
case 13: // EACCES
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Permission denied: " + filename);
case 2: // ENOENT
throw new FileNotFoundException(errorMsg != null ? errorMsg : "No such file or directory: " + filename);
case 12: // ENOMEM
throw new OutOfMemoryError(errorMsg);
case 21: // EISDIR
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Is a directory: " + filename);
default:
throw new IOException(errorMsg != null ? errorMsg : "Error " + errno + defaultText + filename);
}
}
}
package 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);
}
}
package de.robv.android.xposed.services;
import java.io.InputStream;
/**
* Holder for the result of a {@link BaseService#readFile} or {@link BaseService#statFile} call.
*/
public final class FileResult {
/** File content, might be {@code null} if the file wasn't read. */
public final byte[] content;
/** File input stream, might be {@code null} if the file wasn't read. */
public final InputStream stream;
/** File size. */
public final long size;
/** File last modification time. */
public final long mtime;
/*package*/ FileResult(long size, long mtime) {
this.content = null;
this.stream = null;
this.size = size;
this.mtime = mtime;
}
/*package*/ FileResult(byte[] content, long size, long mtime) {
this.content = content;
this.stream = null;
this.size = size;
this.mtime = mtime;
}
/*package*/ FileResult(InputStream stream, long size, long mtime) {
this.content = null;
this.stream = stream;
this.size = size;
this.mtime = mtime;
}
/** @hide */
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
if (content != null) {
sb.append("content.length: ");
sb.append(content.length);
sb.append(", ");
}
if (stream != null) {
sb.append("stream: ");
sb.append(stream.toString());
sb.append(", ");
}
sb.append("size: ");
sb.append(size);
sb.append(", mtime: ");
sb.append(mtime);
sb.append("}");
return sb.toString();
}
}
package de.robv.android.xposed.services;
import java.io.IOException;
import java.util.Arrays;
/** @hide */
@SuppressWarnings("JniMissingFunction")
public final class ZygoteService extends BaseService {
@Override
public native boolean checkFileAccess(String filename, int mode);
@Override
public native FileResult statFile(String filename) throws IOException;
@Override
public native byte[] readFile(String filename) throws IOException;
@Override
// Just for completeness, we don't expect this to be called often in Zygote.
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
FileResult stat = statFile(filename);
if (previousSize == stat.size && previousTime == stat.mtime)
return stat;
return new FileResult(readFile(filename), stat.size, stat.mtime);
}
@Override
// Just for completeness, we don't expect this to be called often in Zygote.
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
FileResult stat = statFile(filename);
if (previousSize == stat.size && previousTime == stat.mtime)
return stat;
// Shortcut for the simple case
if (offset <= 0 && length <= 0)
return new FileResult(readFile(filename), stat.size, stat.mtime);
// Check range
if (offset > 0 && offset >= stat.size) {
throw new IllegalArgumentException("offset " + offset + " >= size " + stat.size + " for " + filename);
} else if (offset < 0) {
offset = 0;
}
if (length > 0 && (offset + length) > stat.size) {
throw new IllegalArgumentException("offset " + offset + " + length " + length + " > size " + stat.size + " for " + filename);
} else if (length <= 0) {
length = (int) (stat.size - offset);
}
byte[] content = readFile(filename);
return new FileResult(Arrays.copyOfRange(content, offset, offset + length), stat.size, stat.mtime);
}
}
/**
* Contains file access services provided by the Xposed framework.
*/
package de.robv.android.xposed.services;
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
<resources>
<string name="app_name">XposedBridge</string>
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</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