diff --git a/app/build.gradle b/app/build.gradle index 27d8f72f..5fbf1d28 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,15 +60,15 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.2.0-alpha03' + implementation 'androidx.appcompat:appcompat:1.2.0-rc01' implementation 'androidx.browser:browser:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4' - implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01" - implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-beta01' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6' + implementation "androidx.recyclerview:recyclerview:1.2.0-alpha03" + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01' implementation 'com.github.bumptech.glide:glide:4.11.0' implementation "com.github.topjohnwu.libsu:core:2.5.1" implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0' - implementation 'com.google.android.material:material:1.2.0-alpha05' + implementation 'com.google.android.material:material:1.2.0-alpha06' implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.takisoft.preferencex:preferencex:1.1.0' implementation 'com.takisoft.preferencex:preferencex-colorpicker:1.1.0' diff --git a/app/src/main/java/org/meowcat/edxposed/manager/CompileDialogFragment.java b/app/src/main/java/org/meowcat/edxposed/manager/CompileDialogFragment.java index 5f466af1..81ef938e 100644 --- a/app/src/main/java/org/meowcat/edxposed/manager/CompileDialogFragment.java +++ b/app/src/main/java/org/meowcat/edxposed/manager/CompileDialogFragment.java @@ -6,6 +6,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.Bundle; +import android.text.TextUtils; import android.view.LayoutInflater; import androidx.annotation.NonNull; @@ -19,6 +20,8 @@ import org.meowcat.edxposed.manager.databinding.FragmentCompileDialogBinding; import org.meowcat.edxposed.manager.util.ToastUtil; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; public class CompileDialogFragment extends AppCompatDialogFragment { @@ -103,7 +106,18 @@ public class CompileDialogFragment extends AppCompatDialogFragment { if (outerRef.get() == null) { return outerRef.get().requireContext().getString(R.string.compile_failed); } - return Shell.su(commands).exec().getOut().toString(); + // Also get STDERR + List stdout = new ArrayList<>(); + List stderr = new ArrayList<>(); + Shell.Result result = Shell.su(commands).to(stdout, stderr).exec(); + List ret; + if (stderr.size() > 0) { + return "Error: " + TextUtils.join("\n", stderr); + } else if (!result.isSuccess()) { // they might don't write to stderr + return "Error: " + TextUtils.join("\n", stdout); + } else { + return TextUtils.join("\n", stdout); + } } @Override @@ -111,10 +125,13 @@ public class CompileDialogFragment extends AppCompatDialogFragment { if (outerRef.get() == null || !outerRef.get().isAdded()) { return; } - if ("".equals(result.substring(1, result.length() - 1))) { - ToastUtil.showLongToast(outerRef.get().requireContext(), R.string.compile_failed); + Context ctx = outerRef.get().requireContext(); + if (result.length() == 0) { + ToastUtil.showLongToast(ctx, R.string.compile_failed); + } else if (result.length() >= 5 && "Error".equals(result.substring(0, 5))) { + ToastUtil.showLongToast(ctx, ctx.getString(R.string.compile_failed_with_info) + " " + result.substring(6)); } else { - ToastUtil.showLongToast(outerRef.get().requireContext(), R.string.done); + ToastUtil.showLongToast(ctx, R.string.done); } outerRef.get().dismissAllowingStateLoss(); } diff --git a/app/src/main/java/org/meowcat/edxposed/manager/util/ToastUtil.java b/app/src/main/java/org/meowcat/edxposed/manager/util/ToastUtil.java index fa8d602a..1060356f 100644 --- a/app/src/main/java/org/meowcat/edxposed/manager/util/ToastUtil.java +++ b/app/src/main/java/org/meowcat/edxposed/manager/util/ToastUtil.java @@ -15,4 +15,8 @@ public class ToastUtil { Toast.makeText(context, resId, Toast.LENGTH_LONG).show(); } + public static void showLongToast(Context context, String msg) { + Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); + } + } diff --git a/app/src/main/java/org/meowcat/edxposed/manager/xposed/Enhancement.java b/app/src/main/java/org/meowcat/edxposed/manager/xposed/Enhancement.java index 88d525bb..8795ae1b 100644 --- a/app/src/main/java/org/meowcat/edxposed/manager/xposed/Enhancement.java +++ b/app/src/main/java/org/meowcat/edxposed/manager/xposed/Enhancement.java @@ -1,9 +1,16 @@ -package org.meowcat.edxposed.manager.xposed;import android.content.pm.ApplicationInfo; +package org.meowcat.edxposed.manager.xposed; + +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.os.Binder; import android.os.Build; +import android.os.FileObserver; +import android.os.StrictMode; +import android.os.UserHandle; +import android.util.SparseArray; import androidx.annotation.Keep; +import androidx.annotation.Nullable; import org.meowcat.edxposed.manager.StatusInstallerFragment; @@ -13,6 +20,7 @@ import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import de.robv.android.xposed.IXposedHookLoadPackage; @@ -21,9 +29,11 @@ import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; -import de.robv.android.xposed.installer.XposedApp; +import static de.robv.android.xposed.XposedHelpers.callMethod; +import static de.robv.android.xposed.XposedHelpers.callStaticMethod; import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; +import static java.util.Collections.binarySearch; import static org.meowcat.edxposed.manager.BuildConfig.APPLICATION_ID; @Keep @@ -34,8 +44,9 @@ public class Enhancement implements IXposedHookLoadPackage { private static final String LEGACY_INSTALLER = "de.robv.android.xposed.installer"; - private static final List HIDE_WHITE_LIST = Arrays.asList( // TODO: more whitelist packages + private static final List HIDE_WHITE_LIST = Arrays.asList( // TODO: more whitelist packages APPLICATION_ID, // Whitelist or crash + LEGACY_INSTALLER, // for safety "com.android.providers.downloads", // For download modules "com.android.providers.downloads.ui", "com.android.packageinstaller", // For uninstall EdXposed Manager @@ -44,39 +55,84 @@ public class Enhancement implements IXposedHookLoadPackage { "com.android.permissioncontroller", // For permissions grant "com.topjohnwu.magisk", // For superuser root grant "eu.chainfire.supersu" - ); // System server (uid <= 1000) will auto pass + ); // UserHandle.isCore(uid) will auto pass - private static List modulesList = null; + private static final SparseArray> modulesList = new SparseArray<>(); + private static final SparseArray modulesListObservers = new SparseArray<>(); - private static boolean getFlagState(int user, String flag) { - return new File(String.format("/data/user_de/%s/%s/conf/%s", user, APPLICATION_ID, flag)).exists(); + static { + Collections.sort(HIDE_WHITE_LIST); } - private static List getModulesList(int user) { - if (modulesList != null) { - return modulesList; - } - final File listFile = new File(String.format("/data/user_de/%s/%s/conf/enabled_modules.list", user, APPLICATION_ID)); - List list = new ArrayList<>(); + private static boolean getFlagState(int user, String flag) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); try { - FileReader fileReader = new FileReader(listFile); - BufferedReader bufferedReader = new BufferedReader(fileReader); - String str; - while ((str = bufferedReader.readLine()) != null) { - list.add(str); - } - bufferedReader.close(); - fileReader.close(); - } catch (IOException e) { - e.printStackTrace(); + return new File(String.format("/data/user_de/%s/%s/conf/%s", user, APPLICATION_ID, flag)).exists(); + } finally { + StrictMode.setThreadPolicy(oldPolicy); } - modulesList = list; + } + + private static List getModulesList(final int user) { + final int index = modulesList.indexOfKey(user); + if (index >= 0) { + return modulesList.valueAt(index); + } + + final String filename = String.format("/data/user_de/%s/%s/conf/enabled_modules.list", user, APPLICATION_ID); + final FileObserver observer = new FileObserver(filename) { + @Override + public void onEvent(int event, @Nullable String path) { + switch (event) { + case FileObserver.MODIFY: + modulesList.put(user, readModulesList(filename)); + break; + case FileObserver.MOVED_FROM: + case FileObserver.MOVED_TO: + case FileObserver.MOVE_SELF: + case FileObserver.DELETE: + case FileObserver.DELETE_SELF: + modulesList.remove(user); + modulesListObservers.remove(user); + break; + } + } + }; + modulesListObservers.put(user, observer); + final List list = readModulesList(filename); + modulesList.put(user, list); + observer.startWatching(); return list; } + private static List readModulesList(final String filename) { + XposedBridge.log("EdXpMgrEx: Reading modules list " + filename + "..."); + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + final File listFile = new File(filename); + final List list = new ArrayList<>(); + try { + final FileReader fileReader = new FileReader(listFile); + final BufferedReader bufferedReader = new BufferedReader(fileReader); + String str; + while ((str = bufferedReader.readLine()) != null) { + list.add(str); + } + bufferedReader.close(); + fileReader.close(); + } catch (IOException e) { + XposedBridge.log(e); + } + Collections.sort(list); + return list; + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + private static void hookAllMethods(String className, ClassLoader classLoader, String methodName, XC_MethodHook callback) { try { - Class hookClass = XposedHelpers.findClassIfExists(className, classLoader); + final Class hookClass = XposedHelpers.findClassIfExists(className, classLoader); if (hookClass == null || XposedBridge.hookAllMethods(hookClass, methodName, callback).size() == 0) XposedBridge.log("Failed to hook " + methodName + " method in " + className); } catch (Throwable t) { @@ -87,31 +143,33 @@ public class Enhancement implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) { if (lpparam.packageName.equals("android")) { - // android.app.ApplicationPackageManager.getInstalledApplicationsAsUser(int flag, int userId) - findAndHookMethod("android.app.ApplicationPackageManager", lpparam.classLoader, "getInstalledApplicationsAsUser", int.class, int.class, new XC_MethodHook() { + // com.android.server.pm.PackageManagerService.getInstalledApplications(int flag, int userId) + findAndHookMethod("com.android.server.pm.PackageManagerService", lpparam.classLoader, "getInstalledApplications", int.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) { if (param.args != null && param.args[0] != null) { - final int userId = (int) param.args[1]; final int packageUid = Binder.getCallingUid(); + if ((boolean) callStaticMethod(UserHandle.class, "isCore", packageUid)) { + return; + } + final int userId = (int) param.args[1]; boolean isXposedModule = false; - final String[] packages = - (String[]) XposedHelpers.callMethod(param.thisObject, "getPackagesForUid", packageUid); - if (packages == null || packages.length == 0 || packageUid <= 1000) { + final String[] packages = (String[]) callMethod(param.thisObject, "getPackagesForUid", packageUid); + if (packages == null || packages.length == 0) { return; } for (String packageName : packages) { - if (HIDE_WHITE_LIST.contains(packageName)) { + if (binarySearch(HIDE_WHITE_LIST, packageName) >= 0) { return; } - if (getModulesList(userId).contains(packageName)) { + if (binarySearch(getModulesList(userId), packageName) >= 0) { isXposedModule = true; break; } } - @SuppressWarnings("unchecked") List applicationInfoList = (List) param.getResult(); + @SuppressWarnings("unchecked") final List applicationInfoList = (List) callMethod(param.getResult(), "getList"); if (isXposedModule) { if (getFlagState(userId, mPretendXposedInstallerFlag)) { for (ApplicationInfo applicationInfo : applicationInfoList) { @@ -132,35 +190,37 @@ public class Enhancement implements IXposedHookLoadPackage { } } } - param.setResult(applicationInfoList); + param.setResult(param.getResult()); // "reset" the result to indicate that we handled it } } }); - // android.app.ApplicationPackageManager.getInstalledPackagesAsUser(int flag, int userId) - findAndHookMethod("android.app.ApplicationPackageManager", lpparam.classLoader, "getInstalledPackagesAsUser", int.class, int.class, new XC_MethodHook() { + // com.android.server.pm.PackageManagerService.getInstalledPackages(int flag, int userId) + findAndHookMethod("com.android.server.pm.PackageManagerService", lpparam.classLoader, "getInstalledPackages", int.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) { if (param.args != null && param.args[0] != null) { - final int userId = (int) param.args[1]; final int packageUid = Binder.getCallingUid(); + if ((boolean) callStaticMethod(UserHandle.class, "isCore", packageUid)) { + return; + } + final int userId = (int) param.args[1]; boolean isXposedModule = false; - final String[] packages = - (String[]) XposedHelpers.callMethod(param.thisObject, "getPackagesForUid", packageUid); - if (packages == null || packages.length == 0 || packageUid <= 1000) { + final String[] packages = (String[]) callMethod(param.thisObject, "getPackagesForUid", packageUid); + if (packages == null || packages.length == 0) { return; } for (String packageName : packages) { - if (HIDE_WHITE_LIST.contains(packageName)) { + if (binarySearch(HIDE_WHITE_LIST, packageName) >= 0) { return; } - if (getModulesList(userId).contains(packageName)) { + if (binarySearch(getModulesList(userId), packageName) >= 0) { isXposedModule = true; break; } } - @SuppressWarnings("unchecked") List packageInfoList = (List) param.getResult(); + @SuppressWarnings("unchecked") final List packageInfoList = (List) callMethod(param.getResult(), "getList"); if (isXposedModule) { if (getFlagState(userId, mPretendXposedInstallerFlag)) { for (PackageInfo packageInfo : packageInfoList) { @@ -181,7 +241,7 @@ public class Enhancement implements IXposedHookLoadPackage { } } } - param.setResult(packageInfoList); + param.setResult(param.getResult()); // "reset" the result to indicate that we handled it } } }); @@ -190,20 +250,22 @@ public class Enhancement implements IXposedHookLoadPackage { @Override protected void beforeHookedMethod(MethodHookParam param) { if (param.args != null && param.args[0] != null) { - final int userId = (int) param.args[2]; final int packageUid = Binder.getCallingUid(); + if ((boolean) callStaticMethod(UserHandle.class, "isCore", packageUid)) { + return; + } + final int userId = (int) param.args[2]; boolean isXposedModule = false; - final String[] packages = - (String[]) XposedHelpers.callMethod(param.thisObject, "getPackagesForUid", packageUid); - if (packages == null || packages.length == 0 || packageUid <= 1000) { + final String[] packages = (String[]) callMethod(param.thisObject, "getPackagesForUid", packageUid); + if (packages == null || packages.length == 0) { return; } for (String packageName : packages) { - if (HIDE_WHITE_LIST.contains(packageName)) { + if (binarySearch(HIDE_WHITE_LIST, packageName) >= 0) { return; } - if (getModulesList(userId).contains(packageName)) { + if (binarySearch(getModulesList(userId), packageName) >= 0) { isXposedModule = true; break; } @@ -223,7 +285,6 @@ public class Enhancement implements IXposedHookLoadPackage { } } } - } }); // com.android.server.pm.PackageManagerService.getPackageInfo(String packageName, int flag, int userId) @@ -231,20 +292,22 @@ public class Enhancement implements IXposedHookLoadPackage { @Override protected void beforeHookedMethod(MethodHookParam param) { if (param.args != null && param.args[0] != null) { - final int userId = (int) param.args[2]; final int packageUid = Binder.getCallingUid(); + if ((boolean) callStaticMethod(UserHandle.class, "isCore", packageUid)) { + return; + } + final int userId = (int) param.args[2]; boolean isXposedModule = false; - final String[] packages = - (String[]) XposedHelpers.callMethod(param.thisObject, "getPackagesForUid", packageUid); - if (packages == null || packages.length == 0 || packageUid <= 1000) { + final String[] packages = (String[]) callMethod(param.thisObject, "getPackagesForUid", packageUid); + if (packages == null || packages.length == 0) { return; } for (String packageName : packages) { - if (HIDE_WHITE_LIST.contains(packageName)) { + if (binarySearch(HIDE_WHITE_LIST, packageName) >= 0) { return; } - if (getModulesList(userId).contains(packageName)) { + if (binarySearch(getModulesList(userId), packageName) >= 0) { isXposedModule = true; break; } @@ -268,7 +331,7 @@ public class Enhancement implements IXposedHookLoadPackage { }); // Hook AM to remove restrict of EdXposed Manager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - hookAllMethods("com.android.server.am.ActivityManagerService", lpparam.classLoader, "appRestrictedInBackgroundLocked", new XC_MethodHook() { + final XC_MethodHook hook = new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) { if (param.args != null && param.args[1] != null) { @@ -277,31 +340,14 @@ public class Enhancement implements IXposedHookLoadPackage { } } } - }); - hookAllMethods("com.android.server.am.ActivityManagerService", lpparam.classLoader, "appServicesRestrictedInBackgroundLocked", new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - if (param.args != null && param.args[1] != null) { - if (param.args[1].equals(APPLICATION_ID)) { - param.setResult(0); - } - } - } - }); - hookAllMethods("com.android.server.am.ActivityManagerService", lpparam.classLoader, "getAppStartModeLocked", new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) { - if (param.args != null && param.args[1] != null) { - if (param.args[1].equals(APPLICATION_ID)) { - param.setResult(0); - } - } - } - }); + }; + hookAllMethods("com.android.server.am.ActivityManagerService", lpparam.classLoader, "appRestrictedInBackgroundLocked", hook); + hookAllMethods("com.android.server.am.ActivityManagerService", lpparam.classLoader, "appServicesRestrictedInBackgroundLocked", hook); + hookAllMethods("com.android.server.am.ActivityManagerService", lpparam.classLoader, "getAppStartModeLocked", hook); } } else if (lpparam.packageName.equals(APPLICATION_ID)) { // Make sure Xposed work - XposedHelpers.findAndHookMethod(XposedApp.class.getName(), lpparam.classLoader, "isEnhancementEnabled", XC_MethodReplacement.returnConstant(true)); + XposedHelpers.findAndHookMethod(StatusInstallerFragment.class.getName(), lpparam.classLoader, "isEnhancementEnabled", XC_MethodReplacement.returnConstant(true)); // XposedHelpers.findAndHookMethod(StatusInstallerFragment.class.getName(), lpparam.classLoader, "isSELinuxEnforced", XC_MethodReplacement.returnConstant(SELinuxHelper.isSELinuxEnforced())); } } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 684f31e6..9e325702 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -266,6 +266,7 @@ 显示模块和管理器 在应用列表内显示模块和管理器\n此选项并不会移除已勾选的应用标识 优化失败或返回值为空 + 优化失败: 强制添加模块 强制将模块添加进白名单并移出黑名单\n关闭此选项并不会移除已勾选的应用标识\n仅当启用应用名单模式时有效 使用镜像化的模块列表 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f42aa2e0..3664b795 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -300,6 +300,7 @@ Show modules and manager Show modules and manager in application list\nThis option does not remove checked application flag Optimization failed or return value is empty + Optimization failed: Force hook modules Force add modules to white-list and remove from black list\nClosing this option does not remove modules from white list\nApp list mode is required to enable Not installed diff --git a/build.gradle b/build.gradle index a8a4fd85..e998aa72 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0-beta03' + classpath 'com.android.tools.build:gradle:4.0.0-rc01' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2' // NOTE: Do not place your application dependencies here; they belong