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 @@ + + + - - + + + + + +