();
3 | public void init();
4 | }
5 |
6 | -keep class com.wind.xpatch.proxy.**{*;}
7 |
8 | -keep class de.robv.android.xposed.**{*;}
9 |
10 | -keep class android.app.**{*;}
11 | -keep class android.content.**{*;}
12 | -keep class android.os.**{*;}
13 |
14 | -keep class android.view.**{*;}
15 | -keep class com.lody.whale.**{*;}
16 | -keep class com.android.internal.**{*;}
17 | -keep class xposed.dummy.**{*;}
18 | -keep class com.wind.xposed.entry.util.**{*;}
19 |
20 | -keep class com.swift.sandhook.**{*;}
21 | -keep class com.swift.sandhook.xposedcompat.**{*;}
22 |
23 | -dontwarn android.content.res.Resources
24 | -dontwarn android.content.res.Resources$Theme
25 | -dontwarn android.content.res.AssetManager
26 | -dontwarn android.content.res.TypedArray
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 | version_name=3.0
16 | version_code=3
17 | android.disableResourceValidation=true
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/core/src/main/java/android/content/res/XResForwarder.java:
--------------------------------------------------------------------------------
1 | package android.content.res;
2 |
3 | /**
4 | * Instances of this class can be used for {@link XResources#setReplacement(String, String, String, Object)}
5 | * and its variants. They forward the resource request to a different {@link Resources}
6 | * instance with a possibly different ID.
7 | *
8 | * Usually, instances aren't created directly but via {@link XModuleResources#fwd}.
9 | */
10 | public class XResForwarder {
11 | private final Resources res;
12 | private final int id;
13 |
14 | /**
15 | * Creates a new instance.
16 | *
17 | * @param res The target {@link Resources} instance to forward requests to.
18 | * @param id The target resource ID.
19 | */
20 | public XResForwarder(Resources res, int id) {
21 | this.res = res;
22 | this.id = id;
23 | }
24 |
25 | /** Returns the target {@link Resources} instance. */
26 | public Resources getResources() {
27 | return res;
28 | }
29 |
30 | /** Returns the target resource ID. */
31 | public int getId() {
32 | return id;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/XLog.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import com.wind.xposed.entry.BuildConfig;
4 |
5 | public class XLog {
6 |
7 | private static boolean enableLog = true;
8 |
9 | public static void d(String tag, String msg) {
10 | if (enableLog) {
11 | android.util.Log.d(tag, msg);
12 | }
13 | }
14 |
15 | public static void v(String tag, String msg) {
16 | if (enableLog) {
17 | android.util.Log.v(tag, msg);
18 | }
19 | }
20 |
21 | public static void w(String tag, String msg) {
22 | if (enableLog) {
23 | android.util.Log.w(tag, msg);
24 | }
25 | }
26 |
27 | public static void i(String tag, String msg) {
28 | if (enableLog) {
29 | android.util.Log.i(tag, msg);
30 | }
31 | }
32 |
33 | public static void e(String tag, String msg) {
34 | if (enableLog) {
35 | android.util.Log.e(tag, msg);
36 | }
37 | }
38 |
39 | public static void e(String tag, String msg, Throwable tr) {
40 | if (enableLog) {
41 | android.util.Log.e(tag, msg, tr);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/SharedPrefUtils.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | public class SharedPrefUtils {
7 |
8 | private static Context appContext;
9 |
10 | private static final String SHARED_PREFERENE_FILE_PATH = "xpatch_wl_shared_pref";
11 |
12 | public static void init(Context context) {
13 | appContext = context;
14 | }
15 |
16 | public static long getLong() {
17 | if (appContext == null) {
18 | return 0L;
19 | }
20 | SharedPreferences sharedPreferences = appContext.getSharedPreferences(SHARED_PREFERENE_FILE_PATH, Context.MODE_PRIVATE);
21 | long result = sharedPreferences.getLong("time", 0L);
22 | return result;
23 | }
24 |
25 | public static void putLong(long data) {
26 | if (appContext == null) {
27 | return;
28 | }
29 | SharedPreferences sharedPreferences = appContext.getSharedPreferences(SHARED_PREFERENE_FILE_PATH, Context.MODE_PRIVATE);
30 | SharedPreferences.Editor editor = sharedPreferences.edit();
31 | editor.putLong("time", data);
32 | editor.apply();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/SandHookInitialization.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | import com.swift.sandhook.SandHook;
7 | import com.swift.sandhook.SandHookConfig;
8 | import com.swift.sandhook.xposedcompat.XposedCompat;
9 | import com.wind.xposed.entry.util.XpatchUtils;
10 |
11 | /**
12 | * @author Windysha
13 | */
14 | public class SandHookInitialization {
15 |
16 | public static void init(Context context) {
17 | Log.d("SandHookInitialization", "start init");
18 | if (context == null) {
19 | Log.e("SandHookInitialization", "try to init SandHook, but app context is null !!!!");
20 | return;
21 | }
22 |
23 | sandHookCompat(context);
24 |
25 | SandHookConfig.DEBUG = XpatchUtils.isApkDebugable(context);
26 | XposedCompat.cacheDir = context.getCacheDir();
27 | XposedCompat.context = context;
28 | XposedCompat.classLoader = context.getClassLoader();
29 | XposedCompat.isFirstApplication = true;
30 | }
31 |
32 | private static void sandHookCompat(Context context) {
33 | SandHook.disableVMInline();
34 | SandHook.tryDisableProfile(context.getPackageName());
35 | SandHook.disableDex2oatInline(false);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/storm/wind/xposed/MainTestActivity.java:
--------------------------------------------------------------------------------
1 | package com.storm.wind.xposed;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 | import android.os.Bundle;
8 | //import android.support.v4.app.ActivityCompat;
9 | import android.view.View;
10 |
11 | public class MainTestActivity extends Activity {
12 |
13 | //读写权限
14 | private static String[] PERMISSIONS_STORAGE = {
15 | Manifest.permission.READ_EXTERNAL_STORAGE,
16 | Manifest.permission.WRITE_EXTERNAL_STORAGE};
17 | private static final int REQUEST_PERMISSION_CODE = 1;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_main);
23 |
24 | // 需要文件读写权限才能读取sd卡里,用于管理xposed module的文件:xposed_config/modules.list
25 | // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
26 | // if (ActivityCompat.checkSelfPermission(this,
27 | // Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
28 | // ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE);
29 | // }
30 | // }
31 | }
32 |
33 | public void onClick(View view) {
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 | compileSdkVersion 28
7 | defaultConfig {
8 | applicationId "com.storm.wind.xposed"
9 | minSdkVersion 21
10 | targetSdkVersion 30
11 | versionCode version_code as Integer
12 | versionName version_name
13 |
14 | multiDexEnabled false
15 |
16 | ndk {
17 | // abiFilters 'armeabi-v7a', 'arm64-v8a'
18 | abiFilters 'armeabi-v7a'
19 | }
20 | }
21 | buildTypes {
22 | debug {
23 | debuggable true
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | release {
28 | debuggable false
29 | minifyEnabled false
30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
31 | }
32 | }
33 |
34 | // applicationVariants.all { variant ->
35 | // variant.outputs.all {
36 | // outputFileName = "${variant.getFlavorName()}-${variant.versionName}.apk"
37 | // }
38 | // }
39 | }
40 |
41 | dependencies {
42 | implementation fileTree(dir: 'libs', include: ['*.jar'])
43 | // implementation project(':core')
44 | implementation("io.github.windysha:xposed_module_loader:1.0.4")
45 | }
46 |
--------------------------------------------------------------------------------
/core/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.kezong.fat-aar' // https://github.com/kezong/fat-aar-android
3 |
4 | apply from: '../publish.gradle'
5 |
6 | android {
7 | compileSdkVersion 31
8 | buildToolsVersion "26.0.3"
9 |
10 | defaultConfig {
11 | minSdkVersion 19
12 | targetSdkVersion 31
13 |
14 |
15 | ndk {
16 | abiFilters "armeabi-v7a", "arm64-v8a"
17 | }
18 | }
19 | buildTypes {
20 | debug {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | }
30 |
31 | fataar {
32 | /**
33 | * If transitive is true, local jar module and remote library's dependencies will be embed.
34 | * If transitive is false, just embed first level dependency
35 | * Local aar project does not support transitive, always embed first level
36 | * Default value is false
37 | * @since 1.3.0
38 | */
39 | transitive = true
40 | }
41 |
42 | dependencies {
43 | implementation fileTree(include: ['*.jar'], dir: 'libs')
44 | embed(name: 'hooklib', ext: 'aar')
45 | embed(name: 'xposedcompat', ext: 'aar')
46 | // embed 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
47 | }
--------------------------------------------------------------------------------
/core/src/main/java/de/robv/android/xposed/XposedHelper.java:
--------------------------------------------------------------------------------
1 | package de.robv.android.xposed;
2 |
3 | import android.util.Log;
4 |
5 | import java.lang.reflect.Member;
6 |
7 | public class XposedHelper {
8 |
9 | private static final String TAG = "XposedHelper";
10 |
11 | public static void initSeLinux(String processName) {
12 | // SELinuxHelper.initOnce();
13 | // SELinuxHelper.initForProcess(processName);
14 | }
15 |
16 | public static boolean isIXposedMod(Class> moduleClass) {
17 | // Log.d(TAG, "module's classLoader : " + moduleClass.getClassLoader() + ", super: " + moduleClass.getSuperclass());
18 | // Log.d(TAG, "IXposedMod's classLoader : " + IXposedMod.class.getClassLoader());
19 | return IXposedMod.class.isAssignableFrom(moduleClass);
20 | }
21 |
22 |
23 | public static XC_MethodHook.Unhook newUnHook(XC_MethodHook methodHook, Member member) {
24 | return methodHook.new Unhook(member);
25 | }
26 |
27 | public static void callInitZygote(String modulePath, Object moduleInstance) throws Throwable {
28 | IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
29 | param.modulePath = modulePath;
30 | param.startsSystemServer = false;
31 | ((IXposedHookZygoteInit) moduleInstance).initZygote(param);
32 | }
33 |
34 | public static void beforeHookedMethod(XC_MethodHook methodHook, XC_MethodHook.MethodHookParam param) throws Throwable{
35 | methodHook.beforeHookedMethod(param);
36 | }
37 |
38 | public static void afterHookedMethod(XC_MethodHook methodHook, XC_MethodHook.MethodHookParam param) throws Throwable{
39 | methodHook.afterHookedMethod(param);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/PackageNameCache.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageInfo;
5 | import android.content.pm.PackageManager;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | /**
11 | * Created by Wind
12 | */
13 | public class PackageNameCache {
14 |
15 | private static final String TAG = PackageNameCache.class.getSimpleName();
16 |
17 | private Context mContext;
18 | private Map mPackageNameMap = new HashMap<>();
19 |
20 | private static PackageNameCache instance;
21 |
22 | private PackageNameCache(Context context) {
23 | this.mContext = context;
24 | }
25 |
26 | public static PackageNameCache getInstance(Context context) {
27 | if (instance == null) {
28 | synchronized (PackageNameCache.class) {
29 | if (instance == null) {
30 | instance = new PackageNameCache(context);
31 | }
32 | }
33 | }
34 | return instance;
35 | }
36 |
37 | public String getPackageNameByPath(String apkPath) {
38 | if (apkPath == null || apkPath.length() == 0) {
39 | return "";
40 | }
41 | String packageName = mPackageNameMap.get(apkPath);
42 | if (packageName != null && packageName.length() > 0) {
43 | return packageName;
44 | }
45 | packageName = "";
46 | PackageManager pm = mContext.getPackageManager();
47 | long startTime = System.currentTimeMillis();
48 | PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
49 | XLog.d(TAG, "Get package name time -> " + (System.currentTimeMillis() - startTime)
50 | + " apkPath -> " + apkPath);
51 | if (info != null) {
52 | packageName = info.packageName;
53 | mPackageNameMap.put(apkPath, packageName);
54 | }
55 | return packageName;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/storm/wind/xposed/XposedTestApplication.java:
--------------------------------------------------------------------------------
1 | package com.storm.wind.xposed;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.content.Context;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 |
9 | import com.wind.xposed.entry.XposedModuleEntry;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | import de.robv.android.xposed.XC_MethodHook;
15 | import de.robv.android.xposed.XposedHelpers;
16 |
17 | public class XposedTestApplication extends Application {
18 |
19 | static {
20 | List list = new ArrayList() {
21 | {
22 | add("/mnt/sdcard/app-debug.apk");
23 | add("/data/data/com.storm.wind.xposed/files/app-debug.apk");
24 | }
25 | };
26 | XposedModuleEntry.init(null, list);
27 | // XposedModuleEntry.init(null, "/data/data/com.storm.wind.xposed/");
28 | }
29 |
30 | @Override
31 | protected void attachBaseContext(Context base) {
32 | // List list = new ArrayList() {
33 | // {
34 | // add("/mnt/sdcard/app-debug.apk");
35 | // add("/data/data/com.storm.wind.xposed/files/app-debug.apk");
36 | // }
37 | // };
38 | //// XposedModuleEntry.init(base, list);
39 | // XposedModuleEntry.init(base, null, false);
40 |
41 | super.attachBaseContext(base);
42 | }
43 |
44 | @Override
45 | public void onCreate() {
46 | super.onCreate();
47 | hookOnCreate();
48 | }
49 |
50 | private void hookOnCreate() {
51 | XposedHelpers.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() {
52 | @Override
53 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
54 | super.beforeHookedMethod(param);
55 | Log.e("xiawanli", " beforeHookedMethod onCreate");
56 | }
57 |
58 | @Override
59 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
60 | super.afterHookedMethod(param);
61 | Log.e("xiawanli", " beforeHookedMethod onCreate");
62 | }
63 | });
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import android.Manifest;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 | import android.os.Process;
7 |
8 | import java.io.BufferedReader;
9 | import java.io.Closeable;
10 | import java.io.InputStream;
11 | import java.io.InputStreamReader;
12 |
13 | public class FileUtils {
14 |
15 | //读写权限
16 | private static String[] PERMISSIONS_STORAGE = {
17 | Manifest.permission.READ_EXTERNAL_STORAGE,
18 | Manifest.permission.WRITE_EXTERNAL_STORAGE};
19 |
20 | public static boolean isFilePermissionGranted(Context context) {
21 | int pid = android.os.Process.myPid();
22 | int uid = Process.myUid();
23 | return context.checkPermission(PERMISSIONS_STORAGE[0], pid, uid) == PackageManager.PERMISSION_GRANTED &&
24 | context.checkPermission(PERMISSIONS_STORAGE[1], pid, uid) == PackageManager.PERMISSION_GRANTED;
25 | }
26 |
27 | public static String readTextFromAssets(Context context, String assetsFileName) {
28 | if (context == null) {
29 | return null;
30 | }
31 | try {
32 | InputStream is = context.getAssets().open(assetsFileName);
33 | return readTextFromInputStream(is);
34 | } catch (Exception e) {
35 | e.printStackTrace();
36 | }
37 | return null;
38 | }
39 |
40 | public static String readTextFromInputStream(InputStream is) {
41 | InputStreamReader reader = null;
42 | BufferedReader bufferedReader = null;
43 | try {
44 | reader = new InputStreamReader(is, "UTF-8");
45 | bufferedReader = new BufferedReader(reader);
46 | StringBuilder builder = new StringBuilder();
47 | String str;
48 | while ((str = bufferedReader.readLine()) != null) {
49 | builder.append(str);
50 | }
51 | return builder.toString();
52 | } catch (Exception e) {
53 | e.printStackTrace();
54 | } finally {
55 | closeSafely(reader);
56 | closeSafely(bufferedReader);
57 | }
58 | return null;
59 | }
60 |
61 | private static void closeSafely(Closeable closeable) {
62 | try {
63 | if (closeable != null) {
64 | closeable.close();
65 | }
66 | } catch (Exception e) {
67 | e.printStackTrace();
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/PluginNativeLibExtractor.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import static android.content.Context.MODE_PRIVATE;
4 |
5 | import android.content.Context;
6 | import android.content.SharedPreferences;
7 | import android.util.Log;
8 |
9 | import java.io.File;
10 | import java.util.Set;
11 |
12 | public class PluginNativeLibExtractor {
13 |
14 | private static final String TAG = "NativeLibExtractor";
15 | private static final String SHARE_PREF_FILE_NAME = "xpatch_module_native_lib_config";
16 |
17 | private static SharedPreferences sharedPreferences;
18 |
19 | public static void copySoFileIfNeeded(Context context, String libPath, String pluginApkPath) {
20 | boolean isMainProcess = XpatchUtils.isMainProcess(context);
21 | if (!isMainProcess) {
22 | return;
23 | }
24 |
25 | Set abiSet = NativeLibraryHelperCompat.getSupportAbiList(pluginApkPath);
26 | if (abiSet.isEmpty()) {
27 | Log.i(TAG, " plugin: " + pluginApkPath + " do not contains any so files.");
28 | return;
29 | }
30 |
31 | XpatchUtils.ensurePathExist(libPath);
32 |
33 | Log.i(TAG, " copySoFileIfNeeded procecess = " + XpatchUtils.getCurProcessName(context) + " isMainProcess = " + XpatchUtils.isMainProcess(context));
34 | Log.i(TAG, " copyPluginSoFile libPath = " + libPath + " pluginApkPath = " + pluginApkPath);
35 | if (sharedPreferences == null) {
36 | sharedPreferences = context.getSharedPreferences(SHARE_PREF_FILE_NAME, MODE_PRIVATE);
37 | }
38 | String savedMd5 = getSavedApkFileMd5(sharedPreferences, pluginApkPath);
39 | String curMd5 = XpatchUtils.getFileMD5(new File(pluginApkPath));
40 | Log.i(TAG, " copyPluginSoFile savedMd5 = " + savedMd5 + " curMd5 = " + curMd5);
41 | if (savedMd5 == null || savedMd5.isEmpty() || !savedMd5.equals(curMd5)) {
42 | NativeLibraryHelperCompat.copyNativeBinaries(new File(pluginApkPath), new File(libPath));
43 | saveApkFileMd5(sharedPreferences, pluginApkPath, curMd5);
44 | } else {
45 | Log.d(TAG, "plugin is not changed, no need to copy so file again!");
46 | }
47 | }
48 |
49 | private static String getSavedApkFileMd5(SharedPreferences sp, String key) {
50 | return sp.getString(key, "");
51 | }
52 |
53 | private static void saveApkFileMd5(SharedPreferences sp, String key, String md5) {
54 | sp.edit().putString(key, md5).apply();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 | 
5 |
6 | # Introduction
7 | This is a library used to load xposed module files.
8 |
9 | # Features
10 |
11 | * Support loading xposed modules by the apk file path;
12 | * Support loading all the xposed modules installed in the device;
13 | * Support loading all the native libraries in the xposed modules;
14 | * Support importing xposed styled java hooking framework to android projects;
15 |
16 |
17 | # Usage
18 | ## 1. Add dependency to build.gradle file
19 |
20 | This tool is published on [Maven Central](https://search.maven.org/).
21 |
22 | ```Gradle
23 | allprojects {
24 | repositories {
25 | mavenCentral()
26 | }
27 | }
28 | ```
29 |
30 | ```Gradle
31 | android {
32 | defaultConfig {
33 | ndk {
34 | abiFilters 'armeabi-v7a', 'arm64-v8a'
35 | }
36 | }
37 | }
38 |
39 | dependencies {
40 | implementation 'io.github.windysha:xposed_module_loader:1.0.4'
41 | }
42 | ```
43 |
44 | ## 2. Add init code to the Application file.
45 | * Load xposed modules by file paths:
46 | ```
47 | @Override
48 | protected void attachBaseContext(Context base) {
49 | List list = new ArrayList() {
50 | {
51 | add("/mnt/sdcard/xposed_module.apk"); // app need to hava permission read files in the sdcard.
52 | add("/data/data/com.storm.wind.xposed/files/xposed_module.apk");
53 | }
54 | };
55 | XposedModuleEntry.init(base, list);
56 | super.attachBaseContext(base);
57 | }
58 | ```
59 | * Load xposed modules by file directory:
60 | ```
61 | @Override
62 | protected void attachBaseContext(Context base) {
63 | // all xposed module files in the dir /data/data/package_name/ will be loaded.
64 | XposedModuleEntry.init(base, "/data/data/package_name/");
65 | super.attachBaseContext(base);
66 | }
67 | ```
68 | * Load all xposed modules installed in the devices:
69 | ```
70 | @Override
71 | protected void attachBaseContext(Context base) {
72 | XposedModuleEntry.init(base);
73 | super.attachBaseContext(base);
74 | }
75 | ```
76 | * Only init java hook framework, do not load any xposed modules:
77 | ```
78 | @Override
79 | protected void attachBaseContext(Context base) {
80 | XposedModuleEntry.init(base, null, false);
81 | super.attachBaseContext(base);
82 | }
83 | ```
84 |
85 | # Applied
86 | Early version of [Xpatch](https://github.com/WindySha/Xpatch) use this library to load xposed modules。
87 |
88 | # Reference
89 | [SandHook](https://github.com/asLody/SandHook)
90 |
91 | # License
92 | ```
93 | Copyright 2021 WindySha
94 |
95 | Licensed under the Apache License, Version 2.0 (the "License");
96 | you may not use this file except in compliance with the License.
97 | You may obtain a copy of the License at
98 |
99 | http://www.apache.org/licenses/LICENSE-2.0
100 |
101 | Unless required by applicable law or agreed to in writing, software
102 | distributed under the License is distributed on an "AS IS" BASIS,
103 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
104 | See the License for the specific language governing permissions and
105 | limitations under the License.
106 | ```
107 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/VMRuntime.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import android.os.Build;
4 |
5 | import java.lang.reflect.Method;
6 |
7 | /**
8 | * Ref: https://zhuanlan.zhihu.com/p/364884283
9 | */
10 | public class VMRuntime {
11 | public static void setHiddenApiExemptions(final String[] signaturePrefixes) {
12 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
13 | try {
14 | final Method method_getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod",
15 | String.class, Class[].class);
16 | final Method method_forName = Class.class.getDeclaredMethod("forName", String.class);
17 | final Class> class_VMRuntime = (Class>) method_forName.invoke(null, "dalvik.system.VMRuntime");
18 | final Method method_getRuntime = (Method) method_getDeclaredMethod.invoke(class_VMRuntime,
19 | "getRuntime", null);
20 | final Object object_VMRuntime = method_getRuntime.invoke(null);
21 |
22 | Method setHiddenApiExemptions = null;
23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
24 | final Class> class_Unsafe = Class.forName("sun.misc.Unsafe");
25 | final Method method_getUnsafe = class_Unsafe.getDeclaredMethod("getUnsafe");
26 | final Object object_Unsafe = method_getUnsafe.invoke(null);
27 |
28 | final Method method_getLong = class_Unsafe.getDeclaredMethod("getLong", long.class);
29 | final Method method_putLong = class_Unsafe.getDeclaredMethod("putLong", long.class, long.class);
30 |
31 | final Method method_getInt = class_Unsafe.getDeclaredMethod("getInt", long.class);
32 | // final Method method_putInt = class_Unsafe.getDeclaredMethod("putInt", long.class, int.class);
33 |
34 | final Method method_addressOf = class_VMRuntime.getDeclaredMethod("addressOf", Object.class);
35 | final Method method_newNonMovableArray = class_VMRuntime.getDeclaredMethod("newNonMovableArray", Class.class, int.class);
36 |
37 | final Method[] declaredMethods = class_VMRuntime.getDeclaredMethods();
38 | final int length = declaredMethods.length;
39 | final Method[] array = (Method[]) method_newNonMovableArray.invoke(object_VMRuntime,
40 | Method.class, length);
41 | System.arraycopy(declaredMethods, 0, array, 0, length);
42 |
43 | // http://aosp.opersys.com/xref/android-11.0.0_r3/xref/art/runtime/mirror/executable.h
44 | // uint64_t Executable::art_method_
45 | final int offset_art_method_ = 24;
46 |
47 | final long address = (long) method_addressOf.invoke(object_VMRuntime, (Object) array);
48 | long min = Long.MAX_VALUE, min_second = Long.MAX_VALUE, max = Long.MIN_VALUE;
49 | for (int k = 0; k < length; ++k) {
50 | final long address_Method = (int) method_getInt.invoke(object_Unsafe, address + k * Integer.BYTES);
51 | final long address_art_method = (long) method_getLong.invoke(object_Unsafe,
52 | address_Method + offset_art_method_);
53 | if (min >= address_art_method) {
54 | min = address_art_method;
55 | } else if (min_second >= address_art_method) {
56 | min_second = address_art_method;
57 | }
58 | if (max <= address_art_method) {
59 | max = address_art_method;
60 | }
61 | }
62 |
63 | final long size_art_method = min_second - min;
64 | if (size_art_method > 0 && size_art_method < 100) {
65 | for (min += size_art_method; min < max; min += size_art_method) {
66 | final long address_Method = (int) method_getInt.invoke(object_Unsafe, address);
67 | method_putLong.invoke(object_Unsafe,
68 | address_Method + offset_art_method_, min);
69 | final String name = array[0].getName();
70 | if ("setHiddenApiExemptions".equals(name)) {
71 | setHiddenApiExemptions = array[0];
72 | break;
73 | }
74 | }
75 | }
76 | } else {
77 | setHiddenApiExemptions = (Method) method_getDeclaredMethod.invoke(class_VMRuntime,
78 | "setHiddenApiExemptions", new Class[]{String[].class});
79 | }
80 |
81 | if (setHiddenApiExemptions != null) {
82 | setHiddenApiExemptions.invoke(object_VMRuntime, (Object) signaturePrefixes);
83 | }
84 | } catch (final Exception ignored) {
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/publish.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | ext {
5 | PUBLISH_GROUP_ID = 'io.github.windysha'
6 | PUBLISH_ARTIFACT_ID = 'xposed_module_loader'
7 | PUBLISH_ARTIFACT_NAME = 'Xposed Module Loader.'
8 | PUBLISH_VERSION_NAME = '1.0.4'
9 |
10 | POM_URL = 'https://github.com/WindySha/xposed_module_loader'
11 | POM_DESCRIPTION = 'This is a library that can load xposed module from any path or installed xposed module.'
12 |
13 | POM_DEVELOPER_ID = 'WindySha'
14 | POM_DEVELOPER_NAME = 'WindySha'
15 | POM_DEVELOPER_EMAIL = '819170366@qq.com'
16 | POM_DEVELOPER_URL = 'https://windysha.github.io/'
17 |
18 | POM_SCM_URL = 'https://github.com/WindySha/xposed_module_loader/tree/master'
19 | POM_SCM_CONNECTION = 'scm:git:https://github.com/WindySha/xposed_module_loader.git'
20 | }
21 |
22 | task androidSourcesJar(type: Jar) {
23 | archiveClassifier.set('sources')
24 | if (project.plugins.findPlugin("com.android.library")) {
25 | // For Android libraries
26 | from android.sourceSets.main.java.srcDirs
27 | from android.sourceSets.main.kotlin.srcDirs
28 | } else {
29 | // For pure Kotlin libraries, in case you have them
30 | from sourceSets.main.java.srcDirs
31 | from sourceSets.main.kotlin.srcDirs
32 | }
33 | }
34 |
35 | ext["signing.keyId"] = ''
36 | ext["signing.password"] = ''
37 | ext["signing.secretKeyRingFile"] = ''
38 | ext["ossrhUsername"] = ''
39 | ext["ossrhPassword"] = ''
40 |
41 | File secretPropsFile = project.rootProject.file('maven_signing.properties')
42 | if (secretPropsFile.exists()) {
43 | println "Found secret props file, loading props"
44 | Properties p = new Properties()
45 | p.load(new FileInputStream(secretPropsFile))
46 | p.each { name, value ->
47 | ext[name] = value
48 | }
49 | } else {
50 | println "No props file, loading env vars"
51 | }
52 | publishing {
53 | publications {
54 | release(MavenPublication) {
55 | // The coordinates of the library, being set from variables that
56 | // we'll set up in a moment
57 | groupId PUBLISH_GROUP_ID
58 | artifactId PUBLISH_ARTIFACT_ID
59 | version PUBLISH_VERSION_NAME
60 |
61 | afterEvaluate {
62 | artifact tasks.getByName("bundleReleaseAar")
63 | artifact androidSourcesJar
64 | }
65 |
66 | // Two artifacts, the `aar` and the sources
67 | // artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
68 | // artifact androidSourcesJar
69 |
70 | // Self-explanatory metadata for the most part
71 | pom {
72 | name = PUBLISH_ARTIFACT_NAME
73 | description = POM_DESCRIPTION
74 | url = POM_URL
75 | licenses {
76 | license {
77 | //协议类型,一般默认Apache License2.0的话不用改:
78 | name = 'The Apache License, Version 2.0'
79 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
80 | }
81 | }
82 | developers {
83 | developer {
84 | id = POM_DEVELOPER_ID
85 | name = POM_DEVELOPER_NAME
86 | email = POM_DEVELOPER_EMAIL
87 | url = POM_DEVELOPER_URL
88 | }
89 | }
90 | // Version control info, if you're using GitHub, follow the format as seen here
91 | scm {
92 | url = POM_SCM_URL
93 | connection = POM_SCM_CONNECTION
94 | developerConnection = POM_SCM_CONNECTION
95 | }
96 | // A slightly hacky fix so that your POM will include any transitive dependencies
97 | // that your library builds upon
98 | // https://github.com/kezong/fat-aar-android/issues/45
99 | withXml {
100 | def dependenciesNode = asNode().appendNode('dependencies')
101 |
102 | //Iterate over the compile dependencies, adding a node for each
103 | project.configurations.implementation.allDependencies.each {
104 | def hasGroup = it.group != null
105 | def hasName = (it.name != null || "unspecified".equals(it.name))
106 | def hasVersion = it.version != null
107 |
108 | if (hasGroup && hasName && hasVersion) {
109 | def dependencyNode = dependenciesNode.appendNode('dependency')
110 | dependencyNode.appendNode('groupId', it.group)
111 | dependencyNode.appendNode('artifactId', it.name)
112 | dependencyNode.appendNode('version', it.version)
113 | }
114 | }
115 | }
116 | }
117 | }
118 | }
119 | repositories {
120 | maven {
121 | name = "mavenCentral"
122 | url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
123 |
124 | // The username and password we've fetched earlier
125 | credentials {
126 | username ossrhUsername
127 | password ossrhPassword
128 | }
129 | }
130 | }
131 | }
132 | signing {
133 | sign publishing.publications
134 | }
135 | // https://s01.oss.sonatype.org/#stagingRepositories
136 | // publish dst: https://repo.maven.apache.org/maven2/io/github/windysha/xposed_module_loader/
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/NativeLibraryHelperCompat.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import android.annotation.TargetApi;
4 | import android.os.Build;
5 | import android.os.Process;
6 | import android.util.Log;
7 |
8 | import java.io.File;
9 | import java.util.Collections;
10 | import java.util.Enumeration;
11 | import java.util.HashSet;
12 | import java.util.Set;
13 | import java.util.zip.ZipEntry;
14 | import java.util.zip.ZipFile;
15 |
16 | public class NativeLibraryHelperCompat {
17 | private static final String TAG = "NativeLibraryHelper";
18 |
19 | public static int copyNativeBinaries(File apkFile, File sharedLibraryDir) {
20 | Log.i(TAG, " copyNativeBinaries !!! apkFile = " + apkFile.getAbsolutePath() + " sharedLibraryDir = " + sharedLibraryDir.getAbsolutePath());
21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
22 | return copyNativeBinariesAfterL(apkFile, sharedLibraryDir);
23 | } else {
24 | return copyNativeBinariesBeforeL(apkFile, sharedLibraryDir);
25 | }
26 | }
27 |
28 | private static int copyNativeBinariesBeforeL(File apkFile, File sharedLibraryDir) {
29 | try {
30 | String className = "com.android.internal.content.NativeLibraryHelper";
31 | Object result = ReflectUtils.callMethod(className,
32 | null, "copyNativeBinariesIfNeededLI",
33 | apkFile, sharedLibraryDir);
34 | if (result != null) {
35 | return (int) result;
36 | }
37 | } catch (Throwable e) {
38 | e.printStackTrace();
39 | }
40 | return -1;
41 | }
42 |
43 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
44 | private static int copyNativeBinariesAfterL(File apkFile, File sharedLibraryDir) {
45 | try {
46 | String handleClassName = "com.android.internal.content.NativeLibraryHelper$Handle";
47 | Object handle = ReflectUtils.callMethod(handleClassName,
48 | null, "create", apkFile);
49 | if (handle == null) {
50 | return -1;
51 | }
52 |
53 | String abi = null;
54 | Set abiSet = getSupportAbiList(apkFile.getAbsolutePath());
55 | if (abiSet.isEmpty()) {
56 | return 0;
57 | }
58 | boolean is64Bit = is64bit();
59 | String className = "com.android.internal.content.NativeLibraryHelper";
60 | if (is64Bit && contain64bitAbi(abiSet)) {
61 | if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
62 | int abiIndex = (int) ReflectUtils.callMethod(className,
63 | null,
64 | "findSupportedAbi",
65 | handle, Build.SUPPORTED_64_BIT_ABIS);
66 | if (abiIndex >= 0) {
67 | abi = Build.SUPPORTED_64_BIT_ABIS[abiIndex];
68 | }
69 | }
70 | } else {
71 | if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
72 | int abiIndex = (int) ReflectUtils.callMethod(className,
73 | null,
74 | "findSupportedAbi",
75 | handle, Build.SUPPORTED_32_BIT_ABIS);
76 | if (abiIndex >= 0) {
77 | abi = Build.SUPPORTED_32_BIT_ABIS[abiIndex];
78 | }
79 | }
80 | }
81 | Log.i(TAG, " is64Bit=" + is64Bit + " abi = " + abi + " abiSet = " + abiSet + " sharedLibraryDir =" + sharedLibraryDir);
82 | if (abi == null) {
83 | Log.e(TAG, "Not match any abi." + apkFile.getAbsolutePath());
84 | return -1;
85 | }
86 | int result = (int) ReflectUtils.callMethod(className,
87 | null,
88 | "copyNativeBinaries",
89 | handle, sharedLibraryDir, abi);
90 | Log.i(TAG, "copyNativeBinaries result = " + result + " apkFile path = " + apkFile.getAbsolutePath());
91 | return result;
92 | } catch (Exception e) {
93 | e.printStackTrace();
94 | }
95 | return -1;
96 | }
97 |
98 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
99 | public static boolean is64bitAbi(String abi) {
100 | return "arm64-v8a".equals(abi)
101 | || "x86_64".equals(abi)
102 | || "mips64".equals(abi);
103 | }
104 |
105 | public static boolean is32bitAbi(String abi) {
106 | return "armeabi".equals(abi)
107 | || "armeabi-v7a".equals(abi)
108 | || "mips".equals(abi)
109 | || "x86".equals(abi);
110 | }
111 |
112 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
113 | public static boolean contain64bitAbi(Set supportedABIs) {
114 | for (String supportedAbi : supportedABIs) {
115 | if (is64bitAbi(supportedAbi)) {
116 | return true;
117 | }
118 | }
119 | return false;
120 | }
121 |
122 | public static Set getSupportAbiList(String apk) {
123 | try {
124 | ZipFile apkFile = new ZipFile(apk);
125 | Enumeration extends ZipEntry> entries = apkFile.entries();
126 | Set supportedABIs = new HashSet();
127 | while (entries.hasMoreElements()) {
128 | ZipEntry entry = entries.nextElement();
129 | String name = entry.getName();
130 | if (name.contains("../")) {
131 | continue;
132 | }
133 | if (name.startsWith("lib/") && !entry.isDirectory() && name.endsWith(".so")) {
134 | String supportedAbi = name.substring(name.indexOf("/") + 1, name.lastIndexOf("/"));
135 | supportedABIs.add(supportedAbi);
136 | }
137 | }
138 | return supportedABIs;
139 | } catch (Exception e) {
140 | e.printStackTrace();
141 | }
142 | return Collections.emptySet();
143 | }
144 |
145 | public static boolean is64bit() {
146 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
147 | return false;
148 | }
149 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
150 | return Process.is64Bit();
151 | }
152 | Object runtime = ReflectUtils.callMethod("dalvik.system.VMRuntime", null, "getRuntime");
153 | Object is64Bit = ReflectUtils.callMethod("dalvik.system.VMRuntime", runtime, "is64Bit");
154 | if (is64Bit == null) return true;
155 | return (boolean) is64Bit;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/XposedModuleLoader.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.pm.ApplicationInfo;
5 | import android.util.Log;
6 |
7 | import com.wind.xposed.entry.util.XLog;
8 |
9 | import java.io.BufferedReader;
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.io.InputStreamReader;
14 | import java.lang.reflect.Method;
15 |
16 | import dalvik.system.DexClassLoader;
17 | import de.robv.android.xposed.IXposedHookInitPackageResources;
18 | import de.robv.android.xposed.IXposedHookLoadPackage;
19 | import de.robv.android.xposed.IXposedHookZygoteInit;
20 | import de.robv.android.xposed.XposedBridge;
21 | import de.robv.android.xposed.XposedHelper;
22 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
23 |
24 | public class XposedModuleLoader {
25 |
26 | private static final String TAG = "XposedModuleLoader";
27 |
28 | public static boolean loadModule(final String moduleApkPath, String moduleOdexDir, String moduleLibPath,
29 | final ApplicationInfo currentApplicationInfo, ClassLoader appClassLoader) {
30 |
31 | XLog.i(TAG, "Loading modules from " + moduleApkPath);
32 |
33 | if (!new File(moduleApkPath).exists()) {
34 | Log.e(TAG, moduleApkPath + " does not exist");
35 | return false;
36 | }
37 |
38 | // use system classloader to load asset to avoid the app has file assets/xposed_init
39 | ClassLoader assetLoader = new DexClassLoader(moduleApkPath, moduleOdexDir, moduleLibPath, ClassLoader.getSystemClassLoader());
40 | InputStream is = assetLoader.getResourceAsStream("assets/xposed_init");
41 | if (is == null) {
42 | Log.i(TAG, "assets/xposed_init not found in the APK");
43 | return false;
44 | }
45 |
46 | ClassLoader mcl = new DexClassLoader(moduleApkPath, moduleOdexDir, moduleLibPath, appClassLoader);
47 | BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
48 | try {
49 | String moduleClassName;
50 | while ((moduleClassName = moduleClassesReader.readLine()) != null) {
51 | moduleClassName = moduleClassName.trim();
52 | if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
53 | continue;
54 |
55 | try {
56 | XLog.i(TAG, " Loading class " + moduleClassName);
57 | Class> moduleClass = mcl.loadClass(moduleClassName);
58 |
59 | if (!XposedHelper.isIXposedMod(moduleClass)) {
60 | Log.i(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it");
61 | continue;
62 | } else if (IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
63 | Log.i(TAG, " This class requires resource-related hooks (which are disabled), skipping it.");
64 | continue;
65 | }
66 |
67 | final Object moduleInstance = moduleClass.newInstance();
68 | if (moduleInstance instanceof IXposedHookZygoteInit) {
69 | XposedHelper.callInitZygote(moduleApkPath, moduleInstance);
70 | }
71 |
72 | if (moduleInstance instanceof IXposedHookLoadPackage) {
73 | // hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
74 | IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance);
75 | XposedBridge.CopyOnWriteSortedSet xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>();
76 | xc_loadPackageCopyOnWriteSortedSet.add(wrapper);
77 | XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet);
78 | lpparam.packageName = currentApplicationInfo.packageName;
79 | lpparam.processName = getCurrentProcessName(currentApplicationInfo);;
80 | lpparam.classLoader = appClassLoader;
81 | lpparam.appInfo = currentApplicationInfo;
82 | lpparam.isFirstApplication = true;
83 | XC_LoadPackage.callAll(lpparam);
84 | }
85 |
86 | if (moduleInstance instanceof IXposedHookInitPackageResources) {
87 | // hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
88 | // TODO: Support Resource hook
89 | }
90 |
91 | } catch (Throwable t) {
92 | Log.e(TAG, " error ", t);
93 | }
94 | }
95 | } catch (IOException e) {
96 | Log.e(TAG, " error ", e);
97 | } finally {
98 | try {
99 | is.close();
100 | } catch (IOException ignored) {
101 | }
102 | }
103 | return true;
104 | }
105 |
106 | public static void startInnerHook(ApplicationInfo applicationInfo, ClassLoader originClassLoader) {
107 | IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper(XposedHookLoadPackageInner.newIntance());
108 |
109 | XposedBridge.CopyOnWriteSortedSet xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>();
110 | xc_loadPackageCopyOnWriteSortedSet.add(wrapper);
111 |
112 | XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet);
113 |
114 | lpparam.packageName = applicationInfo.packageName;
115 | lpparam.processName = getCurrentProcessName(applicationInfo);
116 | lpparam.classLoader = originClassLoader;
117 | lpparam.appInfo = applicationInfo;
118 | lpparam.isFirstApplication = true;
119 |
120 | XC_LoadPackage.callAll(lpparam);
121 | }
122 |
123 | private static String currentProcessName = null;
124 |
125 | @SuppressLint("DiscouragedPrivateApi")
126 | private static String getCurrentProcessName(ApplicationInfo applicationInfo) {
127 | if (currentProcessName != null) return currentProcessName;
128 |
129 | currentProcessName = applicationInfo.packageName;
130 | try {
131 | Class activityThread_clazz = Class.forName("android.app.ActivityThread");
132 | Method method = activityThread_clazz.getDeclaredMethod("currentProcessName");
133 | method.setAccessible(true);
134 | currentProcessName = (String) method.invoke(null);
135 | } catch (Exception e) {
136 | e.printStackTrace();
137 | }
138 | return currentProcessName;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/XpatchUtils.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 | import android.content.pm.ApplicationInfo;
6 | import android.util.Log;
7 |
8 | import java.io.BufferedInputStream;
9 | import java.io.BufferedReader;
10 | import java.io.File;
11 | import java.io.FileInputStream;
12 | import java.io.IOException;
13 | import java.io.InputStreamReader;
14 | import java.lang.reflect.Field;
15 | import java.lang.reflect.InvocationTargetException;
16 | import java.lang.reflect.Method;
17 | import java.math.BigInteger;
18 | import java.security.MessageDigest;
19 | import java.security.NoSuchAlgorithmException;
20 |
21 | public class XpatchUtils {
22 |
23 | private static String sCurProcessName = null;
24 |
25 | public static Context createAppContext() {
26 |
27 | // LoadedApk.makeApplication()
28 | // ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
29 |
30 | try {
31 | Class activityThreadClass = Class.forName("android.app.ActivityThread");
32 | Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
33 | currentActivityThreadMethod.setAccessible(true);
34 |
35 | Object activityThreadObj = currentActivityThreadMethod.invoke(null);
36 |
37 | Field boundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
38 | boundApplicationField.setAccessible(true);
39 | Object mBoundApplication = boundApplicationField.get(activityThreadObj); // AppBindData
40 |
41 | Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info
42 | infoField.setAccessible(true);
43 | Object loadedApkObj = infoField.get(mBoundApplication); // LoadedApk
44 |
45 | Class contextImplClass = Class.forName("android.app.ContextImpl");
46 | Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", activityThreadClass, loadedApkObj.getClass());
47 | createAppContextMethod.setAccessible(true);
48 |
49 | Object context = createAppContextMethod.invoke(null, activityThreadObj, loadedApkObj);
50 |
51 | if (context instanceof Context) {
52 | return (Context) context;
53 | }
54 |
55 | } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
56 | e.printStackTrace();
57 | }
58 | return null;
59 | }
60 |
61 | public static boolean isApkDebugable(Context context) {
62 | try {
63 | ApplicationInfo info = context.getApplicationInfo();
64 | return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
65 | } catch (Exception e) {
66 | }
67 | return false;
68 | }
69 |
70 | public static String getCurProcessName(Context context) {
71 | String procName = sCurProcessName;
72 | if (procName != null && !procName.isEmpty()) {
73 | return procName;
74 | }
75 | try {
76 | int pid = android.os.Process.myPid();
77 | ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
78 | for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) {
79 | if (appProcess.pid == pid) {
80 | Log.d("Process", "processName = " + appProcess.processName);
81 | sCurProcessName = appProcess.processName;
82 | return sCurProcessName;
83 | }
84 | }
85 | } catch (Exception e) {
86 | e.printStackTrace();
87 | }
88 | sCurProcessName = getCurProcessNameFromProc();
89 | return sCurProcessName;
90 | }
91 |
92 | private static String getCurProcessNameFromProc() {
93 | BufferedReader cmdlineReader = null;
94 | try {
95 | cmdlineReader = new BufferedReader(new InputStreamReader(
96 | new FileInputStream(
97 | "/proc/" + android.os.Process.myPid() + "/cmdline"),
98 | "iso-8859-1"));
99 | int c;
100 | StringBuilder processName = new StringBuilder();
101 | while ((c = cmdlineReader.read()) > 0) {
102 | processName.append((char) c);
103 | }
104 | Log.d("Process", "get processName = " + processName.toString());
105 | return processName.toString();
106 | } catch (Throwable e) {
107 | // ignore
108 | } finally {
109 | if (cmdlineReader != null) {
110 | try {
111 | cmdlineReader.close();
112 | } catch (Exception e) {
113 | // ignore
114 | }
115 | }
116 | }
117 | return null;
118 | }
119 |
120 | public static boolean isMainProcess(Context context) {
121 | String processName = getCurProcessName(context);
122 | if (processName != null && processName.contains(":")) {
123 | return false;
124 | }
125 | return (processName != null && processName.equals(context.getPackageName()));
126 | }
127 |
128 | public static String getFileMD5(File file) {
129 | if (!file.isFile()) {
130 | return "";
131 | }
132 | MessageDigest digest = null;
133 | BufferedInputStream in = null;
134 | byte buffer[] = new byte[1024];
135 | int len;
136 | try {
137 | digest = MessageDigest.getInstance("MD5");
138 | in = new BufferedInputStream(new FileInputStream(file));
139 | while ((len = in.read(buffer, 0, buffer.length)) != -1) {
140 | digest.update(buffer, 0, len);
141 | }
142 | String md5Result = byteArrayToHex(digest.digest());
143 | return md5Result;
144 | } catch (Exception e) {
145 | return "";
146 | } finally {
147 | if (in != null) {
148 | try {
149 | in.close();
150 | } catch (IOException ignored) {
151 | }
152 | }
153 | }
154 | }
155 |
156 | public static String byteArrayToHex(byte[] byteArray) {
157 | if (byteArray == null || byteArray.length <= 0) {
158 | return "";
159 | }
160 | // 首先初始化一个字符数组,用来存放每个16进制字符
161 | char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
162 | // new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方))
163 | char[] resultCharArray = new char[byteArray.length * 2];
164 | // 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去
165 | int index = 0;
166 | for (byte b : byteArray) {
167 | resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
168 | resultCharArray[index++] = hexDigits[b & 0xf];
169 | }
170 | // 字符数组组合成字符串返回
171 | return new String(resultCharArray);
172 | }
173 |
174 | public static String strMd5(String input) {
175 | if (input == null || input.length() == 0) {
176 | return null;
177 | }
178 | try {
179 | MessageDigest md5 = MessageDigest.getInstance("MD5");
180 | md5.update(input.getBytes());
181 | byte[] byteArray = md5.digest();
182 |
183 | BigInteger bigInt = new BigInteger(1, byteArray);
184 | // 参数16表示16进制
185 | String result = bigInt.toString(16);
186 | // 不足32位高位补零
187 | while (result.length() < 32) {
188 | result = "0" + result;
189 | }
190 | return result;
191 | } catch (NoSuchAlgorithmException e) {
192 | e.printStackTrace();
193 | }
194 | return null;
195 | }
196 |
197 | public static final void ensurePathExist(String path) {
198 | File file = new File(path);
199 | if (!file.exists()) {
200 | file.mkdirs();
201 | }
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/core/src/main/java/com/wind/xposed/entry/util/ReflectUtils.java:
--------------------------------------------------------------------------------
1 | package com.wind.xposed.entry.util;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 |
7 | public class ReflectUtils {
8 |
9 | //获取类的实例的变量的值
10 | public static Object getField(Object receiver, String fieldName) {
11 | return getField(null, receiver, fieldName);
12 | }
13 |
14 | //获取类的静态变量的值
15 | public static Object getField(String className, String fieldName) {
16 | return getField(className,null, fieldName);
17 | }
18 |
19 | public static Object getField(Class> clazz, String className, String fieldName, Object receiver) {
20 | try {
21 | if (clazz == null) clazz = Class.forName(className);
22 | Field field = clazz.getDeclaredField(fieldName);
23 | if (field == null) return null;
24 | field.setAccessible(true);
25 | return field.get(receiver);
26 | } catch (Throwable e) {
27 | e.printStackTrace();
28 | }
29 | return null;
30 | }
31 |
32 | private static Object getField(String className, Object receiver, String fieldName) {
33 | Class> clazz = null;
34 | Field field;
35 | if (!isEmpty(className)) {
36 | try {
37 | clazz = Class.forName(className);
38 | } catch (ClassNotFoundException e) {
39 | e.printStackTrace();
40 | }
41 | } else {
42 | if (receiver != null) {
43 | clazz = receiver.getClass();
44 | }
45 | }
46 | if (clazz == null) return null;
47 |
48 | try {
49 | field = findField(clazz, fieldName);
50 | if (field == null) return null;
51 | field.setAccessible(true);
52 | return field.get(receiver);
53 | } catch (IllegalAccessException e) {
54 | e.printStackTrace();
55 | } catch (IllegalArgumentException e) {
56 | e.printStackTrace();
57 | } catch (NullPointerException e) {
58 | e.printStackTrace();
59 | }
60 | return null;
61 | }
62 |
63 | public static Object setField(Object receiver, String fieldName, Object value) {
64 | try {
65 | Field field;
66 | field = findField(receiver.getClass(), fieldName);
67 | if (field == null) {
68 | return null;
69 | }
70 | field.setAccessible(true);
71 | Object old = field.get(receiver);
72 | field.set(receiver, value);
73 | return old;
74 | } catch (IllegalAccessException e) {
75 | e.printStackTrace();
76 | } catch (IllegalArgumentException e) {
77 | e.printStackTrace();
78 | }
79 | return null;
80 | }
81 |
82 | public static Object setField(Class> clazz, Object receiver, String fieldName, Object value) {
83 | try {
84 | Field field;
85 | field = findField(clazz, fieldName);
86 | if (field == null) {
87 | return null;
88 | }
89 | field.setAccessible(true);
90 | Object old = field.get(receiver);
91 | field.set(receiver, value);
92 | return old;
93 | } catch (IllegalAccessException e) {
94 | e.printStackTrace();
95 | } catch (IllegalArgumentException e) {
96 | e.printStackTrace();
97 | }
98 | return null;
99 | }
100 |
101 | public static Object setField(String clazzName, Object receiver, String fieldName, Object value){
102 | try {
103 | Class> clazz = Class.forName(clazzName);
104 | Field field;
105 | field = findField(clazz, fieldName);
106 | if (field == null) {
107 | return null;
108 | }
109 | field.setAccessible(true);
110 | Object old = field.get(receiver);
111 | field.set(receiver, value);
112 | return old;
113 | } catch (IllegalAccessException e) {
114 | e.printStackTrace();
115 | } catch (IllegalArgumentException e) {
116 | e.printStackTrace();
117 | } catch (ClassNotFoundException e) {
118 | e.printStackTrace();
119 | }
120 | return null;
121 | }
122 |
123 | public static Object callMethod(String className, Object receiver, String methodName, Object... params) {
124 | Class> clazz = null;
125 | if (!isEmpty(className)) {
126 | try {
127 | clazz = Class.forName(className);
128 | } catch (ClassNotFoundException e) {
129 | e.printStackTrace();
130 | }
131 | } else {
132 | if (receiver != null) {
133 | clazz = receiver.getClass();
134 | }
135 | }
136 | if (clazz == null) return null;
137 | try {
138 | Method method = findMethod(clazz, methodName, params);
139 | if (method == null) {
140 | return null;
141 | }
142 | method.setAccessible(true);
143 | return method.invoke(receiver, params);
144 | } catch (IllegalArgumentException e) {
145 | e.printStackTrace();
146 | } catch (IllegalAccessException e) {
147 | e.printStackTrace();
148 | } catch (InvocationTargetException e) {
149 | e.printStackTrace();
150 | }
151 | return null;
152 | }
153 |
154 | private static Method findMethod(Class> clazz, String name, Object... arg) {
155 | Method[] methods = clazz.getMethods();
156 | Method method = null;
157 | for (Method m : methods) {
158 | if (methodFitParam(m, name, arg)) {
159 | method = m;
160 | break;
161 | }
162 | }
163 |
164 | if (method == null) {
165 | method = findDeclaredMethod(clazz, name, arg);
166 | }
167 | return method;
168 | }
169 |
170 | private static Method findDeclaredMethod(Class> clazz, String name, Object... arg) {
171 | Method[] methods = clazz.getDeclaredMethods();
172 | Method method = null;
173 | for (Method m : methods) {
174 | if (methodFitParam(m, name, arg)) {
175 | method = m;
176 | break;
177 | }
178 | }
179 |
180 | if (method == null) {
181 | if (clazz.equals(Object.class)) {
182 | return null;
183 | }
184 | return findDeclaredMethod(clazz.getSuperclass(), name, arg);
185 | }
186 | return method;
187 | }
188 |
189 | private static boolean methodFitParam(Method method, String methodName, Object... arg) {
190 | if (!methodName.equals(method.getName())) {
191 | return false;
192 | }
193 |
194 | Class>[] paramTypes = method.getParameterTypes();
195 | if (arg == null || arg.length == 0) {
196 | if (paramTypes == null || paramTypes.length == 0) {
197 | return true;
198 | } else {
199 | return false;
200 | }
201 | }
202 | if (paramTypes.length != arg.length) {
203 | return false;
204 | }
205 |
206 | for (int i = 0; i < arg.length; ++i) {
207 | Object ar = arg[i];
208 | Class> paramT = paramTypes[i];
209 | if (ar == null) continue;
210 |
211 | //TODO for primitive type
212 | if (paramT.isPrimitive()) continue;
213 |
214 | if (!paramT.isInstance(ar)) {
215 | return false;
216 | }
217 | }
218 | return true;
219 | }
220 |
221 | private static Field findField(Class> clazz, String name) {
222 | try {
223 | return clazz.getDeclaredField(name);
224 | } catch (NoSuchFieldException e) {
225 | if (clazz.equals(Object.class)) {
226 | e.printStackTrace();
227 | return null;
228 | }
229 | Class> base = clazz.getSuperclass();
230 | return findField(base, name);
231 | }
232 | }
233 |
234 | private static boolean isEmpty(String str) {
235 | return str == null || str.length() == 0;
236 | }
237 | }
--------------------------------------------------------------------------------
/core/src/main/java/android/app/AndroidAppHelper.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | import android.content.SharedPreferences;
4 | import android.content.pm.ApplicationInfo;
5 | import android.content.res.CompatibilityInfo;
6 | import android.content.res.Configuration;
7 | import android.content.res.Resources;
8 | import android.os.Build;
9 | import android.os.IBinder;
10 | import android.view.Display;
11 |
12 | import java.lang.ref.WeakReference;
13 | import java.util.Map;
14 |
15 | import de.robv.android.xposed.XSharedPreferences;
16 | import de.robv.android.xposed.XposedBridge;
17 |
18 | import static de.robv.android.xposed.XposedHelpers.findClass;
19 | import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
20 | import static de.robv.android.xposed.XposedHelpers.findMethodExactIfExists;
21 | import static de.robv.android.xposed.XposedHelpers.getObjectField;
22 | import static de.robv.android.xposed.XposedHelpers.newInstance;
23 | import static de.robv.android.xposed.XposedHelpers.setFloatField;
24 |
25 | /**
26 | * Contains various methods for information about the current app.
27 | *
28 | * For historical reasons, this class is in the {@code android.app} package. It can't be moved
29 | * without breaking compatibility with existing modules.
30 | */
31 | public final class AndroidAppHelper {
32 | private AndroidAppHelper() {}
33 |
34 | private static final Class> CLASS_RESOURCES_KEY;
35 | private static final boolean HAS_IS_THEMEABLE;
36 | private static final boolean HAS_THEME_CONFIG_PARAMETER;
37 |
38 | static {
39 | CLASS_RESOURCES_KEY = (Build.VERSION.SDK_INT < 19) ?
40 | findClass("android.app.ActivityThread$ResourcesKey", null)
41 | : findClass("android.content.res.ResourcesKey", null);
42 |
43 | HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null;
44 | HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && Build.VERSION.SDK_INT >= 21
45 | && findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
46 | }
47 |
48 | @SuppressWarnings({ "unchecked", "rawtypes" })
49 | private static Map