From 8ec67055bfde704e4f6d923d55353a5289e71401 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=84=95=E6=99=A8HChen?=
<123531821+HChenX@users.noreply.github.com>
Date: Sun, 31 Mar 2024 17:30:09 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=AE=89=E5=85=A8?=
=?UTF-8?q?=E6=A8=A1=E5=BC=8F=20(#506)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 推送安全模式测试
* test
* 是船新的安全模式!
* 更改计数
* Update MainActivity.java
* 新增 系统界面-锁屏-允许使用三方应用锁屏时使用人脸及指纹识别
* Update AllowThirdLockScreenUseFace.java
* Update SystemUI.java
* Update AllowThirdLockScreenUseFace.java
* 同步
* test
* 同步
* test
* Update MultiActionSettings.java
* 优化AlertDialog布局
* 安全模式线同步上游
* 更新Shell执行
* 同步上游
* 完善安全模式核心逻辑
* 完善安全模式核心逻辑
* update theme
* 完善安全模式
* 同步上游
* 更新检查的作用域
---------
Co-authored-by: 绀漓丨Sevtinge <89193494+Sevtinge@users.noreply.github.com>
Co-authored-by: MoralNorm <732388462@qq.com>
---
app/src/main/AndroidManifest.xml | 34 ++-
.../sevtinge/hyperceiler/module/app/Demo.java | 2 +
.../hyperceiler/module/base/BaseModule.java | 32 ++
.../module/base/BaseXposedInit.java | 18 +-
.../module/hook/demo/CrashDemo.java | 21 ++
.../sevtinge/hyperceiler/safe/CrashData.java | 208 +++++++++++++
.../sevtinge/hyperceiler/safe/CrashHook.java | 282 ++++++++++++++++++
.../hyperceiler/safe/CrashReportActivity.java | 84 ++++++
.../hyperceiler/safe/CrashService.java | 50 ++++
.../sevtinge/hyperceiler/ui/MainActivity.java | 46 ++-
.../hyperceiler/utils/DialogHelper.java | 86 +++---
.../hyperceiler/utils/shell/ShellUtils.java | 2 +-
.../main/res/layout/activity_crash_dialog.xml | 4 +
.../main/res/layout/crash_report_dialog.xml | 24 ++
app/src/main/res/values-night/themes.xml | 14 +
app/src/main/res/values/colors.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
app/src/main/res/values/themes.xml | 34 ++-
18 files changed, 896 insertions(+), 47 deletions(-)
create mode 100644 app/src/main/java/com/sevtinge/hyperceiler/module/hook/demo/CrashDemo.java
create mode 100644 app/src/main/java/com/sevtinge/hyperceiler/safe/CrashData.java
create mode 100644 app/src/main/java/com/sevtinge/hyperceiler/safe/CrashHook.java
create mode 100644 app/src/main/java/com/sevtinge/hyperceiler/safe/CrashReportActivity.java
create mode 100644 app/src/main/java/com/sevtinge/hyperceiler/safe/CrashService.java
create mode 100644 app/src/main/res/layout/activity_crash_dialog.xml
create mode 100644 app/src/main/res/layout/crash_report_dialog.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 89b43a786f..c31dbf7674 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -67,13 +67,36 @@
android:name=".ui.MainActivity"
android:exported="true"
android:screenOrientation="portrait"
- tools:ignore="DiscouragedApi,LockedOrientationActivity">
+ tools:ignore="DiscouragedApi,LockedOrientationActivity"
+ android:theme="@style/Theme.HyperCeiler.SystemBarBackgrounds">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/module/app/Demo.java b/app/src/main/java/com/sevtinge/hyperceiler/module/app/Demo.java
index 1b79470c65..07bfb08cf8 100644
--- a/app/src/main/java/com/sevtinge/hyperceiler/module/app/Demo.java
+++ b/app/src/main/java/com/sevtinge/hyperceiler/module/app/Demo.java
@@ -1,11 +1,13 @@
package com.sevtinge.hyperceiler.module.app;
import com.sevtinge.hyperceiler.module.base.BaseModule;
+import com.sevtinge.hyperceiler.module.hook.demo.CrashDemo;
import com.sevtinge.hyperceiler.module.hook.demo.ToastTest;
public class Demo extends BaseModule {
@Override
public void handleLoadPackage() {
initHook(new ToastTest(), true);
+ initHook(new CrashDemo(), true);
}
}
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseModule.java b/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseModule.java
index faeca5ec06..e9f037ffb7 100644
--- a/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseModule.java
+++ b/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseModule.java
@@ -22,11 +22,17 @@
import com.sevtinge.hyperceiler.XposedInit;
import com.sevtinge.hyperceiler.module.base.dexkit.DexKit;
import com.sevtinge.hyperceiler.module.base.dexkit.InitDexKit;
+import com.sevtinge.hyperceiler.safe.CrashData;
import com.sevtinge.hyperceiler.utils.ContextUtils;
+import com.sevtinge.hyperceiler.utils.PropUtils;
import com.sevtinge.hyperceiler.utils.api.ProjectApi;
import com.sevtinge.hyperceiler.utils.log.XposedLogUtils;
import com.sevtinge.hyperceiler.utils.prefs.PrefsMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
public abstract class BaseModule implements IXposedHook {
@@ -34,8 +40,14 @@ public abstract class BaseModule implements IXposedHook {
public LoadPackageParam mLoadPackageParam = null;
public String TAG = getClass().getSimpleName();
public final PrefsMap mPrefsMap = XposedInit.mPrefsMap;
+ private static HashMap swappedMap = CrashData.swappedData();
public void init(LoadPackageParam lpparam) {
+ if (swappedMap.isEmpty()) swappedMap = CrashData.swappedData();
+ if (needIntercept(lpparam.packageName)) {
+ XposedLogUtils.logI(TAG, "进入安全模式: " + lpparam.packageName);
+ return;
+ }
EzXHelper.initHandleLoadPackage(lpparam);
EzXHelper.setLogTag(TAG);
EzXHelper.setToastTag(TAG);
@@ -73,6 +85,26 @@ public void init(LoadPackageParam lpparam) {
}
}
+ private boolean needIntercept(String pkg) {
+ ArrayList report = getReportCrashProp();
+ for (String s : report) {
+ String mPkg = swappedMap.get(s);
+ if (mPkg != null) {
+ return mPkg.equals(pkg);
+ }
+ }
+ return false;
+ }
+
+ private ArrayList getReportCrashProp() {
+ String data = PropUtils.getProp("persist.hyperceiler.crash.report", "");
+ if (data.isEmpty()) {
+ return new ArrayList<>();
+ }
+ String[] sp = data.split(",");
+ return new ArrayList<>(Arrays.asList(sp));
+ }
+
@Override
public void initZygote() {
}
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseXposedInit.java b/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseXposedInit.java
index df7b0aeab4..f81962d812 100644
--- a/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseXposedInit.java
+++ b/app/src/main/java/com/sevtinge/hyperceiler/module/base/BaseXposedInit.java
@@ -18,6 +18,7 @@
*/
package com.sevtinge.hyperceiler.module.base;
+import static com.sevtinge.hyperceiler.callback.ITAG.TAG;
import static com.sevtinge.hyperceiler.utils.Helpers.getPackageVersionCode;
import static com.sevtinge.hyperceiler.utils.Helpers.getPackageVersionName;
import static com.sevtinge.hyperceiler.utils.devicesdk.SystemSDKKt.getAndroidVersion;
@@ -26,6 +27,7 @@
import static com.sevtinge.hyperceiler.utils.devicesdk.SystemSDKKt.isMoreAndroidVersion;
import static com.sevtinge.hyperceiler.utils.devicesdk.SystemSDKKt.isMoreHyperOSVersion;
import static com.sevtinge.hyperceiler.utils.log.LogManager.logLevelDesc;
+import static com.sevtinge.hyperceiler.utils.log.XposedLogUtils.logE;
import static com.sevtinge.hyperceiler.utils.log.XposedLogUtils.logI;
import androidx.annotation.CallSuper;
@@ -82,8 +84,8 @@
import com.sevtinge.hyperceiler.module.app.VoiceAssist;
import com.sevtinge.hyperceiler.module.app.Weather;
import com.sevtinge.hyperceiler.module.base.tool.ResourcesTool;
+import com.sevtinge.hyperceiler.safe.CrashHook;
import com.sevtinge.hyperceiler.utils.api.ProjectApi;
-import com.sevtinge.hyperceiler.utils.log.AndroidLogUtils;
import com.sevtinge.hyperceiler.utils.log.XposedLogUtils;
import com.sevtinge.hyperceiler.utils.prefs.PrefsMap;
import com.sevtinge.hyperceiler.utils.prefs.PrefsUtils;
@@ -190,7 +192,7 @@ public void setXSharedPrefs() {
mPrefsMap.putAll(allPrefs);
}
} catch (Throwable t) {
- AndroidLogUtils.logD("setXSharedPrefs", t);
+ XposedLogUtils.logE("setXSharedPrefs", t);
}
}
}
@@ -208,12 +210,12 @@ public void init(LoadPackageParam lpparam) {
mSystemFramework.init(lpparam);
mVariousSystemApps.init(lpparam);
}
- // try {
- // new CrashHook(lpparam);
- // logI(TAG.TAG, "Success Hook Crash");
- // } catch (Exception e) {
- // logE(TAG.TAG, "Hook Crash E: " + e);
- // }
+ try {
+ new CrashHook(lpparam);
+ logI(TAG, "Success Hook Crash");
+ } catch (Exception e) {
+ logE(TAG, "Hook Crash E: " + e);
+ }
}
case "com.android.systemui" -> {
if (isSystemUIModuleEnable() && isMoreAndroidVersion(33)) {
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/module/hook/demo/CrashDemo.java b/app/src/main/java/com/sevtinge/hyperceiler/module/hook/demo/CrashDemo.java
new file mode 100644
index 0000000000..27d507c7b1
--- /dev/null
+++ b/app/src/main/java/com/sevtinge/hyperceiler/module/hook/demo/CrashDemo.java
@@ -0,0 +1,21 @@
+package com.sevtinge.hyperceiler.module.hook.demo;
+
+import com.sevtinge.hyperceiler.module.base.BaseHook;
+
+import de.robv.android.xposed.XposedHelpers;
+
+public class CrashDemo extends BaseHook {
+ @Override
+ public void init() throws NoSuchMethodException {
+ XposedHelpers.findAndHookMethod("com.hchen.demo.MainActivity", lpparam.classLoader,
+ "crash", int.class, new MethodHook() {
+ @Override
+ protected void before(MethodHookParam param) throws Throwable {
+ int o = (int) param.args[0];
+ param.args[0] = 0;
+ logE(TAG, "int: " + o);
+ }
+ }
+ );
+ }
+}
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashData.java b/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashData.java
new file mode 100644
index 0000000000..2a2d6cfba9
--- /dev/null
+++ b/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashData.java
@@ -0,0 +1,208 @@
+package com.sevtinge.hyperceiler.safe;
+
+import static com.sevtinge.hyperceiler.utils.log.XposedLogUtils.logE;
+
+import com.sevtinge.hyperceiler.callback.ITAG;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CrashData {
+ private static final String TAG = ITAG.TAG + ": CrashRecord";
+ private static final HashMap scopeMap = new HashMap<>();
+ private static final HashMap swappedMap = new HashMap<>();
+
+ /**
+ * 把模块当前使用的全部作用域加入 Map。
+ *
+ * @return Map
+ * @noinspection SameReturnValue
+ */
+ public static HashMap scopeData() {
+ if (scopeMap.isEmpty()) {
+ scopeMap.put("android", "android");
+ scopeMap.put("com.android.browser", "browser");
+ scopeMap.put("com.android.camera", "camera");
+ scopeMap.put("com.android.calendar", "calendar");
+ scopeMap.put("com.android.externalstorage", "external");
+ scopeMap.put("com.android.fileexplorer", "file");
+ scopeMap.put("com.android.incallui", "incallui");
+ scopeMap.put("com.android.mms", "mms");
+ scopeMap.put("com.android.nfc", "nfc");
+ scopeMap.put("com.android.phone", "phone");
+ scopeMap.put("com.android.providers.downloads", "download");
+ scopeMap.put("com.android.providers.downloads.ui", "ui");
+ scopeMap.put("com.android.systemui", "systemui");
+ scopeMap.put("com.android.settings", "settings");
+ scopeMap.put("com.android.thememanager", "theme");
+ scopeMap.put("com.android.updater", "updater");
+ scopeMap.put("com.lbe.security.miui", "lbe");
+ scopeMap.put("com.milink.service", "milink");
+ scopeMap.put("com.miui.aod", "aod");
+ scopeMap.put("com.miui.backup", "backup");
+ scopeMap.put("com.miui.cloudservice", "cloud");
+ scopeMap.put("com.miui.contentextension", "content");
+ scopeMap.put("com.miui.creation", "creation");
+ scopeMap.put("com.miui.gallery", "gallery");
+ scopeMap.put("com.miui.guardprovider", "guard");
+ scopeMap.put("com.miui.home", "home");
+ scopeMap.put("com.miui.huanji", "huan");
+ scopeMap.put("com.miui.mediaeditor", "media");
+ scopeMap.put("com.miui.mishare.connectivity", "share");
+ scopeMap.put("com.miui.misound", "sound");
+ scopeMap.put("com.miui.miwallpaper", "wallpaper");
+ scopeMap.put("com.miui.notes", "notes");
+ scopeMap.put("com.miui.packageinstaller", "install");
+ scopeMap.put("com.miui.personalassistant", "personal");
+ scopeMap.put("com.miui.powerkeeper", "power");
+ scopeMap.put("com.miui.screenrecorder", "recorder");
+ scopeMap.put("com.miui.screenshot", "shot");
+ scopeMap.put("com.miui.securityadd", "add");
+ scopeMap.put("com.miui.securitycenter", "center");
+ scopeMap.put("com.miui.tsmclient", "tsmclient");
+ scopeMap.put("com.miui.voiceassist", "voice");
+ scopeMap.put("com.miui.weather2", "weather");
+ scopeMap.put("com.xiaomi.aiasst.vision", "vision");
+ scopeMap.put("com.xiaomi.barrage", "barrage");
+ scopeMap.put("com.xiaomi.joyose", "joyose");
+ scopeMap.put("com.xiaomi.market", "market");
+ scopeMap.put("com.xiaomi.mirror", "mirror");
+ scopeMap.put("com.xiaomi.misettings", "misettings");
+ scopeMap.put("com.xiaomi.mtb", "mtb");
+ scopeMap.put("com.xiaomi.scanner", "scanner");
+ scopeMap.put("com.xiaomi.trustservice", "trust");
+ scopeMap.put("com.hchen.demo", "demo");
+ return scopeMap;
+ }
+ return scopeMap;
+ }
+
+ /**
+ * 交换 Map 中 Key 和 Value 位置。
+ *
+ * @return 交换后的 Map
+ * @noinspection SameReturnValue
+ */
+ public static HashMap swappedData() {
+ if (scopeMap.isEmpty()) scopeData();
+ if (!swappedMap.isEmpty()) return swappedMap;
+ for (Map.Entry entry : scopeMap.entrySet()) {
+ swappedMap.put(entry.getValue(), entry.getKey());
+ }
+ return swappedMap;
+ }
+}
+
+/**
+ * 崩溃记录数据库
+ */
+class CrashRecord {
+ public static final String TAG = ITAG.TAG + ": CrashRecord";
+ public String label;
+ public String pkg;
+ public long time;
+ public int count;
+
+ public CrashRecord(String l, String p, long t, int c) {
+ label = l;
+ pkg = p;
+ time = t;
+ count = c;
+ }
+
+ public CrashRecord(String p, int c) {
+ pkg = p;
+ count = c;
+ }
+
+ public JSONObject toJSON() {
+ JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("l", label);
+ jsonObject.put("p", pkg);
+ jsonObject.put("t", time);
+ jsonObject.put("c", count);
+ return jsonObject;
+ } catch (JSONException e) {
+ logE(TAG, "Failed to convert JSON!" + e);
+ }
+ return jsonObject;
+ }
+
+ public JSONObject toJSONSmall() {
+ JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("p", pkg);
+ jsonObject.put("c", count);
+ return jsonObject;
+ } catch (JSONException e) {
+ logE(TAG, "Failed to convert JSON!" + e);
+ }
+ return jsonObject;
+ }
+
+ public static String getLabel(JSONObject jsonObject) {
+ try {
+ return jsonObject.getString("l");
+ } catch (JSONException e) {
+ logE(TAG, "Failed to get name!" + e);
+ }
+ return "null";
+ }
+
+ public static String getPkg(JSONObject jsonObject) {
+ try {
+ return jsonObject.getString("p");
+ } catch (JSONException e) {
+ logE(TAG, "Failed to get package name!" + e);
+ }
+ return "null";
+ }
+
+ public static long getTime(JSONObject jsonObject) {
+ try {
+ return jsonObject.getLong("t");
+ } catch (JSONException e) {
+ logE(TAG, "Failed to get timestamp!" + e);
+ }
+ return -1L;
+ }
+
+ public static int getCount(JSONObject jsonObject) {
+ try {
+ return jsonObject.getInt("c");
+ } catch (JSONException e) {
+ logE(TAG, "Failed to get the number of times!" + e);
+ }
+ return -1;
+ }
+
+ public static JSONObject putCount(JSONObject jsonObject, int count) {
+ try {
+ return jsonObject.put("c", count);
+ } catch (JSONException e) {
+ logE(TAG, "Failed to set the number of times!" + e);
+ }
+ return null;
+ }
+
+ public static ArrayList toArray(String json) {
+ try {
+ ArrayList list = new ArrayList<>();
+ JSONArray jsonArray = new JSONArray(json);
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject obj = jsonArray.getJSONObject(i);
+ list.add(obj);
+ }
+ return list;
+ } catch (Exception e) {
+ logE(TAG, "Failed to convert Array!" + e);
+ }
+ return new ArrayList<>();
+ }
+}
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashHook.java b/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashHook.java
new file mode 100644
index 0000000000..cffe4d41b8
--- /dev/null
+++ b/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashHook.java
@@ -0,0 +1,282 @@
+package com.sevtinge.hyperceiler.safe;
+
+import android.app.ActivityOptions;
+import android.app.ApplicationErrorReport;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import com.sevtinge.hyperceiler.callback.ITAG;
+import com.sevtinge.hyperceiler.module.base.tool.HookTool;
+import com.sevtinge.hyperceiler.utils.api.ProjectApi;
+
+import org.json.JSONObject;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import de.robv.android.xposed.XposedHelpers;
+import de.robv.android.xposed.callbacks.XC_LoadPackage;
+
+/**
+ * 推荐使用的方法,直接 Hook 系统,
+ * 可能误报,但是很稳定。
+ */
+public class CrashHook extends HookTool {
+ private static final String TAG = ITAG.TAG + ": CrashHook";
+ private static HashMap scopeMap = new HashMap<>();
+
+ public CrashHook(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Exception {
+ backgroundActivity(loadPackageParam.classLoader);
+ init(loadPackageParam.classLoader);
+ scopeMap = CrashData.scopeData();
+ }
+
+ public void init(ClassLoader classLoader) throws Exception {
+ Class> appError = findClassIfExists("com.android.server.am.AppErrors", classLoader);
+ if (appError == null) {
+ throw new ClassNotFoundException("No such 'com.android.server.am.AppErrors' classLoader: " + classLoader);
+ }
+ Method hookError = null;
+ for (Method error : appError.getDeclaredMethods()) {
+ if ("handleAppCrashInActivityController".equals(error.getName()))
+ if (error.getReturnType().equals(boolean.class)) {
+ hookError = error;
+ break;
+ }
+ }
+ if (hookError == null) {
+ throw new NoSuchMethodException("No such Method: handleAppCrashInActivityController, ClassLoader: " + classLoader);
+ }
+
+ hookMethod(hookError, new MethodHook() {
+ @Override
+ protected void after(MethodHookParam param) {
+ Context mContext = (Context) XposedHelpers.getObjectField(param.thisObject, "mContext");
+ Object proc = param.args[0];
+ ApplicationErrorReport.CrashInfo crashInfo = (ApplicationErrorReport.CrashInfo) param.args[1];
+ String shortMsg = (String) param.args[2];
+ String longMsg = (String) param.args[3];
+ String stackTrace = (String) param.args[4];
+ long timeMillis = (long) param.args[5];
+ int callingPid = (int) param.args[6];
+ int callingUid = (int) param.args[7];
+ logE(TAG, "context: " + mContext + " pkg: " + mContext.getPackageName() + " proc: " + proc + " crash: " + crashInfo + " short: " + shortMsg
+ + " long: " + longMsg + " stack: " + stackTrace + " time: " + timeMillis + " pid: " + callingPid + " uid: " + callingUid);
+ recordCrash(mContext, proc, crashInfo, shortMsg, longMsg, stackTrace, timeMillis, callingPid, callingUid);
+ }
+ }
+ );
+ }
+
+ private void backgroundActivity(ClassLoader classLoader) {
+ // 允许哈皮露后台启动界面
+ try {
+ findAndHookMethod("com.android.server.wm.ActivityStarter", classLoader, "shouldAbortBackgroundActivityStart",
+ int.class, int.class, String.class, int.class, int.class,
+ "com.android.server.wm.WindowProcessController", "com.android.server.am.PendingIntentRecord",
+ boolean.class, Intent.class, ActivityOptions.class,
+ new MethodHook() {
+ @Override
+ protected void before(MethodHookParam param) {
+ String pkg = (String) param.args[2];
+ if (pkg == null) return;
+ if (ProjectApi.mAppModulePkg.equals(pkg)) {
+ param.setResult(false);
+ }
+ }
+ }
+ );
+ } catch (Throwable e) {
+ findAndHookMethod("com.android.server.wm.BackgroundActivityStartController", classLoader, "checkBackgroundActivityStart",
+ int.class, int.class, String.class, int.class, int.class,
+ "com.android.server.wm.WindowProcessController", "com.android.server.am.PendingIntentRecord",
+ "android.app.BackgroundStartPrivileges", Intent.class, ActivityOptions.class,
+ new MethodHook() {
+ @Override
+ protected void before(MethodHookParam param) {
+ String pkg = (String) param.args[2];
+ if (pkg == null) return;
+ if (ProjectApi.mAppModulePkg.equals(pkg)) {
+ param.setResult(1);
+ }
+ }
+ }
+ );
+ }
+ hookAllMethods("com.android.server.wm.ActivityStarterImpl", classLoader,
+ "isAllowedStartActivity",
+ new MethodHook() {
+ @Override
+ protected void before(MethodHookParam param) {
+ int count = -1;
+ for (Object clz : param.args) {
+ count = count + 1;
+ if (clz instanceof String) {
+ break;
+ }
+ }
+ String pkg = (String) param.args[count];
+ if (pkg == null) return;
+ if (ProjectApi.mAppModulePkg.equals(pkg)) {
+ param.setResult(true);
+ }
+ }
+ }
+ );
+ }
+
+ private void recordCrash(Context mContext, Object proc, ApplicationErrorReport.CrashInfo crashInfo,
+ String shortMsg, String longMsg, String stackTrace, long timeMillis, int pid, int uid) {
+ PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo info = (ApplicationInfo) XposedHelpers.getObjectField(proc, "info");
+ String pkg = info.packageName; // 包名
+ String label = info.loadLabel(pm).toString(); // 应用名
+ if (scopeMap.isEmpty()) scopeMap = CrashData.scopeData();
+ if (scopeMap.get(pkg) == null) {
+ return; // 不属于作用域范围
+ }
+ ArrayList arrayList = new ArrayList<>();
+ arrayList.add(new CrashRecord(label, pkg, timeMillis, 0).toJSON()); // 添加本次崩溃记录
+ ArrayList data = getCrashRecord(mContext);
+ if (!data.isEmpty()) {
+ boolean isNewCrash = false; // 此条崩溃记录是否是全新的
+ boolean needReplace = false; // 是否需要更新记录
+ boolean isReport = false; // 是否需要报告崩溃
+ boolean needRemoveList = false; // 是否更新列表
+ ArrayList removeList = new ArrayList<>();
+ int remove = -1;
+ JSONObject add = null;
+ for (int i = 0; i < data.size(); i++) {
+ remove = remove + 1;
+ JSONObject jsonObject = data.get(i); // 读取
+ long mTime = CrashRecord.getTime(jsonObject);
+ if ((timeMillis - mTime) > 60000) {
+ removeList.add(remove);
+ needRemoveList = true;
+ if (removeList.size() == data.size()) {
+ break;
+ }
+ continue;
+ }
+ if (compare(jsonObject, label, pkg)) { // 如果全部匹配代表已经在数据库中
+ isNewCrash = false; // 不需要再添加
+ if ((timeMillis - mTime) < 10240) {
+ needReplace = true;
+ int count = CrashRecord.getCount(jsonObject);
+ if (count >= 2) { // 崩溃报告临界值
+ ArrayList report = getReportCrash(mContext); // 获取已经存在的报告记录
+ ArrayList newReport = new ArrayList<>();
+ if (!report.isEmpty()) {
+ boolean isNewReport = false; // 是否是新的报告
+ for (JSONObject j : report) {
+ if (compare(j, label, pkg)) {
+ isNewReport = false; // 不是新的直接跳过
+ break;
+ }
+ isNewReport = true;
+ }
+ if (isNewReport) newReport.add(jsonObject); // 是新的则添加
+ } else {
+ newReport.add(jsonObject);
+ }
+ if (!newReport.isEmpty()) { // 非空则报告
+ try {
+ report.addAll(newReport);
+ reportCrash(mContext, report);
+ reportCrashByIntent(mContext, longMsg, stackTrace, newReport.get(0));
+ } catch (Throwable e) {
+ logE(TAG, "Report crash failed!" + e);
+ }
+ // logE(TAG, "new: " + report);
+ }
+ } else {
+ add = CrashRecord.putCount(jsonObject, count + 1); // 崩溃次数不足则累加
+ }
+ } else {
+ needReplace = true; // 超出时间则直接清楚本条记录
+ }
+ break;
+ } else {
+ if (isReport) break; // 是否已经报告过了
+ isNewCrash = true;
+ ArrayList report = getReportCrash(mContext);
+ if (!report.isEmpty()) {
+ for (JSONObject c : report) {
+ if (compare(c, label, pkg)) {
+ isReport = true; // 已经报告过了
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (needRemoveList) {
+ for (int i = removeList.size() - 1; i >= 0; i--) {
+ int id = removeList.get(i);
+ data.remove(id);
+ }
+ }
+ if (needReplace) {
+ if (!(removeList.contains(remove) && needRemoveList))
+ data.remove(remove);
+ if (add != null) data.add(add);
+ }
+ if (isNewCrash) {
+ if (!isReport)
+ data.addAll(arrayList);
+ }
+ // logE(TAG, "data: " + data);
+ setCrashRecord(mContext, data);
+ } else {
+ // logE(TAG, "arr: " + arrayList);
+ setCrashRecord(mContext, arrayList);
+ }
+ }
+
+ private boolean compare(JSONObject object, String label, String pkg) {
+ String mLabel = CrashRecord.getLabel(object);
+ String mPkg = CrashRecord.getPkg(object);
+ return mLabel.equals(label) && mPkg.equals(pkg);
+ }
+
+ private void reportCrashByIntent(Context context, String longMsg, String stackTrace, JSONObject report) {
+ if (scopeMap.isEmpty()) scopeMap = CrashData.scopeData();
+ String pkg = CrashRecord.getPkg(report);
+ String abbr = scopeMap.get(pkg);
+ Intent intent = new Intent();
+ intent.setAction("com.sevtinge.hyperceiler.crash.Service");
+ intent.setPackage("com.sevtinge.hyperceiler");
+ intent.putExtra("key_longMsg", longMsg);
+ intent.putExtra("key_stackTrace", stackTrace);
+ intent.putExtra("key_report", abbr);
+ context.startService(intent);
+ }
+
+ private void reportCrash(Context mContext, ArrayList data) {
+ Settings.System.putString(mContext.getContentResolver(), "hyperceiler_crash_report", data.toString());
+ }
+
+ private ArrayList getReportCrash(Context context) {
+ String data = Settings.System.getString(context.getContentResolver(), "hyperceiler_crash_report");
+ if (data == null || data.isEmpty() || data.equals("[]")) {
+ return new ArrayList<>();
+ }
+ return CrashRecord.toArray(data);
+ }
+
+ private void setCrashRecord(Context mContext, ArrayList data) {
+ Settings.System.putString(mContext.getContentResolver(), "hyperceiler_crash_record_data", data.toString());
+ }
+
+ private ArrayList getCrashRecord(Context mContext) {
+ String data = Settings.System.getString(mContext.getContentResolver(), "hyperceiler_crash_record_data");
+ if (data == null || data.isEmpty() || data.equals("[]")) {
+ return new ArrayList<>();
+ }
+ return CrashRecord.toArray(data);
+ }
+}
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashReportActivity.java b/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashReportActivity.java
new file mode 100644
index 0000000000..676a6b4920
--- /dev/null
+++ b/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashReportActivity.java
@@ -0,0 +1,84 @@
+package com.sevtinge.hyperceiler.safe;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.sevtinge.hyperceiler.R;
+import com.sevtinge.hyperceiler.utils.DialogHelper;
+import com.sevtinge.hyperceiler.utils.shell.ShellInit;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import moralnorm.appcompat.app.AlertDialog;
+import moralnorm.appcompat.app.AppCompatActivity;
+
+public class CrashReportActivity extends AppCompatActivity {
+
+ TextView mMessageTv;
+ TextView mCrashRecordTv;
+
+ private static HashMap swappedMap = CrashData.swappedData();
+
+ @SuppressLint("SetTextI18n")
+ @Override
+ public void onCreate(@Nullable Bundle bundle) {
+ super.onCreate(bundle);
+ ShellInit.init();
+ if (swappedMap.isEmpty()) swappedMap = CrashData.swappedData();
+ setContentView(R.layout.activity_crash_dialog);
+ Intent intent = getIntent();
+ String code = intent.getStringExtra("key_report");
+ String stackTrace = intent.getStringExtra("key_stackTrace");
+ String longMsg = intent.getStringExtra("key_longMsg");
+ String pkg = getReportCrashPkg(code);
+ View view = LayoutInflater.from(this).inflate(R.layout.crash_report_dialog, null);
+ mMessageTv = view.findViewById(R.id.tv_message);
+ mMessageTv.setText("作用域: " + "\n\"" + pkg + "\"\n已进入安全模式,点击确定解除,点击取消稍后处理。");
+ mCrashRecordTv = view.findViewById(R.id.tv_record);
+ mCrashRecordTv.getPaint().setFlags(Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);// 下划线并加清晰
+ mCrashRecordTv.getPaint().setAntiAlias(true);// 抗锯齿
+ mCrashRecordTv.setOnClickListener(v -> {
+ new AlertDialog.Builder(v.getContext())
+ .setCancelable(false)
+ .setTitle("异常记录")
+ .setMessage("异常信息: \n" + longMsg + "\n堆栈跟踪: \n" + stackTrace)
+ .setHapticFeedbackEnabled(true)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
+ .show();
+ // Toast.makeText(this, "查看异常记录", Toast.LENGTH_SHORT).show();
+ });
+ DialogHelper.showCrashReportDialog(this, view);
+ }
+
+ private String getReportCrashPkg(String data) {
+ if (data == null) return null;
+ String[] sp = data.split(",");
+ ArrayList report = new ArrayList<>(Arrays.asList(sp));
+ StringBuilder string = null;
+ for (String s : report) {
+ String mPkg = swappedMap.get(s);
+ if (mPkg != null) {
+ if (string == null) string = new StringBuilder(mPkg);
+ else
+ string.append("\n").append(mPkg);
+ }
+ }
+ if (string == null) return null;
+ return string.toString();
+ }
+
+ @Override
+ protected void onDestroy() {
+ ShellInit.destroy();
+ super.onDestroy();
+ }
+}
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashService.java b/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashService.java
new file mode 100644
index 0000000000..32635e49df
--- /dev/null
+++ b/app/src/main/java/com/sevtinge/hyperceiler/safe/CrashService.java
@@ -0,0 +1,50 @@
+package com.sevtinge.hyperceiler.safe;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+import com.sevtinge.hyperceiler.callback.ITAG;
+import com.sevtinge.hyperceiler.utils.log.AndroidLogUtils;
+import com.sevtinge.hyperceiler.utils.shell.ShellInit;
+
+public class CrashService extends Service {
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ ShellInit.init();
+ AndroidLogUtils.logI(ITAG.TAG, "service create");
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ AndroidLogUtils.logI(ITAG.TAG, "service onStartCommand");
+ String report = intent.getStringExtra("key_report");
+ String stackTrace = intent.getStringExtra("key_stackTrace");
+ String longMsg = intent.getStringExtra("key_longMsg");
+ ShellInit.getShell().run("setprop persist.hyperceiler.crash.report " + "\"" + report + "\"").sync();
+ Intent intent1 = new Intent(this, CrashReportActivity.class);
+ intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ intent1.putExtra("key_report", report);
+ intent1.putExtra("key_stackTrace", stackTrace);
+ intent1.putExtra("key_longMsg", longMsg);
+ startActivity(intent1);
+ stopSelf();
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ public void onDestroy() {
+ AndroidLogUtils.logI(ITAG.TAG, "service onDestroy");
+ ShellInit.destroy();
+ super.onDestroy();
+ }
+}
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/ui/MainActivity.java b/app/src/main/java/com/sevtinge/hyperceiler/ui/MainActivity.java
index 67d896dca7..05488a1f06 100644
--- a/app/src/main/java/com/sevtinge/hyperceiler/ui/MainActivity.java
+++ b/app/src/main/java/com/sevtinge/hyperceiler/ui/MainActivity.java
@@ -29,6 +29,7 @@
import com.sevtinge.hyperceiler.R;
import com.sevtinge.hyperceiler.callback.IResult;
import com.sevtinge.hyperceiler.prefs.PreferenceHeader;
+import com.sevtinge.hyperceiler.safe.CrashData;
import com.sevtinge.hyperceiler.ui.base.NavigationActivity;
import com.sevtinge.hyperceiler.utils.BackupUtils;
import com.sevtinge.hyperceiler.utils.Helpers;
@@ -40,12 +41,17 @@
import com.sevtinge.hyperceiler.utils.search.SearchHelper;
import com.sevtinge.hyperceiler.utils.shell.ShellInit;
+import java.util.ArrayList;
+import java.util.Arrays;
+
import moralnorm.appcompat.app.AlertDialog;
public class MainActivity extends NavigationActivity implements IResult {
private Handler handler;
private Context context;
+ private ArrayList appCrash = new ArrayList<>();
+
@Override
public void onCreate(Bundle savedInstanceState) {
SharedPreferences mPrefs = PrefsUtils.mSharedPreferences;
@@ -62,7 +68,20 @@ public void onCreate(Bundle savedInstanceState) {
ShellInit.init(this);
PropUtils.setProp("persist.hyperceiler.log.level",
(ProjectApi.isRelease() ? def : ProjectApi.isCanary() ? (def == 0 ? 3 : 4) : def));
- // test();
+ if (haveCrashReport()) {
+ new AlertDialog.Builder(this)
+ .setTitle("提示")
+ .setMessage("存在处于安全模式的作用域: \n" + appCrash.toString() + " \n请选择是否解除?")
+ .setHapticFeedbackEnabled(true)
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ ShellInit.getShell().run("setprop persist.hyperceiler.crash.report \"\"").sync();
+ ShellInit.getShell().run("settings put system hyperceiler_crash_report \"[]\"").sync();
+ dialog.dismiss();
+ })
+ .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
+ .show();
+ }
}
@Override
@@ -76,6 +95,31 @@ public void error(String reason) {
.show());
}
+ private boolean haveCrashReport() {
+ return !needIntercept().isEmpty();
+ }
+
+ private ArrayList needIntercept() {
+ ArrayList report = getReportCrashProp();
+ for (String s : report) {
+ String mPkg = CrashData.swappedData().get(s);
+ if (mPkg != null) {
+ appCrash.add(mPkg);
+ }
+ }
+ return appCrash;
+ }
+
+ private ArrayList getReportCrashProp() {
+ String data = PropUtils.getProp("persist.hyperceiler.crash.report", "");
+ if (data.isEmpty()) {
+ return new ArrayList<>();
+ }
+ String[] sp = data.split(",");
+ return new ArrayList<>(Arrays.asList(sp));
+ }
+
+
@Override
protected void onDestroy() {
ShellInit.destroy();
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/utils/DialogHelper.java b/app/src/main/java/com/sevtinge/hyperceiler/utils/DialogHelper.java
index a6f7b9b43d..0df18a9ec9 100644
--- a/app/src/main/java/com/sevtinge/hyperceiler/utils/DialogHelper.java
+++ b/app/src/main/java/com/sevtinge/hyperceiler/utils/DialogHelper.java
@@ -1,28 +1,31 @@
/*
- * This file is part of HyperCeiler.
+ * This file is part of HyperCeiler.
- * HyperCeiler is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License.
+ * HyperCeiler is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
- * Copyright (C) 2023-2024 HyperCeiler Contributions
-*/
+ * Copyright (C) 2023-2024 HyperCeiler Contributions
+ */
package com.sevtinge.hyperceiler.utils;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
+import android.view.View;
import com.sevtinge.hyperceiler.R;
+import com.sevtinge.hyperceiler.utils.shell.ShellExec;
+import com.sevtinge.hyperceiler.utils.shell.ShellInit;
import com.sevtinge.hyperceiler.view.RestartAlertDialog;
import moralnorm.appcompat.app.AlertDialog;
@@ -35,44 +38,59 @@ public static void showDialog(Activity activity, String title, String message) {
public static void showDialog(Activity activity, String title, String message, DialogInterface.OnClickListener onClickListener) {
new AlertDialog.Builder(activity)
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(android.R.string.ok, onClickListener)
- .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
- .show();
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, onClickListener)
+ .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
+ .show();
}
public static void showDialog(Activity activity, int title, int message, DialogInterface.OnClickListener onClickListener) {
new AlertDialog.Builder(activity)
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(android.R.string.ok, onClickListener)
- .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
- .show();
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, onClickListener)
+ .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss())
+ .show();
}
public static void showPositiveButtonDialog(Activity activity, String title, String message, DialogInterface.OnClickListener onClickListener) {
new AlertDialog.Builder(activity)
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(android.R.string.ok, onClickListener)
- .show();
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, onClickListener)
+ .show();
}
public static void showXposedActivateDialog(Context context) {
new AlertDialog.Builder(context)
- .setCancelable(false)
- .setTitle(R.string.tip)
- .setMessage(R.string.hook_failed)
- .setHapticFeedbackEnabled(true)
- .setPositiveButton(R.string.exit, (dialogInterface, i) -> System.exit(0))
- .setNegativeButton(R.string.ignore, null)
- .show();
+ .setCancelable(false)
+ .setTitle(R.string.tip)
+ .setMessage(R.string.hook_failed)
+ .setHapticFeedbackEnabled(true)
+ .setPositiveButton(R.string.exit, (dialogInterface, i) -> System.exit(0))
+ .setNegativeButton(R.string.ignore, null)
+ .show();
}
public static void showRestartDialog(Context context) {
new RestartAlertDialog(context).show();
}
+ public static void showCrashReportDialog(Activity activity, View view) {
+ new AlertDialog.Builder(activity)
+ .setCancelable(false)
+ .setTitle("警告")
+ .setView(view)
+ .setHapticFeedbackEnabled(true)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ ShellExec shellExec = ShellInit.getShell();
+ shellExec.run("setprop persist.hyperceiler.crash.report \"\"").sync();
+ shellExec.run("settings put system hyperceiler_crash_report \"[]\"").sync();
+ activity.finish();
+ })
+ .setNegativeButton(android.R.string.cancel, (dialog, which) -> activity.finish())
+ .show();
+ }
}
diff --git a/app/src/main/java/com/sevtinge/hyperceiler/utils/shell/ShellUtils.java b/app/src/main/java/com/sevtinge/hyperceiler/utils/shell/ShellUtils.java
index 1eb591b7a9..53b8593a3d 100644
--- a/app/src/main/java/com/sevtinge/hyperceiler/utils/shell/ShellUtils.java
+++ b/app/src/main/java/com/sevtinge/hyperceiler/utils/shell/ShellUtils.java
@@ -215,7 +215,7 @@ public static CommandResult execCommand(String[] commands, boolean isRoot, boole
}
}
return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null
- : errorMsg.toString());
+ : errorMsg.toString());
}
/**
diff --git a/app/src/main/res/layout/activity_crash_dialog.xml b/app/src/main/res/layout/activity_crash_dialog.xml
new file mode 100644
index 0000000000..98f2dc5b2d
--- /dev/null
+++ b/app/src/main/res/layout/activity_crash_dialog.xml
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/crash_report_dialog.xml b/app/src/main/res/layout/crash_report_dialog.xml
new file mode 100644
index 0000000000..4a0d1ae864
--- /dev/null
+++ b/app/src/main/res/layout/crash_report_dialog.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 888370e7cb..60cedd3634 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -7,4 +7,18 @@
- true
- true
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index d1a837d81b..a17c57b90c 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -10,6 +10,7 @@
#1A000000
#FF000000
#FFFFFFFF
+ #50000000
#80000000
#ccffffff
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index da1b1d973a..d2415e28ab 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,6 +1,7 @@
HyperCeiler
+
NFC
VPN
GPS
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index b904c555e7..aa5883fb1e 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,4 +1,12 @@
+
+
+
+
+
-
-
+
+
+
+
+
+
+