├── miuistub ├── .gitignore ├── src │ └── main │ │ └── java │ │ ├── miui │ │ └── os │ │ │ └── Build.java │ │ ├── android │ │ ├── app │ │ │ └── ActivityManagerHidden.java │ │ ├── os │ │ │ ├── SystemProperties.java │ │ │ └── UserHandleHidden.java │ │ └── content │ │ │ └── ContextHidden.java │ │ └── miuix │ │ └── preference │ │ └── TextPreference.java └── build.gradle.kts ├── app ├── .gitignore ├── src │ └── main │ │ ├── assets │ │ └── xposed_init │ │ ├── test-playstore.png │ │ ├── res │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── arrays.xml │ │ │ ├── colors.xml │ │ │ ├── themes.xml │ │ │ └── strings.xml │ │ ├── mipmap-anydpi │ │ │ └── ic_launcher.xml │ │ ├── layout │ │ │ └── settings_activity.xml │ │ ├── xml │ │ │ ├── warn_preference.xml │ │ │ ├── header_preferences.xml │ │ │ └── root_preferences.xml │ │ ├── values-night │ │ │ └── themes.xml │ │ ├── drawable │ │ │ └── ic_launcher_foreground.xml │ │ ├── values-zh-rCN │ │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ │ └── strings.xml │ │ └── values-ru-rRU │ │ │ └── strings.xml │ │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── chsbuffer │ │ │ └── miuihelper │ │ │ ├── hooks │ │ │ ├── screenshot │ │ │ │ └── SaveToPictures.kt │ │ │ ├── systemui │ │ │ │ ├── NotificationSettingsNoWhiteList.kt │ │ │ │ ├── RestoreCnQuickAccessWalletTile.kt │ │ │ │ ├── RestoreCnNearby.kt │ │ │ │ └── NotificationClickInfoItemStartChannelSetting.kt │ │ │ ├── securitycenter │ │ │ │ ├── EnabledAllTextView.kt │ │ │ │ ├── IntlEnableBehaviorRecord.kt │ │ │ │ ├── RemoveSetSystemAppWifiRuleAllow.kt │ │ │ │ ├── LockOneHundred.kt │ │ │ │ ├── RemoveBehaviorRecordWhiteListAndNoIgnoreSystemApp.kt │ │ │ │ ├── AppDetailsSystemAppWlanControl.kt │ │ │ │ ├── DexKitCache.kt │ │ │ │ └── AppDetailsStockOpenDefaultSettings.kt │ │ │ ├── screenrecorder │ │ │ │ ├── ForceSupportPlaybackCapture.kt │ │ │ │ └── SaveToMovies.kt │ │ │ ├── updater │ │ │ │ └── RemoveOTAValidate.kt │ │ │ ├── home │ │ │ │ ├── RestoreCnBuildGoogleApp.kt │ │ │ │ ├── RestoreGoogleSearch.kt │ │ │ │ └── RestoreSwitchMinusScreen.kt │ │ │ ├── gallery │ │ │ │ └── FixAlbum.kt │ │ │ ├── aiasst │ │ │ │ └── SupportAiSubtitles.kt │ │ │ └── lbe │ │ │ │ └── SuggestPermissions.kt │ │ │ ├── model │ │ │ ├── ScopedHook.kt │ │ │ ├── Hook.kt │ │ │ └── BooleanDuringMethod.kt │ │ │ ├── util │ │ │ └── XposedUtil.kt │ │ │ ├── SettingsActivity.kt │ │ │ └── MainHook.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle.kts ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .gitignore ├── settings.gradle.kts ├── README.md ├── gradle.properties ├── gradlew.bat └── gradlew /miuistub/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | io.github.chsbuffer.miuihelper.MainHook -------------------------------------------------------------------------------- /app/src/main/test-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsbuffer/MIUIQOL/HEAD/app/src/main/test-playstore.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsbuffer/MIUIQOL/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | .kotlin -------------------------------------------------------------------------------- /miuistub/src/main/java/miui/os/Build.java: -------------------------------------------------------------------------------- 1 | package miui.os; 2 | 3 | public class Build extends android.os.Build { 4 | public static boolean IS_INTERNATIONAL_BUILD; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF6A00 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /miuistub/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | } 4 | 5 | android { 6 | namespace = "miui.os" 7 | } 8 | 9 | dependencies { 10 | annotationProcessor(libs.refine.annotation.processor) 11 | compileOnly(libs.refine.annotation) 12 | compileOnly(libs.androidx.preference) 13 | } -------------------------------------------------------------------------------- /miuistub/src/main/java/android/app/ActivityManagerHidden.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import dev.rikka.tools.refine.RefineAs; 4 | 5 | @RefineAs(ActivityManager.class) 6 | public class ActivityManagerHidden { 7 | public static int getCurrentUser() { 8 | throw new RuntimeException("Stub!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/settings_activity.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | -------------------------------------------------------------------------------- /miuistub/src/main/java/android/os/SystemProperties.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class SystemProperties { 4 | private SystemProperties() { 5 | 6 | } 7 | 8 | public static String get(String key) { 9 | throw new RuntimeException("Stub!"); 10 | } 11 | 12 | public static int getInt(String string, int i) { 13 | throw new RuntimeException("Stub!"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/warn_preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /miuistub/src/main/java/android/content/ContextHidden.java: -------------------------------------------------------------------------------- 1 | package android.content; 2 | 3 | import android.os.UserHandle; 4 | 5 | import dev.rikka.tools.refine.RefineAs; 6 | 7 | @RefineAs(Context.class) 8 | public class ContextHidden { 9 | public void startActivityAsUser(Intent intent, 10 | UserHandle user) { 11 | throw new RuntimeException("Stub!"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.android.updater 5 | com.miui.securitycenter 6 | com.miui.screenrecorder 7 | com.miui.screenshot 8 | com.miui.home 9 | com.xiaomi.aiasst.vision 10 | com.android.systemui 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #FF6A00 11 | -------------------------------------------------------------------------------- /miuistub/src/main/java/android/os/UserHandleHidden.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | import dev.rikka.tools.refine.RefineAs; 4 | 5 | @RefineAs(UserHandle.class) 6 | public class UserHandleHidden { 7 | public static final int USER_ALL = -1; 8 | public static final int USER_CURRENT = -2; 9 | 10 | public static final UserHandle CURRENT = null /*new UserHandle(USER_CURRENT) */; 11 | 12 | public static UserHandle of(int userId) { 13 | throw new RuntimeException("Stub!"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/screenshot/SaveToPictures.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.screenshot 2 | 3 | import de.robv.android.xposed.XposedHelpers 4 | import io.github.chsbuffer.miuihelper.model.Hook 5 | 6 | object SaveToPictures: Hook() { 7 | override fun init() { 8 | if (!xPrefs.getBoolean("save_to_pictures", true)) 9 | return 10 | val clazz = XposedHelpers.findClass("android.os.Environment", classLoader) 11 | XposedHelpers.setStaticObjectField(clazz, "DIRECTORY_DCIM", "Pictures") 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/model/ScopedHook.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.model 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedBridge 5 | import java.lang.reflect.Member 6 | 7 | 8 | class ScopedHook(val hookMethod: Member, val callback: XC_MethodHook) : XC_MethodHook() { 9 | lateinit var Unhook: Unhook 10 | override fun beforeHookedMethod(param: MethodHookParam) { 11 | Unhook = XposedBridge.hookMethod(hookMethod, callback) 12 | } 13 | 14 | override fun afterHookedMethod(param: MethodHookParam) { 15 | Unhook.unhook() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /miuistub/src/main/java/miuix/preference/TextPreference.java: -------------------------------------------------------------------------------- 1 | package miuix.preference; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | import androidx.preference.Preference; 9 | 10 | public class TextPreference extends Preference { 11 | public TextPreference(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 12 | super(context, attrs, defStyleAttr, defStyleRes); 13 | } 14 | 15 | public void setText(String str) { 16 | throw new RuntimeException("Stub!"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/header_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | maven("https://api.xposed.info/") { 20 | content { 21 | includeGroup("de.robv.android.xposed") 22 | } 23 | } 24 | } 25 | } 26 | 27 | rootProject.name = "MIUIHelper" 28 | include(":app", ":miuistub") -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/model/Hook.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.model 2 | 3 | import de.robv.android.xposed.XSharedPreferences 4 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 5 | import io.github.chsbuffer.miuihelper.BuildConfig 6 | 7 | abstract class Hook { 8 | companion object { 9 | val xPrefs = XSharedPreferences( 10 | BuildConfig.APPLICATION_ID, 11 | "prefs" 12 | ) 13 | } 14 | 15 | protected lateinit var lpparam: LoadPackageParam 16 | protected lateinit var classLoader: ClassLoader 17 | 18 | fun init(lpparam: LoadPackageParam) { 19 | this.lpparam = lpparam 20 | this.classLoader = lpparam.classLoader 21 | 22 | init() 23 | } 24 | 25 | open fun init() { 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/systemui/NotificationSettingsNoWhiteList.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.systemui 2 | 3 | import de.robv.android.xposed.XposedHelpers 4 | import io.github.chsbuffer.miuihelper.model.Hook 5 | import miui.os.Build 6 | 7 | object NotificationSettingsNoWhiteList : Hook() { 8 | override fun init() { 9 | if (!xPrefs.getBoolean( 10 | "notification_settings_no_white_list", 11 | false 12 | ) || Build.IS_INTERNATIONAL_BUILD 13 | ) return 14 | 15 | XposedHelpers.setStaticBooleanField( 16 | XposedHelpers.findClass( 17 | "com.android.systemui.statusbar.notification.NotificationSettingsManager", 18 | classLoader 19 | ), "USE_WHITE_LISTS", false 20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/securitycenter/EnabledAllTextView.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.securitycenter 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedHelpers 5 | import io.github.chsbuffer.miuihelper.model.Hook 6 | 7 | object EnabledAllTextView : Hook() { 8 | override fun init() { 9 | if (!xPrefs.getBoolean("enable_all_text_view", true)) 10 | return 11 | // this exposed lots of disabled settings such as set system app wlan restrict 12 | 13 | XposedHelpers.findAndHookMethod( 14 | "android.widget.TextView", 15 | classLoader, 16 | "setEnabled", 17 | Boolean::class.javaPrimitiveType, 18 | object : XC_MethodHook() { 19 | override fun beforeHookedMethod(param: MethodHookParam) { 20 | param.args[0] = true 21 | } 22 | }) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/systemui/RestoreCnQuickAccessWalletTile.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.systemui 2 | 3 | import de.robv.android.xposed.XposedHelpers 4 | import io.github.chsbuffer.miuihelper.model.BooleanDuringMethod 5 | import io.github.chsbuffer.miuihelper.model.Hook 6 | import miui.os.Build 7 | 8 | 9 | object RestoreCnQuickAccessWalletTile : Hook() { 10 | override fun init() { 11 | if (!xPrefs.getBoolean("restore_wallet_tile", true)) return 12 | 13 | if (Build.IS_INTERNATIONAL_BUILD) return 14 | 15 | XposedHelpers.findAndHookMethod( 16 | "com.android.systemui.qs.tiles.QuickAccessWalletTile", 17 | classLoader, 18 | "isAvailable", 19 | BooleanDuringMethod( 20 | XposedHelpers.findClass( 21 | "miui.os.Build", classLoader 22 | ), "IS_INTERNATIONAL_BUILD", true 23 | ) 24 | ) 25 | } 26 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | -dontobfuscate 16 | -optimizations 17 | -keep class io.github.chsbuffer.miuihelper.MainHook { 18 | void handleLoadPackage(...); 19 | } 20 | 21 | # Uncomment this to preserve the line number information for 22 | # debugging stack traces. 23 | #-keepattributes SourceFile,LineNumberTable 24 | 25 | # If you keep the line number information, uncomment this to 26 | # hide the original source file name. 27 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/screenrecorder/ForceSupportPlaybackCapture.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.screenrecorder 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedHelpers 5 | import io.github.chsbuffer.miuihelper.model.Hook 6 | 7 | object ForceSupportPlaybackCapture : Hook() { 8 | override fun init() { 9 | if (!xPrefs.getBoolean("force_support_playbackcapture", true)) return 10 | 11 | XposedHelpers.findAndHookMethod("android.os.SystemProperties", 12 | classLoader, 13 | "getBoolean", 14 | String::class.java, 15 | Boolean::class.javaPrimitiveType, 16 | object : XC_MethodHook() { 17 | override fun beforeHookedMethod(param: MethodHookParam) { 18 | if (param.args[0] == "ro.vendor.audio.playbackcapture.screen") 19 | param.result = true 20 | } 21 | }) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/updater/RemoveOTAValidate.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.updater 2 | 3 | import de.robv.android.xposed.XC_MethodReplacement 4 | import de.robv.android.xposed.XposedBridge 5 | import io.github.chsbuffer.miuihelper.model.Hook 6 | import org.luckypray.dexkit.DexKitBridge 7 | import org.luckypray.dexkit.query.enums.StringMatchType 8 | 9 | class RemoveOTAValidate(val dexKit: DexKitBridge) : Hook() { 10 | 11 | override fun init() { 12 | if (!xPrefs.getBoolean("remove_ota_validate", false)) 13 | return 14 | 15 | val m = dexKit.findMethod { 16 | matcher { 17 | addUsingString("support_ota_validate", StringMatchType.Equals) 18 | } 19 | findFirst = true 20 | }.single().getMethodInstance(classLoader) 21 | 22 | XposedBridge.hookMethod(m, XC_MethodReplacement.returnConstant(false)) 23 | 24 | /* 25 | return FeatureParser.getBoolean("support_ota_validate", false); 26 | */ 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/screenrecorder/SaveToMovies.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.screenrecorder 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedHelpers 5 | import io.github.chsbuffer.miuihelper.model.Hook 6 | 7 | object SaveToMovies : Hook() { 8 | @Suppress("UNCHECKED_CAST") 9 | override fun init() { 10 | if (!xPrefs.getBoolean("save_to_movies", true)) return 11 | 12 | /**/ 13 | val clazz = XposedHelpers.findClass("android.os.Environment", classLoader) 14 | XposedHelpers.setStaticObjectField(clazz, "DIRECTORY_DCIM", "Movies") 15 | 16 | /**/ 17 | XposedHelpers.findAndHookMethod("android.content.ContentValues", 18 | classLoader, 19 | "put", 20 | String::class.java, 21 | String::class.java, 22 | object : XC_MethodHook() { 23 | override fun beforeHookedMethod(param: MethodHookParam) { 24 | if (param.args[0] == "relative_path") { 25 | param.args[1] = (param.args[1] as String).replace("DCIM", "Movies") 26 | } 27 | } 28 | }) 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/securitycenter/IntlEnableBehaviorRecord.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.securitycenter 2 | 3 | import de.robv.android.xposed.XposedBridge 4 | import de.robv.android.xposed.XposedHelpers 5 | import io.github.chsbuffer.miuihelper.BuildConfig 6 | import io.github.chsbuffer.miuihelper.model.BooleanDuringMethod 7 | import io.github.chsbuffer.miuihelper.model.Hook 8 | import miui.os.Build 9 | 10 | class IntlEnableBehaviorRecord(val dexKitCache: DexKitCache) : Hook() { 11 | override fun init() { 12 | if ((!xPrefs.getBoolean("behavior_record_enhance", true) 13 | || !Build.IS_INTERNATIONAL_BUILD) 14 | && !BuildConfig.DEBUG 15 | ) return 16 | 17 | val spoofCN = BooleanDuringMethod( 18 | XposedHelpers.findClass( 19 | "miui.os.Build", classLoader 20 | ), "IS_INTERNATIONAL_BUILD", false 21 | ) 22 | 23 | dexKitCache.behavior_shouldIgnore.declaredClass!!.findMethod { 24 | matcher { 25 | addUsingField("Lmiui/os/Build;->IS_INTERNATIONAL_BUILD:Z") 26 | } 27 | }.forEach { 28 | XposedBridge.hookMethod(it.getMethodInstance(classLoader), spoofCN) 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.7.0" 3 | kotlin = "2.0.20" 4 | annotation = "1.8.2" 5 | xposed-api = "82" 6 | dexkit = "2.0.2" 7 | preference = "1.2.1" 8 | refine = "4.4.0" 9 | 10 | [libraries] 11 | androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } 12 | androidx-preference = { module = "androidx.preference:preference", version.ref = "preference" } 13 | refine-annotation = { module = "dev.rikka.tools.refine:annotation", version.ref = "refine" } 14 | refine-annotation-processor = { module = "dev.rikka.tools.refine:annotation-processor", version.ref = "refine" } 15 | refine-runtime = { module = "dev.rikka.tools.refine:runtime", version.ref = "refine" } 16 | xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposed-api" } 17 | dexkit = { module = "org.luckypray:dexkit", version.ref = "dexkit" } 18 | kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } 19 | 20 | [plugins] 21 | android-application = { id = "com.android.application", version.ref = "agp" } 22 | android-library = { id = "com.android.library", version.ref = "agp" } 23 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 24 | refine = { id = "dev.rikka.tools.refine", version.ref = "refine" } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 15 | 18 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/home/RestoreCnBuildGoogleApp.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.home 2 | 3 | import android.content.ComponentName 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedHelpers 6 | import io.github.chsbuffer.miuihelper.model.Hook 7 | import miui.os.Build 8 | 9 | object RestoreCnBuildGoogleApp : Hook() { 10 | override fun init() { 11 | if (!xPrefs.getBoolean("restore_google_icon", true) || Build.IS_INTERNATIONAL_BUILD) 12 | return 13 | 14 | XposedHelpers.findAndHookConstructor( 15 | "com.miui.home.launcher.AppFilter", 16 | classLoader, 17 | object : XC_MethodHook() { 18 | override fun afterHookedMethod(param: MethodHookParam) { 19 | val skippedItem = XposedHelpers.getObjectField( 20 | param.thisObject, 21 | "mSkippedItems" 22 | ) as HashSet 23 | 24 | skippedItem.removeIf { 25 | it.packageName == "com.google.android.googlequicksearchbox" 26 | || it.packageName == "com.google.android.gms" 27 | } 28 | } 29 | } 30 | ) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/gallery/FixAlbum.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.gallery 2 | 3 | import android.os.Environment 4 | import de.robv.android.xposed.XC_MethodReplacement 5 | import de.robv.android.xposed.XposedHelpers 6 | import io.github.chsbuffer.miuihelper.model.Hook 7 | 8 | // DO_NOT_USE_OR_YOU_WILL_BE_FIRED 9 | object FixAlbum : Hook() { 10 | override fun init() { 11 | XposedHelpers.findAndHookMethod( 12 | "com.miui.gallery.model.dto.Album", 13 | classLoader, 14 | "isMustVisibleAlbum", 15 | String::class.java, 16 | XC_MethodReplacement.returnConstant(true) 17 | ) 18 | 19 | val saveToPictures = xPrefs.getBoolean("save_to_pictures", true) 20 | val saveToMovies = xPrefs.getBoolean("save_to_movies", true) 21 | val constantClazz = 22 | XposedHelpers.findClass( 23 | "com.miui.gallery.storage.constants.MIUIStorageConstants", 24 | classLoader 25 | ) 26 | if (saveToPictures) 27 | XposedHelpers.setStaticObjectField( 28 | constantClazz, "DIRECTORY_SCREENSHOT_PATH", 29 | Environment.DIRECTORY_PICTURES + "/Screenshots" 30 | ) 31 | 32 | if (saveToMovies) 33 | XposedHelpers.setStaticObjectField( 34 | constantClazz, "DIRECTORY_SCREENRECORDER_PATH", 35 | Environment.DIRECTORY_MOVIES + "/screenrecorder" 36 | ) 37 | } 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

MIUI 生活质量提升

3 |

4 | English 丨 简体中文 5 |

6 | Telegram 群组 7 |
8 | 9 | ## 模块说明 10 | 11 | 此模块改善了我在 MIUI 12~14 以及 HyperOS 的日常使用体验,功能的详细描述可在模块设置内查看。 12 | 13 | ⭐ 本模块自豪地使用了 [DexKit](https://luckypray.org/DexKit/zh-cn/),一个高性能运行时 DEX 解析库。 14 | 15 | ## 模块功能 16 | 17 | - 手机管家 18 | - 应用行为记录显示系统应用(HyperOS 不再需要) 19 | - 系统应用 WLAN 联网控制 20 | - 将应用详情中“消除默认操作”改为“默认打开”设置 21 | - 无视“启用危险选项警告”倒计时直接确认 22 | - 体检分数锁定100分 23 | - 系统桌面 24 | - 不隐藏 Google 桌面图标¹ 25 | - 强制启用负一屏选择器(可选择 Google Feed)¹ 26 | - 替换全局搜索为 Google 搜索¹ 27 | - 系统界面 28 | - 不隐藏附近分享磁贴² 29 | - 不隐藏谷歌钱包磁贴² 30 | - 去除通知设置白名单 31 | - 通知更多设置重定向至通知渠道设置 32 | - 截图 33 | - 截图保存到 `Pictures/Screenshots` 34 | - 屏幕录制 35 | - 视频保存到 `Movies/ScreenRecorder` 36 | - 强制启用原生录音支持 37 | - 小爱翻译 38 | - 启用 AI字幕(只能识别翻译中文、英文) 39 | - 系统更新 40 | - 移除OTA验证(同 WooBox for MIUI 移除 OTA 验证) 41 | 42 | 注: 43 | 44 | 1. 需要 [Google 应用](https://play.google.com/store/apps/details?id=com.google.android.googlequicksearchbox) 45 | 2. 在国行系统上,部分谷歌移动服务功能被停用。要启用这些功能,需要对系统文件进行修改。更多信息请参阅 Unlock CN GMS 面具模块:[https://github.com/fei-ke/unlock-cn-gms](https://github.com/fei-ke/unlock-cn-gms) 46 | 47 | ## 社区 48 | 49 | 交流请通过 GitHub Issues 或 [Telegram 群组](https://t.me/miuiqol)。 50 | 51 | 贡献翻译可以通过 [Crowdin](https://crwd.in/miuiqol)。 52 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/aiasst/SupportAiSubtitles.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.aiasst 2 | 3 | import android.content.Context 4 | import de.robv.android.xposed.XC_MethodReplacement 5 | import de.robv.android.xposed.XposedHelpers 6 | import io.github.chsbuffer.miuihelper.model.Hook 7 | 8 | 9 | object SupportAiSubtitles : Hook() { 10 | override fun init() { 11 | if (!xPrefs.getBoolean("support_ai_subtitles", true)) 12 | return 13 | val clazz = XposedHelpers.findClass( 14 | "com.xiaomi.aiasst.vision.utils.SupportAiSubtitlesUtils", 15 | classLoader 16 | ) 17 | XposedHelpers.findAndHookMethod( 18 | clazz, 19 | "isSupportAiSubtitles", 20 | Context::class.java, 21 | XC_MethodReplacement.returnConstant(true) 22 | ) 23 | XposedHelpers.findAndHookMethod( 24 | clazz, 25 | "isSupportJapanKoreaTranslation", 26 | Context::class.java, 27 | XC_MethodReplacement.returnConstant(true) 28 | ) 29 | runCatching { 30 | XposedHelpers.findAndHookMethod( 31 | clazz, 32 | "deviceWhetherSupportOfflineSubtitles", 33 | XC_MethodReplacement.returnConstant(true) 34 | ) 35 | XposedHelpers.findAndHookMethod( 36 | clazz, 37 | "isSupportOfflineAiSubtitles", 38 | android.content.Context::class.java, 39 | XC_MethodReplacement.returnConstant(true) 40 | ) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | org.gradle.parallel=true 14 | # https://docs.gradle.org/current/userguide/performance.html 15 | org.gradle.configuration-cache=true 16 | org.gradle.caching=true 17 | # AndroidX package structure to make it clearer which packages are bundled with the 18 | # Android operating system, and which are packaged with your app"s APK 19 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 20 | android.useAndroidX=true 21 | # Automatically convert third-party libraries to use AndroidX 22 | android.enableJetifier=false 23 | # Kotlin code style for this project: "official" or "obsolete": 24 | kotlin.code.style=official 25 | # Enables namespacing of each library's R class so that its R class includes only the 26 | # resources declared in the library itself and none from the library's dependencies, 27 | # thereby reducing the size of the R class for that library 28 | android.nonTransitiveRClass=true 29 | # 30 | android.enableNewResourceShrinker.preciseShrinking=true 31 | android.enableAppCompileTimeRClass=true 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 10 | 16 | 19 | 22 | 23 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/securitycenter/RemoveSetSystemAppWifiRuleAllow.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.securitycenter 2 | 3 | import android.util.ArrayMap 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XC_MethodReplacement 6 | import de.robv.android.xposed.XposedHelpers 7 | import io.github.chsbuffer.miuihelper.model.Hook 8 | 9 | 10 | object RemoveSetSystemAppWifiRuleAllow : Hook() { 11 | override fun init() { 12 | if(!xPrefs.getBoolean("system_app_wlan_control",true)) 13 | return 14 | 15 | XposedHelpers.findAndHookMethod( 16 | "com.miui.networkassistant.service.FirewallService", 17 | classLoader, 18 | "setSystemAppWifiRuleAllow", 19 | ArrayMap::class.java, 20 | XC_MethodReplacement.returnConstant(null) 21 | ) 22 | XposedHelpers.findAndHookMethod( 23 | "com.miui.networkassistant.ui.fragment.ShowAppDetailFragment", 24 | classLoader, 25 | "initFirewallData", 26 | object : XC_MethodHook() { 27 | private var mAppInfo: Any? = null 28 | private var isSystemApp = false 29 | override fun beforeHookedMethod(param: MethodHookParam) { 30 | val obj = param.thisObject 31 | mAppInfo = XposedHelpers.getObjectField(obj, "mAppInfo") 32 | isSystemApp = XposedHelpers.getBooleanField(mAppInfo, "isSystemApp") 33 | XposedHelpers.setBooleanField(mAppInfo, "isSystemApp", false) 34 | } 35 | 36 | override fun afterHookedMethod(param: MethodHookParam) { 37 | XposedHelpers.setBooleanField(mAppInfo, "isSystemApp", isSystemApp) 38 | mAppInfo = null 39 | } 40 | }) 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/securitycenter/LockOneHundred.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.securitycenter 2 | 3 | import android.view.View 4 | import de.robv.android.xposed.XC_MethodReplacement 5 | import de.robv.android.xposed.XposedBridge 6 | import de.robv.android.xposed.XposedHelpers 7 | import io.github.chsbuffer.miuihelper.model.Hook 8 | import io.github.chsbuffer.miuihelper.util.logSearch 9 | import org.luckypray.dexkit.DexKitBridge 10 | import org.luckypray.dexkit.query.enums.StringMatchType 11 | 12 | 13 | class LockOneHundred(val dexKit: DexKitBridge) : Hook() { 14 | override fun init() { 15 | if (!xPrefs.getBoolean("lock_one_hundred", true)) return 16 | //防止点击重新检测 17 | XposedHelpers.findAndHookMethod( 18 | "com.miui.securityscan.ui.main.MainContentFrame", 19 | classLoader, 20 | "onClick", 21 | View::class.java, 22 | XC_MethodReplacement.returnConstant(null) 23 | ) 24 | //锁定100分 25 | /* 26 | public int p() { 27 | int n10 = n(); 28 | if (n10 > 100) { 29 | n10 = 100; 30 | } else if (n10 < 0) { 31 | n10 = 0; 32 | } 33 | return 100 - n10; 34 | } 35 | private int n() { 36 | if (f16651p) { 37 | Log.d("ScoreManager", "getMinusPredictScore------------------------------------------------ "); 38 | } 39 | ... 40 | */ 41 | val minusScoreMethod = logSearch("securityScan_getMinusPredictScore", { it.descriptor }) { 42 | dexKit.getClassData("com.miui.securityscan.scanner.ScoreManager")!!.findMethod { 43 | matcher { 44 | addUsingString("getMinusPredictScore", StringMatchType.Contains) 45 | } 46 | findFirst = true 47 | }.single() 48 | } 49 | 50 | XposedBridge.hookMethod( 51 | minusScoreMethod.getMethodInstance(classLoader), XC_MethodReplacement.returnConstant(0) 52 | ) 53 | } 54 | } -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.refine) 5 | } 6 | 7 | android { 8 | defaultConfig { 9 | applicationId = "io.github.chsbuffer.miuihelper" 10 | versionCode = 19 11 | versionName = "1.1.17" 12 | ndk { 13 | //noinspection ChromeOsAbiSupport 14 | abiFilters.add("arm64-v8a") 15 | } 16 | } 17 | buildTypes { 18 | debug { 19 | applicationIdSuffix = ".debug" 20 | isDebuggable = true 21 | // isMinifyEnabled = true 22 | // isShrinkResources = true 23 | proguardFiles( 24 | getDefaultProguardFile("proguard-android-optimize.txt"), 25 | "proguard-rules.pro" 26 | ) 27 | } 28 | release { 29 | isMinifyEnabled = true 30 | isShrinkResources = true 31 | proguardFiles( 32 | getDefaultProguardFile("proguard-android-optimize.txt"), 33 | "proguard-rules.pro" 34 | ) 35 | // signingConfig = signingConfigs["debug"] 36 | } 37 | } 38 | kotlinOptions { 39 | jvmTarget = "11" 40 | compileOptions { 41 | freeCompilerArgs += listOf( 42 | "-Xno-param-assertions", 43 | "-Xno-receiver-assertions", 44 | "-Xno-call-assertions" 45 | ) 46 | } 47 | } 48 | buildFeatures { 49 | viewBinding = true 50 | buildConfig = true 51 | } 52 | namespace = "io.github.chsbuffer.miuihelper" 53 | } 54 | 55 | dependencies { 56 | //noinspection KtxExtensionAvailable 57 | compileOnly(libs.androidx.preference) 58 | implementation(libs.dexkit) 59 | implementation(libs.androidx.annotation) 60 | compileOnly(libs.xposed.api) 61 | compileOnly(project(":miuistub")) 62 | 63 | implementation(libs.refine.runtime) 64 | implementation(libs.kotlin.stdlib.jdk8) 65 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/home/RestoreGoogleSearch.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.home 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import de.robv.android.xposed.XC_MethodReplacement 6 | import de.robv.android.xposed.XposedBridge 7 | import de.robv.android.xposed.XposedHelpers 8 | import io.github.chsbuffer.miuihelper.model.Hook 9 | 10 | 11 | object RestoreGoogleSearch : Hook() { 12 | override fun init() { 13 | if (!xPrefs.getBoolean("restore_google_search", false)) 14 | return 15 | 16 | XposedHelpers.findAndHookMethod( 17 | "com.miui.home.launcher.SearchBarDesktopLayout", 18 | classLoader, 19 | "launchGlobalSearch", 20 | java.lang.String::class.java, 21 | java.lang.String::class.java, 22 | object : XC_MethodReplacement() { 23 | override fun replaceHookedMethod(param: MethodHookParam) { 24 | val context = 25 | XposedHelpers.getObjectField(param.thisObject, "mLauncher") as Context 26 | try { 27 | context.startActivity( 28 | Intent("android.search.action.GLOBAL_SEARCH").addFlags( 29 | Intent.FLAG_ACTIVITY_NEW_TASK 30 | // and Intent.FLAG_ACTIVITY_CLEAR_TASK 31 | and Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 32 | ).setPackage("com.google.android.googlequicksearchbox") 33 | ) 34 | } catch (e: Exception) { 35 | // fallback doesn't work, still keep the code here because i don't care 36 | XposedBridge.invokeOriginalMethod( 37 | param.method, 38 | param.thisObject, 39 | param.args 40 | ) 41 | } 42 | } 43 | } 44 | ) 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/lbe/SuggestPermissions.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.lbe 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedHelpers 5 | import io.github.chsbuffer.miuihelper.model.Hook 6 | 7 | // DO_NOT_USE_OR_YOU_WILL_BE_FIRED 8 | object SuggestPermissions : Hook() { 9 | 10 | private const val PERM_ID_AUTOSTART: Long = 16384 11 | 12 | private const val PERM_ID_INSTALL_SHORTCUT = 4503599627370496L 13 | 14 | private const val DefaultAccept = PERM_ID_AUTOSTART or PERM_ID_INSTALL_SHORTCUT 15 | 16 | override fun init() { 17 | val clazz = 18 | XposedHelpers.findClass("com.lbe.security.bean.AppPermissionConfig", classLoader) 19 | 20 | XposedHelpers.findAndHookMethod(clazz, 21 | "setSuggestAccept", 22 | Long::class.javaPrimitiveType, 23 | object : XC_MethodHook() { 24 | override fun beforeHookedMethod(param: MethodHookParam) { 25 | param.args[0] = (param.args[0] as Long) or DefaultAccept 26 | } 27 | }) 28 | 29 | XposedHelpers.findAndHookMethod(clazz, 30 | "setSuggestReject", 31 | Long::class.javaPrimitiveType, 32 | object : XC_MethodHook() { 33 | override fun beforeHookedMethod(param: MethodHookParam) { 34 | param.args[0] = (param.args[0] as Long) xor DefaultAccept 35 | } 36 | }) 37 | 38 | XposedHelpers.findAndHookMethod(clazz, 39 | "setSuggestedPermissions", 40 | Long::class.javaPrimitiveType, 41 | Long::class.javaPrimitiveType, 42 | Long::class.javaPrimitiveType, 43 | Long::class.javaPrimitiveType, 44 | Long::class.javaPrimitiveType, 45 | Long::class.javaPrimitiveType, 46 | object : XC_MethodHook() { 47 | override fun beforeHookedMethod(param: MethodHookParam) { 48 | param.args[0] = (param.args[0] as Long) or DefaultAccept 49 | param.args[3] = (param.args[3] as Long) xor DefaultAccept 50 | } 51 | }) 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/securitycenter/RemoveBehaviorRecordWhiteListAndNoIgnoreSystemApp.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.securitycenter 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XC_MethodReplacement 5 | import de.robv.android.xposed.XposedBridge 6 | import io.github.chsbuffer.miuihelper.model.Hook 7 | 8 | 9 | class RemoveBehaviorRecordWhiteListAndNoIgnoreSystemApp(val dexKitCache: DexKitCache) : Hook() { 10 | val whiteApp = setOf( 11 | "com.miui.micloudsync", 12 | "com.mobiletools.systemhelper", 13 | "com.android.contacts", 14 | "com.android.phone", 15 | "com.android.mms", 16 | "com.android.providers.contacts", 17 | "com.android.calendar", 18 | "com.android.providers.calendar", 19 | "com.lbe.security.miui", 20 | "com.android.permissioncontroller", 21 | "com.android.incallui", 22 | "com.xiaomi.metoknlp", 23 | "com.xiaomi.location.fused", 24 | "android", 25 | "com.qualcomm.location", 26 | "com.xiaomi.bsp.gps.nps", 27 | "com.android.systemui", 28 | "com.google.android.gms", 29 | ) 30 | 31 | override fun init() { 32 | if (!xPrefs.getBoolean("behavior_record_enhance", true)) return 33 | // 去除照明弹行为记录白名单 34 | 35 | // com.miui.permcenter.privacymanager.behaviorrecord 36 | 37 | val checkHook = object : XC_MethodHook() { 38 | override fun beforeHookedMethod(param: MethodHookParam) { 39 | if (xPrefs.getBoolean("behavior_record_system_app_whitelist", true)) { 40 | // 不记录硬编码的系统应用,主要是系统组件 41 | val str = param.args[1] as? String ?: return 42 | val pkg = str.split('@')[0] 43 | 44 | param.result = whiteApp.contains(pkg) 45 | } else { 46 | // 记录全部系统应用 47 | param.result = false 48 | } 49 | } 50 | } 51 | 52 | // 跳过加载容许规则(云端+内置) 53 | XposedBridge.hookMethod( 54 | dexKitCache.behavior_initTolerateAppsMethod.getMethodInstance(classLoader), 55 | XC_MethodReplacement.returnConstant(null) 56 | ) 57 | 58 | // 替换 check should ignore 方法 59 | XposedBridge.hookMethod( 60 | dexKitCache.behavior_shouldIgnore.getMethodInstance(classLoader), checkHook 61 | ) 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/model/BooleanDuringMethod.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.model 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XposedHelpers 5 | 6 | open class BooleanDuringMethod : XC_MethodHook { 7 | enum class Type { 8 | ThisObj, Obj, Class, Func 9 | } 10 | 11 | private var type: Type 12 | private var clazz: Class<*>? = null 13 | private var obj: Any? = null 14 | private var func: ((MethodHookParam) -> Any)? = null 15 | 16 | private val fieldName: String 17 | protected var value: Boolean 18 | 19 | constructor(fieldName: String, value: Boolean) : super() { 20 | type = Type.ThisObj 21 | this.fieldName = fieldName 22 | this.value = value 23 | } 24 | 25 | constructor(func: (MethodHookParam) -> Any, fieldName: String, value: Boolean) : super() { 26 | type = Type.Func 27 | this.func = func 28 | this.fieldName = fieldName 29 | this.value = value 30 | } 31 | 32 | constructor(obj: Any, fieldName: String, value: Boolean) : super() { 33 | type = Type.Obj 34 | this.obj = obj 35 | this.fieldName = fieldName 36 | this.value = value 37 | } 38 | 39 | constructor(clazz: Class<*>, fieldName: String, value: Boolean) : super() { 40 | type = Type.Class 41 | this.clazz = clazz 42 | this.fieldName = fieldName 43 | this.value = value 44 | } 45 | 46 | private var oldValue: Boolean = false 47 | 48 | override fun beforeHookedMethod(param: MethodHookParam) { 49 | when (type) { 50 | Type.ThisObj -> obj = param.thisObject 51 | Type.Func -> obj = func!!(param) 52 | Type.Class -> { 53 | oldValue = XposedHelpers.getStaticBooleanField(clazz, fieldName) 54 | XposedHelpers.setStaticBooleanField(clazz, fieldName, value) 55 | return 56 | } 57 | else -> {} 58 | } 59 | 60 | oldValue = XposedHelpers.getBooleanField(obj, fieldName) 61 | XposedHelpers.setBooleanField(obj, fieldName, value) 62 | } 63 | 64 | override fun afterHookedMethod(param: MethodHookParam?) { 65 | when (type) { 66 | Type.ThisObj, Type.Obj, Type.Func -> XposedHelpers.setBooleanField( 67 | obj, 68 | fieldName, 69 | oldValue 70 | ) 71 | Type.Class -> XposedHelpers.setStaticBooleanField(clazz, fieldName, oldValue) 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/util/XposedUtil.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.util 2 | 3 | import android.app.Application 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedBridge 6 | import de.robv.android.xposed.XposedHelpers 7 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 8 | import io.github.chsbuffer.miuihelper.BuildConfig 9 | import io.github.chsbuffer.miuihelper.model.Hook 10 | import org.luckypray.dexkit.DexKitBridge 11 | import org.luckypray.dexkit.result.MethodData 12 | import kotlin.system.measureTimeMillis 13 | 14 | fun dlog(text: String) { 15 | if (BuildConfig.DEBUG) XposedBridge.log("[MIUI QOL] " + text) 16 | } 17 | 18 | fun log(text: String) { 19 | XposedBridge.log("[MIUI QOL] " + text) 20 | } 21 | 22 | fun hooks(lpparam: LoadPackageParam, vararg hooks: Hook) { 23 | hooks.forEach { hook -> 24 | runCatching { 25 | hook.init(lpparam) 26 | }.onFailure { 27 | log("Failed to do ${hook::class.java.simpleName} hook\n${it}") 28 | dlog(it.stackTraceToString()) 29 | } 30 | } 31 | } 32 | 33 | fun inContext(lpparam: LoadPackageParam, f: (Application) -> Unit) { 34 | val appClazz = XposedHelpers.findClass(lpparam.appInfo.className, lpparam.classLoader) 35 | XposedBridge.hookMethod(appClazz.getMethod("onCreate"), object : XC_MethodHook() { 36 | override fun afterHookedMethod(param: MethodHookParam) { 37 | val app = param.thisObject as Application 38 | f(app) 39 | } 40 | }) 41 | } 42 | 43 | val dexKit: DexKitBridge 44 | get() = _dexKit!! 45 | private var _dexKit: DexKitBridge? = null 46 | 47 | fun useDexKit(lpparam: LoadPackageParam, f: (DexKitBridge) -> Unit) { 48 | System.loadLibrary("dexkit") 49 | runCatching { 50 | DexKitBridge.create(lpparam.appInfo.sourceDir) 51 | }.onSuccess { 52 | _dexKit = it 53 | f(it) 54 | _dexKit!!.close() 55 | _dexKit = null 56 | }.onFailure { 57 | log("DexKitBridge create failed for ${lpparam.packageName}") 58 | } 59 | } 60 | 61 | 62 | /** 记录错误,记录converter转换后的结果 */ 63 | inline fun logSearch(name: String, converter: (T) -> String, f: () -> T): T { 64 | if (BuildConfig.DEBUG) { 65 | var timeInMillis: Long = 0 66 | return runCatching { 67 | val ret: T 68 | timeInMillis = measureTimeMillis { 69 | ret = f() 70 | } 71 | ret 72 | }.onSuccess { 73 | dlog("$name: ${converter(it)}\ttime cost: $timeInMillis ms") 74 | }.onFailure { 75 | log("find $name failed: $it") 76 | }.getOrThrow() 77 | } else { 78 | return runCatching(f).onFailure { 79 | log("find $name failed: $it") 80 | }.getOrThrow() 81 | } 82 | } 83 | 84 | 85 | val MethodData.method 86 | get() = "${this.name}${this.methodSign}" 87 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 更改设置后强行停止对应应用后功能生效。 4 | 手机管家 5 | 应用行为记录增强 6 | 查看应用行为记录不过滤白名单应用、系统应用。 7 | 系统应用 WLAN 联网控制 8 | - 阻止手机管家自行恢复系统应用 WLAN 联网\n- 显示系统应用联网控制 WLAN 联网开关\n\n要查看系统应用联网控制,请前往手机管家 万能工具箱-应用联网控制-(数据)-系统应用,在列表中点击要设置的应用。 9 | 启用所有 TextView 10 | - 启用系统应用 WLAN 联网控制开关\n- 跳过警告时间(自行无视倒计时数字)\n- ……\n\n*为不可预见的后果做好准备。 11 | 体检分数锁定 100 分 12 | 同 WooBox for MIUI 锁定100分 13 | 系统桌面 14 | 恢复 Google 桌面图标 15 | 截图 16 | 截图保存到 Pictures/Screenshots 17 | 屏幕录制 18 | 视频保存到 Movies/ScreenRecorder 19 | 小爱翻译 20 | 强制启用 AI字幕 21 | 只能识别翻译中文、英文。 22 | 系统更新 23 | 移除 OTA 验证 24 | 同 WooBox for MIUI 移除 OTA 验证。\n- 仅支持VAB系列使用,其余系列请不要开启。\n- 无需内测试资格即可刷入完整内测包。\n- 拥有内测权限的用户将无法接收内测更新\n可用于屏蔽系统更新。\n- 跨版本类型升级后,建议清空数据。\n- 不支持非官方ROM使用。\n\n*为不可预见的后果做好准备。 25 | Telegram 群组 26 | 点击加入 MIUI QOL Telegram 群组 27 | MIUI 生活质量提升 28 | MIUI QOL\n由 ChsBuffer 随便做做 29 | MIUI 生活质量提升 30 | 已经启用?点此结束运行,然后再次打开模块设置 31 | 模块尚未启用! 32 | 系统界面 33 | 恢复附近分享的快速设置磁贴 34 | 应用行为记录系统应用白名单 35 | 将部分系统应用列入白名单,防止隐私信息保护通知吓到不知道如何关闭它们的用户。 36 | 将应用详情中“消除默认操作”改为“默认打开”设置 37 | 默认打开 38 | 允许应用打开支持的链接 39 | 不允许应用打开链接 40 | 去除通知设置白名单 41 | 通知默认悬浮通知、震动、发出声音、在锁定屏幕上显示。 42 | 强行停止系统界面 43 | 确定强行停止系统界面? 44 | 确定 45 | 取消 46 | 开放源代码许可 47 | 需要 x.12.6.1.x 或更高版本。\n如果声音录制出现问题请关闭此功能。 48 | 强制启用原生录音支持 49 | 打开通知频道设置 50 | 打开通知菜单的设置会导航至频道设置而不是应用通用通知设置 51 | 替换搜索为 Google 搜索 52 | 恢复切换负一屏 53 | 如果使用谷歌探索无法滑动到负一屏,请强行停止 Google 54 | 恢复电子钱包的快速设置磁贴 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 更改設定後強制停止對應應用以生效 4 | 手機管家 5 | 應用行為記錄增強 6 | 查看應用行為記錄不過濾白名單應用、系統應用。 7 | 系統應用 Wi-Fi 連網限制 8 | - 阻止手機管家自行恢復系統應用 Wi-Fi 連網\n- 顯示系統應用連線控制 Wi-Fi 連線開關 9 | 啟用所有 TextView 10 | - 啟用系統應用 Wi-Fi 連線控制開關\n- 跳過警告時間 (自行無視倒數計時數字)\n- ……\n\n*為不可預期的後果請做好準備。 11 | 手機管家分數鎖定100分 12 | 同 WooBox for MIUI 鎖定100分 13 | 系統桌面 14 | 恢復 Google 桌面圖標 15 | 螢幕截圖 16 | 截圖保存到 Pictures/Screenshots 17 | 螢幕錄製 18 | 影片保存到 Movies/ScreenRecorder 19 | 小愛翻譯 20 | 強制啟用 AI 字幕 21 | 只能識別翻譯中文、英文。 22 | 系統更新 23 | 移除 OTA 驗證 24 | 同 WooBox for MIUI 移除 OTA 驗證。 \n- 僅支持VAB系列使用,其餘系列請不要開啟。 \n- 無需內測試資格即可刷入完整內測包。 \n- 擁有內測權限的用戶將無法接收內測更新\n可用於關閉系統更新。 \n- 跨版本類型升級後,建議清空數據。 \n- 不支援非官方ROM使用。 \n\n*為不可預期的後果請做好準備。 25 | Telegram 群組 26 | 點擊加入 MIUI QOL Telegram 群組 27 | MIUI 生活品質提升 28 | MIUI QOL\n由 ChsBuffer 製作\n翻譯:聖小熊 (Github: @a406010503) 29 | MIUI 生活品質提升 30 | 已經啟用? 點此結束應用程式執行,然後再次打開模組設定 31 | 模組尚未啟用! 32 | 系統介面 33 | 恢復鄰近分享的快捷設定磁貼 34 | 應用行為記錄系統應用白名單 35 | 將部分系統應用列入白名單,這樣隱私保護通知就不會嚇到不知道如何關閉它們的用戶。 36 | 將應用詳情中「消除預設操作」改為「預設為開啟」設定 37 | 預設為開啟 38 | 允許應用程式開啟支援的連結 39 | 不允許應用程式開啟連結 40 | 移除通知設定白名單 41 | 預設允許懸浮通知、通知鈴聲、通知震動、鎖定螢幕通知。 42 | 強制停止 SystemUI 43 | 您確定要強制停止 SystemUI 嗎? 44 | 確認 45 | 取消 46 | 開放原始碼授權 47 | 需要 x.12.6.1.x 或更高版本。 \n如果聲音錄製出現問題請關閉此功能。 48 | 強制啟用原生錄音支援 49 | 開啟頻道設置 50 | 開啟通知功能表的設置會導航至頻道設置而不是應用通用通知設置 51 | 將搜尋欄替換為 Google 搜尋 52 | 恢復切換負一屏 53 | 如果使用 Google Discover 無法切換到負一屏,請强制停止 Google 應用。 54 | 恢復錢包的快捷設定磁貼 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/home/RestoreSwitchMinusScreen.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.home 2 | 3 | import android.content.Intent 4 | import de.robv.android.xposed.XC_MethodHook 5 | import de.robv.android.xposed.XposedHelpers 6 | import io.github.chsbuffer.miuihelper.model.BooleanDuringMethod 7 | import io.github.chsbuffer.miuihelper.model.Hook 8 | import miui.os.Build 9 | 10 | 11 | object RestoreSwitchMinusScreen : Hook() { 12 | override fun init() { 13 | if (!xPrefs.getBoolean("restore_switch_minus_screen", false) || Build.IS_INTERNATIONAL_BUILD) return 14 | 15 | // 启用可以切换负一屏(即“智能助理”或“Google”) 16 | val LauncherAssistantCompatClass = 17 | classLoader.loadClass("com.miui.home.launcher.LauncherAssistantCompat") 18 | XposedHelpers.setStaticBooleanField( 19 | LauncherAssistantCompatClass, "CAN_SWITCH_MINUS_SCREEN", true 20 | ) 21 | 22 | // isUseGoogleMinusScreen, instead of LauncherAssistantCompatMIUI 23 | val mUtilities = classLoader.loadClass("com.miui.home.launcher.common.Utilities") 24 | XposedHelpers.findAndHookMethod("com.miui.home.launcher.LauncherAssistantCompat", 25 | classLoader, 26 | "newInstance", 27 | "com.miui.home.launcher.Launcher", 28 | object : BooleanDuringMethod( 29 | XposedHelpers.findClass( 30 | "miui.os.Build", classLoader 31 | ), "IS_INTERNATIONAL_BUILD", true 32 | ) { 33 | override fun beforeHookedMethod(param: MethodHookParam) { 34 | super.value = 35 | XposedHelpers.callStaticMethod(mUtilities, "getCurrentPersonalAssistant") 36 | .equals("personal_assistant_google") 37 | super.beforeHookedMethod(param) 38 | } 39 | }) 40 | 41 | // 使用含 AssistantSwitchObserver 的 LauncherCallbacksGlobal, 而不是 LauncherCallbacksChinese (只观察负一屏是否开启) 42 | XposedHelpers.findAndHookConstructor( 43 | "com.miui.home.launcher.Launcher", classLoader, BooleanDuringMethod( 44 | XposedHelpers.findClass( 45 | "miui.os.Build", classLoader 46 | ), "IS_INTERNATIONAL_BUILD", true 47 | ) 48 | ) 49 | 50 | // 恢复“切换负一屏”设置 51 | XposedHelpers.findAndHookMethod("com.miui.home.settings.MiuiHomeSettings", 52 | classLoader, 53 | "onCreatePreferences", 54 | android.os.Bundle::class.java, 55 | java.lang.String::class.java, 56 | object : XC_MethodHook() { 57 | override fun afterHookedMethod(param: MethodHookParam) { 58 | val mSwitchPersonalAssistant = 59 | XposedHelpers.getObjectField(param.thisObject, "mSwitchPersonalAssistant") 60 | XposedHelpers.callMethod( 61 | mSwitchPersonalAssistant, 62 | "setIntent", 63 | Intent("com.miui.home.action.LAUNCHER_PERSONAL_ASSISTANT_SETTING") 64 | ) 65 | XposedHelpers.callMethod( 66 | mSwitchPersonalAssistant, "setOnPreferenceChangeListener", param.thisObject 67 | ) 68 | val mPreferenceScreen = 69 | XposedHelpers.callMethod(param.thisObject, "getPreferenceScreen") 70 | XposedHelpers.callMethod( 71 | mPreferenceScreen, "addPreference", mSwitchPersonalAssistant 72 | ) 73 | } 74 | }) 75 | 76 | XposedHelpers.findAndHookMethod("com.miui.home.settings.MiuiHomeSettings", 77 | classLoader, 78 | "onResume", 79 | object : XC_MethodHook() { 80 | override fun afterHookedMethod(param: MethodHookParam) { 81 | val mSwitchPersonalAssistant = 82 | XposedHelpers.getObjectField(param.thisObject, "mSwitchPersonalAssistant") 83 | XposedHelpers.callMethod( 84 | mSwitchPersonalAssistant, "setVisible", true 85 | ) 86 | 87 | } 88 | }) 89 | 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") @file:SuppressLint("WorldReadableFiles") 2 | 3 | package io.github.chsbuffer.miuihelper 4 | 5 | import android.annotation.SuppressLint 6 | import android.app.Activity 7 | import android.app.AlertDialog 8 | import android.os.Build 9 | import android.os.Bundle 10 | import android.preference.Preference 11 | import android.preference.Preference.OnPreferenceChangeListener 12 | import android.preference.Preference.OnPreferenceClickListener 13 | import android.preference.PreferenceFragment 14 | import android.preference.SwitchPreference 15 | import kotlin.system.exitProcess 16 | 17 | 18 | class SettingsActivity : Activity() { 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | fragmentManager.beginTransaction().replace(android.R.id.content, SettingsFragment()) 23 | .commit() 24 | } 25 | 26 | class SettingsFragment : PreferenceFragment(), OnPreferenceChangeListener, 27 | OnPreferenceClickListener { 28 | 29 | @Deprecated("Deprecated in Java") 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | addPreferencesFromResource(R.xml.header_preferences) 33 | 34 | val isModuleActivated: Boolean = try { 35 | context.getSharedPreferences("prefs", MODE_WORLD_READABLE) 36 | true 37 | } catch (e: SecurityException) { 38 | false 39 | } 40 | 41 | if (!isModuleActivated) { 42 | addPreferencesFromResource(R.xml.warn_preference) 43 | findPreference("warn").onPreferenceClickListener = this 44 | return 45 | } 46 | 47 | preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE 48 | preferenceManager.sharedPreferencesName = "prefs" 49 | addPreferencesFromResource(R.xml.root_preferences) 50 | 51 | findPreference("behavior_record_enhance").onPreferenceChangeListener = this 52 | findPreference("kill_system_ui").onPreferenceClickListener = this 53 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { 54 | removePreference("original_default_open_setting") 55 | } 56 | if (miui.os.Build.IS_INTERNATIONAL_BUILD) { 57 | removePreference("restore_google_icon") 58 | removePreference("notification_settings_no_white_list") 59 | removePreference("restore_wallet_tile") 60 | } 61 | } 62 | 63 | private fun removePreference(key: String) { 64 | val pref = findPreference(key) 65 | val category = pref.parent!! 66 | category.removePreference(pref) 67 | 68 | if (category.preferenceCount == 0) { 69 | category.parent!!.removePreference(category) 70 | } 71 | } 72 | 73 | @Deprecated("Deprecated in Java") 74 | override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean = 75 | when (preference.key) { 76 | "behavior_record_enhance" -> { 77 | (findPreference("behavior_record_system_app_whitelist") as SwitchPreference).isChecked = 78 | newValue as Boolean 79 | true 80 | } 81 | else -> true 82 | } 83 | 84 | @Deprecated("Deprecated in Java") 85 | override fun onPreferenceClick(preference: Preference) = when (preference.key) { 86 | "warn" -> { 87 | exitProcess(0) 88 | } 89 | "kill_system_ui" -> { 90 | AlertDialog.Builder(context).setMessage(R.string.kill_system_ui_confirm) 91 | .setPositiveButton(R.string.confirm) { _, _ -> 92 | Runtime.getRuntime().exec("su -c killall com.android.systemui") 93 | } 94 | .setNegativeButton(R.string.cancel) { _, _ -> } 95 | .show() 96 | true 97 | } 98 | else -> false 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/MainHook.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper 2 | 3 | import androidx.annotation.Keep 4 | import de.robv.android.xposed.IXposedHookLoadPackage 5 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 6 | import io.github.chsbuffer.miuihelper.hooks.aiasst.SupportAiSubtitles 7 | import io.github.chsbuffer.miuihelper.hooks.home.RestoreCnBuildGoogleApp 8 | import io.github.chsbuffer.miuihelper.hooks.home.RestoreGoogleSearch 9 | import io.github.chsbuffer.miuihelper.hooks.home.RestoreSwitchMinusScreen 10 | import io.github.chsbuffer.miuihelper.hooks.screenrecorder.ForceSupportPlaybackCapture 11 | import io.github.chsbuffer.miuihelper.hooks.screenrecorder.SaveToMovies 12 | import io.github.chsbuffer.miuihelper.hooks.screenshot.SaveToPictures 13 | import io.github.chsbuffer.miuihelper.hooks.securitycenter.AppDetailsStockOpenDefaultSettings 14 | import io.github.chsbuffer.miuihelper.hooks.securitycenter.AppDetailsSystemAppWlanControl 15 | import io.github.chsbuffer.miuihelper.hooks.securitycenter.DexKitCache 16 | import io.github.chsbuffer.miuihelper.hooks.securitycenter.EnabledAllTextView 17 | import io.github.chsbuffer.miuihelper.hooks.securitycenter.IntlEnableBehaviorRecord 18 | import io.github.chsbuffer.miuihelper.hooks.securitycenter.LockOneHundred 19 | import io.github.chsbuffer.miuihelper.hooks.securitycenter.RemoveBehaviorRecordWhiteListAndNoIgnoreSystemApp 20 | import io.github.chsbuffer.miuihelper.hooks.securitycenter.RemoveSetSystemAppWifiRuleAllow 21 | import io.github.chsbuffer.miuihelper.hooks.systemui.NotificationClickInfoItemStartChannelSetting 22 | import io.github.chsbuffer.miuihelper.hooks.systemui.NotificationSettingsNoWhiteList 23 | import io.github.chsbuffer.miuihelper.hooks.systemui.RestoreCnNearby 24 | import io.github.chsbuffer.miuihelper.hooks.systemui.RestoreCnQuickAccessWalletTile 25 | import io.github.chsbuffer.miuihelper.hooks.updater.RemoveOTAValidate 26 | import io.github.chsbuffer.miuihelper.util.hooks 27 | import io.github.chsbuffer.miuihelper.util.inContext 28 | import io.github.chsbuffer.miuihelper.util.useDexKit 29 | 30 | @Keep 31 | class MainHook : IXposedHookLoadPackage { 32 | override fun handleLoadPackage(lpparam: LoadPackageParam) { 33 | when (lpparam.packageName) { 34 | "com.android.updater" -> useDexKit(lpparam) { dexKit -> 35 | hooks( 36 | lpparam, 37 | RemoveOTAValidate(dexKit) 38 | ) 39 | } 40 | 41 | "com.miui.securitycenter" -> inContext(lpparam) { app -> 42 | useDexKit(lpparam) { dexKit -> 43 | val dexKitCache = DexKitCache() 44 | hooks( 45 | lpparam, 46 | RemoveBehaviorRecordWhiteListAndNoIgnoreSystemApp(dexKitCache), 47 | RemoveSetSystemAppWifiRuleAllow, 48 | EnabledAllTextView, 49 | LockOneHundred(dexKit), 50 | AppDetailsSystemAppWlanControl(dexKitCache, app), 51 | AppDetailsStockOpenDefaultSettings(dexKitCache, app), 52 | IntlEnableBehaviorRecord(dexKitCache) 53 | ) 54 | } 55 | } 56 | 57 | "com.miui.screenrecorder" -> hooks( 58 | lpparam, 59 | SaveToMovies, 60 | ForceSupportPlaybackCapture 61 | ) 62 | 63 | "com.miui.screenshot" -> hooks( 64 | lpparam, 65 | SaveToPictures 66 | ) 67 | 68 | "com.miui.home" -> inContext(lpparam) { 69 | hooks( 70 | lpparam, 71 | RestoreCnBuildGoogleApp, 72 | RestoreSwitchMinusScreen, 73 | RestoreGoogleSearch 74 | ) 75 | } 76 | 77 | "com.xiaomi.aiasst.vision" -> hooks( 78 | lpparam, 79 | SupportAiSubtitles 80 | ) 81 | 82 | "com.android.systemui" -> useDexKit(lpparam) { dexKit -> 83 | hooks( 84 | lpparam, 85 | RestoreCnNearby(dexKit), 86 | RestoreCnQuickAccessWalletTile, 87 | NotificationSettingsNoWhiteList, 88 | NotificationClickInfoItemStartChannelSetting(dexKit) 89 | ) 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/securitycenter/AppDetailsSystemAppWlanControl.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.securitycenter 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.app.Application 6 | import android.content.DialogInterface 7 | import android.content.pm.PackageManager 8 | import android.view.View 9 | import de.robv.android.xposed.XC_MethodHook 10 | import de.robv.android.xposed.XposedBridge 11 | import de.robv.android.xposed.XposedHelpers 12 | import io.github.chsbuffer.miuihelper.BuildConfig 13 | import io.github.chsbuffer.miuihelper.model.BooleanDuringMethod 14 | import io.github.chsbuffer.miuihelper.model.Hook 15 | import io.github.chsbuffer.miuihelper.util.log 16 | 17 | 18 | class AppDetailsSystemAppWlanControl(val dexKitCache: DexKitCache, val app: Application) : 19 | Hook() { 20 | 21 | @SuppressLint("DiscouragedApi") 22 | override fun init() { 23 | if (!xPrefs.getBoolean("system_app_wlan_control", true)) return 24 | 25 | val net_id = app.resources.getIdentifier("am_detail_net", "id", app.packageName) 26 | 27 | if (net_id == 0) { 28 | log("network control has been removed from app details.") 29 | return 30 | } 31 | 32 | // init lazy 33 | dexKitCache.appDetails_isSystemAppField 34 | dexKitCache.appDetails_genNetCtrlSummaryMethod 35 | dexKitCache.appDetails_netCtrlShowDialogMethod 36 | dexKitCache.appDetails_OnLoadDataFinishMethod 37 | 38 | val appDetailClz = 39 | XposedHelpers.findClass("com.miui.appmanager.ApplicationsDetailsActivity", classLoader) 40 | 41 | /* "联网控制"对话框确定 onClick */ 42 | val saveNetCtrlDialogOnClickMethod = appDetailClz.declaredClasses.first { 43 | DialogInterface.OnMultiChoiceClickListener::class.java.isAssignableFrom(it) 44 | }.methods.first { 45 | it.name == "onClick" 46 | } 47 | 48 | XposedHelpers.findAndHookConstructor(appDetailClz, object : XC_MethodHook() { 49 | override fun beforeHookedMethod(param: MethodHookParam) { 50 | // 和 联网控制 有关的方法调用期间,将 isSystemApp 设为 false 51 | val hook = BooleanDuringMethod( 52 | param.thisObject, dexKitCache.appDetails_isSystemAppField.name, false 53 | ) 54 | 55 | XposedBridge.hookMethod(saveNetCtrlDialogOnClickMethod, hook) 56 | XposedBridge.hookMethod( 57 | dexKitCache.appDetails_genNetCtrlSummaryMethod.getMethodInstance(classLoader), 58 | hook 59 | ) 60 | XposedBridge.hookMethod( 61 | dexKitCache.appDetails_netCtrlShowDialogMethod.getMethodInstance(classLoader), 62 | hook 63 | ) 64 | } 65 | }) 66 | 67 | // 仅WIFi设备会直接隐藏联网控制 68 | // Hook LiveData,当查看的应用有联网权限,显示”联网控制“ 69 | if (XposedHelpers.callStaticMethod( 70 | classLoader.loadClass("android.os.SystemProperties"), 71 | "getBoolean", 72 | "ro.radio.noril", 73 | false 74 | ) as Boolean || BuildConfig.DEBUG 75 | ) { 76 | val net_ctrl_title_id = app.resources.getIdentifier( 77 | "app_manager_net_control_title", "string", app.packageName 78 | ) 79 | val app_manager_disable = app.getString( 80 | app.resources.getIdentifier( 81 | "app_manager_disable", "string", app.packageName 82 | ) 83 | ) 84 | 85 | XposedBridge.hookMethod(dexKitCache.appDetails_OnLoadDataFinishMethod.getMethodInstance( 86 | classLoader 87 | ), object : XC_MethodHook() { 88 | override fun afterHookedMethod(param: MethodHookParam) { 89 | val pkgName = 90 | (param.thisObject as Activity).intent.getStringExtra("package_name")!! 91 | val allowInternet = app.packageManager.checkPermission( 92 | "android.permission.INTERNET", pkgName 93 | ) == PackageManager.PERMISSION_GRANTED 94 | 95 | if (allowInternet && pkgName != "com.xiaomi.finddevice" && pkgName != "com.miui.mishare.connectivity") { 96 | val netCtrlView = (param.thisObject as Activity).findViewById(net_id) 97 | netCtrlView.visibility = View.VISIBLE 98 | XposedHelpers.callMethod(netCtrlView, "setTitle", net_ctrl_title_id) 99 | } 100 | } 101 | }) 102 | 103 | XposedBridge.hookMethod(dexKitCache.appDetails_genNetCtrlSummaryMethod.getMethodInstance( 104 | classLoader 105 | ), 106 | object : XC_MethodHook() { 107 | override fun afterHookedMethod(param: MethodHookParam) { 108 | // 生成 Summary 文本的方法未考虑仅WIFI设备的系统应用被禁用联网,会返回空白字符串 109 | // 返回 "不允许" Resource ID 110 | if ((param.result as String).isBlank()) param.result = app_manager_disable 111 | } 112 | }) 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/systemui/RestoreCnNearby.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.systemui 2 | 3 | import de.robv.android.xposed.XC_MethodHook 4 | import de.robv.android.xposed.XC_MethodReplacement 5 | import de.robv.android.xposed.XposedBridge 6 | import de.robv.android.xposed.XposedHelpers 7 | import io.github.chsbuffer.miuihelper.model.BooleanDuringMethod 8 | import io.github.chsbuffer.miuihelper.model.Hook 9 | import io.github.chsbuffer.miuihelper.util.dlog 10 | import io.github.chsbuffer.miuihelper.util.method 11 | import miui.os.Build 12 | import org.luckypray.dexkit.DexKitBridge 13 | import org.luckypray.dexkit.query.enums.StringMatchType 14 | import java.lang.reflect.Method 15 | 16 | class RestoreCnNearby(val dexKit: DexKitBridge) : Hook() { 17 | var isHooked = false 18 | 19 | override fun init() { 20 | if (!xPrefs.getBoolean("restore_nearby_sharing_tile", true)) return 21 | 22 | /* hook miui.systemui.plugin */ 23 | val getPluginClassLoaderMethod = findGetPluginClassLoaderMethod() 24 | XposedBridge.hookMethod(getPluginClassLoaderMethod, object : XC_MethodHook() { 25 | override fun afterHookedMethod(param: MethodHookParam) { 26 | val pluginClassLoader = param.result as? ClassLoader ?: return 27 | if (isHooked) return 28 | 29 | val filterNearbyMethod = XposedHelpers.findMethodExactIfExists( 30 | "miui.systemui.controlcenter.qs.customize.TileQueryHelper\$Companion", 31 | pluginClassLoader, 32 | "filterNearby", 33 | String::class.java 34 | ) ?: return 35 | 36 | XposedBridge.hookMethod( 37 | filterNearbyMethod, XC_MethodReplacement.returnConstant(false) 38 | ) 39 | isHooked = true 40 | } 41 | }) 42 | 43 | 44 | /* hook systemui */ 45 | val controlCenterUtilsClazz = XposedHelpers.findClass( 46 | "com.android.systemui.controlcenter.utils.ControlCenterUtils", classLoader 47 | ) 48 | 49 | if (!Build.IS_INTERNATIONAL_BUILD) { 50 | dexKit.findMethod { 51 | matcher { 52 | addUsingString( 53 | "custom(com.google.android.gms/.nearby.sharing.SharingTileService)", 54 | StringMatchType.Equals 55 | ) 56 | } 57 | }.forEach { createMiuiTileMethodData -> 58 | dlog("createMiuiTile is: ${createMiuiTileMethodData.method}") 59 | 60 | dexKit.findField { 61 | matcher { 62 | type = "boolean" 63 | addReadMethod(createMiuiTileMethodData.descriptor) 64 | name("INTERNATIONAL", StringMatchType.Contains) 65 | } 66 | findFirst = true 67 | }.single().also { isInternationalFieldData -> 68 | dlog("${createMiuiTileMethodData.method} using IS_INTERNATIONAL field: ${isInternationalFieldData.descriptor}") 69 | 70 | val hook = BooleanDuringMethod( 71 | XposedHelpers.findClass( 72 | isInternationalFieldData.declaredClassName, classLoader 73 | ), isInternationalFieldData.name, true 74 | ) 75 | XposedBridge.hookMethod( 76 | createMiuiTileMethodData.getMethodInstance(classLoader), hook 77 | ) 78 | } 79 | } 80 | } 81 | 82 | // 83 | XposedHelpers.findMethodExactIfExists( 84 | controlCenterUtilsClazz, "filterNearby", String::class.java 85 | )?.let { 86 | XposedBridge.hookMethod(it, XC_MethodReplacement.returnConstant(false)) 87 | } 88 | } 89 | 90 | private fun findGetPluginClassLoaderMethod(): Method { 91 | val methodDescriptor = dexKit.batchFindMethodUsingStrings { 92 | addSearchGroup { 93 | // HyperOS 1.0: maybe somewhere else, don't care, just do dexKit search. 94 | // MIUI 14: com.android.systemui.shared.plugins.PluginInstance$Factory.getClassLoader(android.content.pm.ApplicationInfo, java.lang.ClassLoader) 95 | groupName = "MIUI 14" 96 | usingStrings( 97 | listOf("Cannot get class loader for non-privileged plugin. Src:"), 98 | StringMatchType.Equals 99 | ) 100 | } 101 | addSearchGroup { 102 | // MIUI 13 com.android.systemui.shared.plugins.PluginManagerImpl.getClassLoader(android.content.pm.ApplicationInfo) 103 | groupName = "MIUI 13" 104 | usingStrings( 105 | listOf("Cannot get class loader for non-whitelisted plugin. Src:"), 106 | StringMatchType.Equals 107 | ) 108 | } 109 | }.values.flatten().singleOrNull() 110 | ?.also { dlog("System UI plugin loader method is: ${it.descriptor}") } 111 | ?: throw Exception("System UI plugin loader method not found.") 112 | return methodDescriptor.getMethodInstance(classLoader) 113 | } 114 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MIUI QOL 3 | Force stop the corresponding application to apply the settings. 4 | Security app 5 | App behavior record enhance 6 | View all app behavior does not filter whitelisted applications and system applications. 7 | System apps\' WLAN setting 8 | - Prevent Security app from recovering system apps\' WLAN connection setting\n- Show System apps\' WLAN setting 9 | Enable all TextView 10 | - Enable System apps\' WLAN setting\n- Skip warning time (Ignore the countdown numbers by yourself)\n- ……\n\n*Prepare for unforeseen consequences. 11 | Set 100 points on Security app 12 | As same as WooBox for MIUI Set 100 points on Security app 13 | System Launcher 14 | Restore Google shortcut 15 | Screenshot 16 | Save screenshots to Pictures/Screenshots 17 | ScreenRecorder 18 | Save videos to Movies/ScreenRecorder 19 | Mi AI Translator 20 | Force support AI subtitles 21 | Can only translate or transcribe Chinese and English. 22 | Updater 23 | Remove OTA validate 24 | As same as WooBox for MIUI *Remove OTA validate.\n- Only support VAB series use, please do not open other series.\n- No need for internal testing qualification to flash into the full internal testing package.\n- Those who have internal testing permission will not be able to receive internal testing updates, and can be used to block system updates.\n- After cross-version type upgrade, it is recommended to clear data.\n- Unofficial ROM usage is not supported.\n\n*Prepare for unforeseen consequences. 25 | Telegram Group 26 | Join the MIUI QOL Telegram Group 27 | MIUI Quality of Life Improvement 28 | MIUI QOL\nMade casually by ChsBuffer 29 | MIUI Quality of Life Improvement 30 | Already enabled? Click here to stop the module, then open the module settings again. 31 | Module is not enabled! 32 | SystemUI 33 | Restore Nearby Sharing Quick Settings Tile 34 | App Behavior Record system applications whitelist 35 | Whitelisted some system applications to prevent Privacy Protection Notifications from scaring users who don\'t know how to turn them off. 36 | Change App Info \"Clear defaults\" to \"Open by default\" 37 | Open by default 38 | Allow app to open supported links 39 | Don\'t allow app to open links 40 | Remove notification settings whitelist 41 | Allow floating notification, notification sound, notification vibration, notification on the lock screen by default. 42 | Force stop SystemUI 43 | Are you sure to force stop SystemUI? 44 | OK 45 | Cancel 46 | Open-source licenses 47 | Requires ScreenRecorder app version x.12.6.1.x or later.\nTurn off if you experience problems with audio recording. 48 | Force enable Native Audio Recorder 49 | Open channel settings 50 | Opening settings from notification menu leads to channel settings instead of app\'s common notification settings 51 | Search bar opens Google Search 52 | Restore switch -1 screen 53 | If you can\'t swipe to -1 screen when using Google Discover, force stop Google App 54 | Restore Quick Access Wallet Tile 55 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru-rRU/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Принудительно остановить соответствующее приложение для применения настроек. 4 | Безопасность MIUI 5 | Улучшение поведения записи приложений 6 | Просмотр всех действий приложений. Не фильтрует приложения и системные приложения из белого списка. 7 | Настройка WLAN системных приложений 8 | - Запретить приложению MIUI Безопасность восстанавливать настройки WLAN для системных приложений\n- Отображать настройки WLAN системных приложений 9 | Включить все текстовые представления (TextView) 10 | - Включение настройки WLAN для системных приложений\n- Пропускать время предупреждения (самостоятельно игнорировать цифры обратного отсчета)\n- ……\n\n*Приготовьтесь к непредвиденным последствиям. 11 | Установить 100 баллов в приложении Безопасность MIUI 12 | Так же, как и WooBox для MIUI, установка 100 баллов для приложения Безопасность MIUI 13 | Рабочий стол MIUI 14 | Восстановление ярлыка Google 15 | Скриншот 16 | Сохранять скриншоты по пути Pictures/Screenshots 17 | Запись экрана 18 | Сохранять видео по пути Movies/ScreenRecorder 19 | Mi AI Translator 20 | Принудительная поддержка AI субтитров 21 | Можно перевести или переписать только китайский и английский языки. 22 | Updater 23 | Удалить проверку OTA 24 | Так же, как WooBox для MIUI * Удаление проверки OTA.\n- Поддерживает только использование серии VAB, пожалуйста, не используйте на другие серии.\n- Нет необходимости в том, чтобы квалификация внутреннего тестирования была включена в полный пакет внутреннего тестирования.\n- Те, у кого есть разрешение на внутреннее тестирование, не смогут получать обновления для внутреннего тестирования, и могут быть использованы для блокировки обновлений системы.\n- После обновления типа кросс-версии рекомендуется очистить данные.\n- Неофициальные прошивки не поддерживаются.\n\n*Приготовьтесь к непредвиденным последствиям. 25 | Группа Telegram 26 | Присоединиться к группе MIUI QOL в Telegram 27 | Улучшение качества жизни MIUI 28 | MIUI QOL\nАвтор: ChsBuffer\nПеревел @majestics_translation 29 | Улучшение качества жизни MIUI 30 | Уже включен? Нажмите здесь, чтобы остановить модуль, затем снова откройте настройки модуля. 31 | Модуль не включен! 32 | SystemUI 33 | Восстановление плитки Nearby Sharing в центре управления 34 | Белый список параметров записи системных приложений 35 | Внести в белый список некоторые системные приложения, чтобы уведомления о защите конфиденциальности не пугали пользователей, которые не знают, как их отключить. 36 | Изменить информацию о приложении «Очистить по умолчанию» на «Открыть по умолчанию» 37 | Открывать по умолчанию 38 | Разрешить приложению открывать поддерживаемые ссылки 39 | Запретить приложению открывать ссылки 40 | Удалить белый список настроек уведомлений 41 | Разрешить плавающие уведомления, звук уведомлений, вибрацию уведомлений, уведомления на экране блокировки по умолчанию. 42 | Перезапустить SystemUI 43 | Вы уверены, что хотите принудительно перезапустить SystemUI? 44 | Ок 45 | Отмена 46 | Лицензирование открытого исходного кода 47 | Требует ScreenRecorder приложение версии x.12.6.1.x или более поздней версии.\nОтключите, если у вас возникли проблемы с записью. 48 | Принудительное включение нативной записи аудио 49 | Открывать настройки канала 50 | Открытие настроек из меню уведомления ведёт к настройкам канала, а не к общим настройкам уведомлений приложения 51 | Строка поиска открывает поиск Google 52 | Восстановление переключения -1 экрана 53 | Если Вы не можете перейти на -1 экран при использовании Google Discover, принудительно остановите приложение Google 54 | Восстановление плитки Кошелек в центре управления 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/securitycenter/DexKitCache.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.securitycenter 2 | 3 | import io.github.chsbuffer.miuihelper.util.dexKit 4 | import io.github.chsbuffer.miuihelper.util.dlog 5 | import io.github.chsbuffer.miuihelper.util.logSearch 6 | import io.github.chsbuffer.miuihelper.util.method 7 | import org.luckypray.dexkit.query.enums.OpCodeMatchType 8 | import org.luckypray.dexkit.query.enums.StringMatchType 9 | 10 | class DexKitCache { 11 | 12 | val appDetailsView by lazy(LazyThreadSafetyMode.NONE) { 13 | // getClassData 很便宜,不需要前置 14 | dexKit.getClassData("com.miui.appmanager.fragment.ApplicationsDetailsFragment") ?: 15 | dexKit.getClassData("com.miui.appmanager.ApplicationsDetailsActivity")!! 16 | } 17 | 18 | /** LiveData 读取后更新 View 的方法 */ 19 | val appDetails_OnLoadDataFinishMethod by lazy(LazyThreadSafetyMode.NONE) { 20 | // 21 | // public void a(a.j.b.c cVar, Boolean bool) { // <- a 22 | // …… 23 | // if (this.k0) { 24 | // appDetailTextBannerView = this.p; 25 | // i2 = R.string.app_manager_default_open_summary; 26 | // } else { 27 | // appDetailTextBannerView = this.p; 28 | // i2 = R.string.app_manager_default_close_summary; 29 | // } 30 | logSearch("appDetails_onLoadDataFinished", { it.method }) { 31 | appDetailsView.findMethod { 32 | matcher { 33 | addEqString("enter_way") 34 | returnType = "void" 35 | paramTypes = listOf("", "") 36 | } 37 | findFirst = true 38 | }.single() 39 | } 40 | } 41 | 42 | /** "联网控制" 按钮生成 Summary 文本的方法 */ 43 | val appDetails_genNetCtrlSummaryMethod by lazy(LazyThreadSafetyMode.NONE) { 44 | // 45 | // this.m.setSummary(L()); // <- L 46 | // ... 47 | // L() { 48 | // if (this.o0) { // o0 isSystemApp 49 | // i2 = C0412R.string.app_manager_system_mobile_disable; 50 | // } else if (!this.q0) { 51 | // i2 = C0412R.string.app_manager_disable; 52 | // } 53 | // ... 54 | logSearch("appDetails_genNetCtrlSummaryMethod", { it.method }) { 55 | appDetailsView.findMethod { 56 | matcher { 57 | opCodes(listOf(0x55, 0x39, 0x55, 0x39, 0x55, 0x38), OpCodeMatchType.StartsWith) 58 | returnType = "java.lang.String" 59 | paramTypes = listOf() 60 | } 61 | }.single() 62 | } 63 | } 64 | 65 | /** "联网控制" 按钮 onClick 处理方法 */ 66 | val appDetails_netCtrlShowDialogMethod by lazy(LazyThreadSafetyMode.NONE) { 67 | // 68 | // Z(); // <- Z 69 | // str = "network_control"; 70 | logSearch("appDetails_netCtrlShowDialogMethod", { it.method }) { 71 | appDetailsView.findMethod { 72 | matcher { 73 | opCodes(listOf(0x55, 0x5c, 0x55, 0x5c, 0x55, 0x5c), OpCodeMatchType.StartsWith) 74 | returnType = "void" 75 | paramTypes = listOf() 76 | } 77 | }.single() 78 | } 79 | } 80 | 81 | /** "是否是系统应用" 字段 */ 82 | val appDetails_isSystemAppField by lazy(LazyThreadSafetyMode.NONE) { 83 | appDetails_genNetCtrlSummaryMethod // 依赖项前置 84 | 85 | // 86 | // initView() { 87 | // ... 88 | // this.o0 = (applicationInfo.flags & 1) != 0 89 | // ... 90 | logSearch("appDetails_isSystemAppField", { it.name }) { 91 | appDetailsView.findField { 92 | matcher { 93 | addWriteMethod("Lcom/miui/appmanager/ApplicationsDetailsActivity;->initView()V") 94 | addReadMethod(appDetails_genNetCtrlSummaryMethod.descriptor) 95 | type = "boolean" 96 | } 97 | }.single() 98 | } 99 | } 100 | 101 | private val behavior_FindMethodUsingStrings by lazy(LazyThreadSafetyMode.NONE) { 102 | logSearch("behavior_batchFindMethodUsingStrings", { "Success" }) { 103 | dexKit.batchFindMethodUsingStrings { 104 | addSearchGroup { 105 | // 106 | // public static void a(Context context, boolean z) { 107 | // Set set; 108 | // String str; 109 | // List a2 = a(context); // getCloudBehaviorWhite 云端白名单 110 | // if (a2 != null && a2.size() > 0) { 111 | // ... 112 | // } 113 | // // 7.9.? 不再有此条日志,故查找 getCloudBehaviorWhite caller 114 | // Log.i("BehaviorRecord-Utils", "initTolerateApps by cloud success"); 115 | // } 116 | // } 117 | // b(context); // behavior_record_white.csv 本地白名单 118 | // } 119 | groupName = "getListForBehaviorWhite" 120 | usingStrings( 121 | listOf( 122 | "content://com.miui.sec.THIRD_DESKTOP", "getListForBehaviorWhite" 123 | ), StringMatchType.Equals 124 | ) 125 | } 126 | 127 | addSearchGroup { 128 | // shouldIgnore, 判断行为是否应被忽略 129 | // 130 | // Log.d("Enterprise", "Package " + str + "is ignored"); 131 | // ... 132 | // return !com.miui.permcenter.privacymanager.l.c.a(context) ? UserHandle.getAppId(a2.applicationInfo.uid) < 10000 || (a2.applicationInfo.flags & 1) != 0 : !c(context, str2); 133 | 134 | groupName = "shouldIgnore" 135 | usingStrings( 136 | listOf("Enterprise", "Package ", "is ignored"), StringMatchType.Equals 137 | ) 138 | } 139 | } 140 | }.also { 141 | dlog("behavior_getListForBehaviorWhite: " + it["getListForBehaviorWhite"]!!.single().descriptor) 142 | dlog("behavior_shouldIgnore: " + it["shouldIgnore"]!!.single().descriptor) 143 | } 144 | } 145 | 146 | /** initTolerateApps, 初始化容忍应用 */ 147 | val behavior_initTolerateAppsMethod by lazy(LazyThreadSafetyMode.NONE) { 148 | getListForBehaviorWhite // 依赖项前置 149 | 150 | logSearch("behavior_find_getListForBehaviorWhite_caller", { it.descriptor }) { 151 | getListForBehaviorWhite.declaredClass!!.findMethod { 152 | matcher { 153 | addInvoke(getListForBehaviorWhite.descriptor) 154 | } 155 | }.single() 156 | } 157 | } 158 | 159 | private inline val getListForBehaviorWhite 160 | get() = behavior_FindMethodUsingStrings["getListForBehaviorWhite"]!!.single() 161 | 162 | val behavior_shouldIgnore 163 | get() = behavior_FindMethodUsingStrings["shouldIgnore"]!!.single() 164 | 165 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/systemui/NotificationClickInfoItemStartChannelSetting.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.systemui 2 | 3 | import android.content.Context 4 | import android.content.ContextHidden 5 | import android.content.ContextWrapper 6 | import android.content.Intent 7 | import android.os.Build 8 | import android.os.Bundle 9 | import android.os.UserHandle 10 | import android.os.UserHandleHidden 11 | import android.provider.Settings 12 | import android.service.notification.StatusBarNotification 13 | import de.robv.android.xposed.XC_MethodHook 14 | import de.robv.android.xposed.XC_MethodReplacement 15 | import de.robv.android.xposed.XposedBridge 16 | import de.robv.android.xposed.XposedHelpers 17 | import dev.rikka.tools.refine.Refine 18 | import io.github.chsbuffer.miuihelper.model.Hook 19 | import io.github.chsbuffer.miuihelper.model.ScopedHook 20 | import org.luckypray.dexkit.DexKitBridge 21 | import org.luckypray.dexkit.result.MethodData 22 | 23 | /* 24 | * 更改记录: 25 | * v1: MIUI 12 到 HyperOS 1.0 (A13) 26 | * v2: HyperOS 1.0(A14),r8 重拳出击,合并 lambda, 内联helper函数,呕 27 | * */ 28 | class NotificationClickInfoItemStartChannelSetting(val dexKit: DexKitBridge) : Hook() { 29 | 30 | override fun init() { 31 | if (!xPrefs.getBoolean("notification_channel_setting", false)) return 32 | 33 | val startAppNotificationSettingsMethod = dexKit.findMethod { 34 | matcher { 35 | addEqString("startAppNotificationSettings pkg=%s label=%s uid=%s") 36 | } 37 | }.single() 38 | 39 | if (startAppNotificationSettingsMethod.methodName != "onClick") { 40 | hookV1() 41 | } else { 42 | hookV2(startAppNotificationSettingsMethod) 43 | } 44 | } 45 | 46 | private fun hookV1() { 47 | var context: Context? = null 48 | var sbn: StatusBarNotification? = null 49 | 50 | XposedHelpers.findAndHookMethod("com.android.systemui.statusbar.notification.row.MiuiNotificationMenuRow", 51 | classLoader, 52 | "onClickInfoItem", 53 | Context::class.java, 54 | "com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin\$MenuItem", 55 | object : XC_MethodHook() { 56 | override fun beforeHookedMethod(param: MethodHookParam) { 57 | sbn = XposedHelpers.getObjectField( 58 | param.thisObject, "mSbn" 59 | ) as StatusBarNotification? 60 | context = XposedHelpers.getObjectField( 61 | param.thisObject, "mContext" 62 | ) as Context 63 | } 64 | }) 65 | 66 | XposedHelpers.findAndHookMethod("com.android.systemui.statusbar.notification.NotificationSettingsHelper", 67 | classLoader, 68 | "startAppNotificationSettings", 69 | Context::class.java, 70 | String::class.java, 71 | String::class.java, 72 | Int::class.javaPrimitiveType, 73 | String::class.java, 74 | object : XC_MethodReplacement() { 75 | @Suppress("SameReturnValue") 76 | override fun replaceHookedMethod(param: MethodHookParam): Any? { 77 | if (sbn!!.packageName == "com.miui.hybrid") return null 78 | 79 | val intent = startAppNotificationChannelSetting(sbn!!) 80 | Refine.unsafeCast(context) 81 | .startActivityAsUser(intent, UserHandleHidden.CURRENT) 82 | return null 83 | } 84 | }) 85 | } 86 | 87 | private fun hookV2(startAppNotificationSettingsMethod: MethodData) { 88 | val startAppNotificationSettingsMethodInstance = 89 | startAppNotificationSettingsMethod.getMethodInstance(classLoader) 90 | // compiler merged closures. hope won't break soon. 91 | val r8Class = startAppNotificationSettingsMethod.declaredClass!! 92 | val menuRowField = 93 | r8Class.fields.single { it.typeName == "com.android.systemui.statusbar.notification.row.MiuiNotificationMenuRow" } 94 | 95 | var menuRow: Any? 96 | var context: Context? = null 97 | var sbn: StatusBarNotification? = null 98 | 99 | XposedBridge.hookMethod( 100 | startAppNotificationSettingsMethodInstance, 101 | object : XC_MethodHook() { 102 | override fun beforeHookedMethod(param: MethodHookParam) { 103 | menuRow = XposedHelpers.getObjectField(param.thisObject, menuRowField.fieldName) 104 | context = XposedHelpers.getObjectField(menuRow, "mContext") as Context 105 | sbn = XposedHelpers.getObjectField(menuRow, "mSbn") as StatusBarNotification 106 | } 107 | }) 108 | 109 | val startActivityAsUser = XposedHelpers.findMethodExact( 110 | ContextWrapper::class.java, 111 | "startActivityAsUser", 112 | Intent::class.java, 113 | UserHandle::class.java 114 | ) 115 | 116 | XposedBridge.hookMethod( 117 | startAppNotificationSettingsMethodInstance, 118 | ScopedHook(startActivityAsUser, object : XC_MethodReplacement() { 119 | override fun replaceHookedMethod(param: MethodHookParam) { 120 | if (sbn!!.packageName == "com.miui.hybrid") return 121 | 122 | val intent = startAppNotificationChannelSetting(sbn) 123 | XposedBridge.invokeOriginalMethod( 124 | param.method, context, arrayOf(intent, UserHandleHidden.CURRENT) 125 | ) 126 | } 127 | }) 128 | ) 129 | } 130 | 131 | private fun startAppNotificationChannelSetting( 132 | sbn: StatusBarNotification 133 | ): Intent { 134 | val uid = XposedHelpers.getIntField(sbn, "mAppUid") 135 | val pkgName = sbn.packageName 136 | val channelId = sbn.notification.channelId 137 | val intent: Intent = Intent(Intent.ACTION_MAIN).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 138 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).setClassName( 139 | "com.android.settings", "com.android.settings.SubSettings" 140 | ).putExtra( 141 | // miui 自己修改了标准的 .notification.app 下的 ChannelNotificationSettings 代码后, 142 | // 拷贝了一份到 .notification 又稍作修改。在系统设置中实际使用了 .notification 的。 143 | // 并且"显示通知渠道设置"的 Xposed 模块作用的仅是 .notification 的。 144 | ":android:show_fragment", 145 | "com.android.settings.notification.ChannelNotificationSettings" 146 | ).putExtra(Settings.EXTRA_APP_PACKAGE, pkgName) 147 | .putExtra(Settings.EXTRA_CHANNEL_ID, channelId) 148 | .putExtra("app_uid", uid) // miui non-standard dual apps shit 149 | 150 | // workaround for miui 12.5, 金凡!!! 151 | val bundle = Bundle() 152 | bundle.putString(Settings.EXTRA_CHANNEL_ID, channelId) 153 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 154 | bundle.putString(Settings.EXTRA_CONVERSATION_ID, sbn.notification.shortcutId) 155 | } 156 | intent.putExtra(":android:show_fragment_args", bundle) 157 | 158 | return intent 159 | } 160 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/root_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 18 | 25 | 32 | 37 | 43 | 44 | 45 | 50 | 56 | 61 | 62 | 63 | 67 | 72 | 77 | 83 | 89 | 90 | 91 | 96 | 97 | 98 | 103 | 109 | 110 | 111 | 117 | 118 | 119 | 125 | 126 | 127 | 130 | 133 | 134 | 137 | 140 | 141 | 145 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/chsbuffer/miuihelper/hooks/securitycenter/AppDetailsStockOpenDefaultSettings.kt: -------------------------------------------------------------------------------- 1 | package io.github.chsbuffer.miuihelper.hooks.securitycenter 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.app.Application 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.pm.verify.domain.DomainVerificationManager 9 | import android.net.Uri 10 | import android.os.Build 11 | import android.provider.Settings 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import android.widget.LinearLayout 15 | import android.widget.TextView 16 | import androidx.annotation.RequiresApi 17 | import androidx.preference.Preference 18 | import androidx.preference.PreferenceFragmentCompat 19 | import de.robv.android.xposed.XC_MethodHook 20 | import de.robv.android.xposed.XposedBridge 21 | import de.robv.android.xposed.XposedHelpers 22 | import io.github.chsbuffer.miuihelper.BuildConfig 23 | import io.github.chsbuffer.miuihelper.R 24 | import io.github.chsbuffer.miuihelper.model.Hook 25 | import io.github.chsbuffer.miuihelper.util.dlog 26 | import miuix.preference.TextPreference 27 | 28 | /* 29 | * 修改历史: 30 | * v1: 6.x.x~7.x.x 针对 Activity 的【清除默认设置 am_detail_default: AppDetailTextBannerView】 重设文本,hook onClick 方法 31 | * v2: 7.x.x 后期, 【清除默认设置】 被删除,自行构建 AppDetailTextBannerView 32 | * v3: 10.x.x, 针对 androidx PreferenceFragmentCompat 的 app_default_pref,hook onPreferenceClick;原 Activity hook onClick 改为设置 onClickListener, 33 | * */ 34 | @SuppressLint("NewApi") 35 | class AppDetailsStockOpenDefaultSettings(val dexKitCache: DexKitCache, val app: Application) : 36 | Hook() { 37 | val domainVerificationManager: DomainVerificationManager by lazy(LazyThreadSafetyMode.NONE) { 38 | app.getSystemService( 39 | DomainVerificationManager::class.java 40 | ) 41 | } 42 | val moduleContext: Context by lazy(LazyThreadSafetyMode.NONE) { 43 | app.createPackageContext( 44 | BuildConfig.APPLICATION_ID, 0 45 | ) 46 | } 47 | 48 | fun getOpenDefaultState(pkgName: String): String { 49 | val isLinkHandlingAllowed = domainVerificationManager.getDomainVerificationUserState( 50 | pkgName 51 | )?.isLinkHandlingAllowed ?: false 52 | val subTextId = 53 | if (isLinkHandlingAllowed) R.string.app_link_open_always else R.string.app_link_open_never 54 | return moduleContext.getString(subTextId) 55 | } 56 | 57 | fun getOpenDefaultTitle(): String = moduleContext.getString(R.string.open_by_default) 58 | 59 | companion object { 60 | @JvmStatic 61 | fun OpenDefaultOnClick(activity: Activity) { 62 | val pkgName = activity.intent.getStringExtra("package_name")!! 63 | dlog("open default: $pkgName") 64 | 65 | val intent = Intent().apply { 66 | action = Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS 67 | addCategory(Intent.CATEGORY_DEFAULT) 68 | data = Uri.parse("package:${pkgName}") 69 | addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) 70 | addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) 71 | } 72 | activity.startActivity(intent) 73 | } 74 | } 75 | 76 | override fun init() { 77 | if (!xPrefs.getBoolean( 78 | "original_default_open_setting", true 79 | ) 80 | ) return 81 | 82 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return 83 | 84 | val appDetailsView = dexKitCache.appDetailsView.getInstance(classLoader) 85 | 86 | if (appDetailsView.isAssignableFrom(Activity::class.java)) { 87 | // v1, v2 88 | XposedBridge.hookMethod(dexKitCache.appDetails_OnLoadDataFinishMethod.getMethodInstance( 89 | classLoader 90 | ), object : XC_MethodHook() { 91 | override fun afterHookedMethod(param: MethodHookParam) { 92 | handleActivityOnLoadDataFinish(param.thisObject as Activity) 93 | } 94 | }) 95 | } else { 96 | // v3 97 | XposedBridge.hookMethod(dexKitCache.appDetails_OnLoadDataFinishMethod.getMethodInstance( 98 | classLoader 99 | ), object : XC_MethodHook() { 100 | override fun afterHookedMethod(param: MethodHookParam) { 101 | handleFragmentOnLoadDataFinish(param.thisObject as PreferenceFragmentCompat) 102 | } 103 | }) 104 | 105 | injectClassLoader() 106 | XposedHelpers.findAndHookMethod(appDetailsView, 107 | "onPreferenceClick", 108 | "androidx.preference.Preference", 109 | object : XC_MethodHook() { 110 | override fun beforeHookedMethod(param: MethodHookParam) { 111 | val pref = param.args[0] as Preference 112 | if (pref.key == "app_default_pref") { 113 | val prefFrag = param.thisObject as PreferenceFragmentCompat 114 | OpenDefaultOnClick(prefFrag.requireActivity()) 115 | param.result = true 116 | } 117 | } 118 | }) 119 | } 120 | } 121 | 122 | // v1, v2 123 | fun handleActivityOnLoadDataFinish(activity: Activity) { 124 | var openDefaultView: View? = null 125 | val default_id = app.resources.getIdentifier( 126 | "am_detail_default", "id", app.packageName 127 | ) 128 | if (default_id != 0) { 129 | openDefaultView = activity.findViewById(default_id) 130 | } 131 | // v2 132 | openDefaultView = openDefaultView ?: createOpenDefaultView(activity) 133 | openDefaultView.setOnClickListener { OpenDefaultOnClick(activity) } 134 | 135 | val pkgName = activity.intent.getStringExtra("package_name")!! 136 | // 加载完毕数据后,修改“清除默认操作”按钮标题和描述为“默认打开” 137 | setOpenDefaultViewText(openDefaultView, pkgName) 138 | } 139 | 140 | // v2 141 | fun createOpenDefaultView(activity: Activity): View { 142 | val appmanagerTextBannerViewClass = XposedHelpers.findClass( 143 | "com.miui.appmanager.widget.AppDetailTextBannerView", classLoader 144 | ) 145 | 146 | val anotherTextBannerId = app.resources.getIdentifier( 147 | "am_global_perm", "id", app.packageName 148 | ) 149 | val anotherTextBanner = activity.findViewById(anotherTextBannerId) 150 | 151 | val attributeSet = null 152 | val defaultView = XposedHelpers.newInstance( 153 | appmanagerTextBannerViewClass, activity, attributeSet 154 | ) as LinearLayout 155 | copyLinearLayoutStyle(defaultView, anotherTextBanner) 156 | 157 | val insertAfterViewId = app.resources.getIdentifier("am_full_screen", "id", app.packageName) 158 | val insertAfterView = activity.findViewById(insertAfterViewId) 159 | val viewGroup = insertAfterView.parent as ViewGroup 160 | viewGroup.addView(defaultView, viewGroup.indexOfChild(insertAfterView) + 1) 161 | 162 | return defaultView 163 | } 164 | 165 | // v2 166 | private fun copyLinearLayoutStyle(thiz: LinearLayout, that: LinearLayout) { 167 | thiz.layoutParams = that.layoutParams 168 | thiz.minimumHeight = that.minimumHeight 169 | thiz.background = that.background 170 | 171 | thiz.setPadding( 172 | that.paddingLeft, that.paddingTop, that.paddingRight, that.paddingBottom 173 | ) 174 | thiz.gravity = that.gravity 175 | thiz.orientation = that.orientation 176 | } 177 | 178 | // v1, v2 179 | @RequiresApi(Build.VERSION_CODES.S) 180 | fun setOpenDefaultViewText(cleanDefaultView: View, pkgName: String) { 181 | // set title 182 | // 因为 AppDetailTextBannerView 没有 setTitle 方法, 183 | // 所以先将分别作为 Title 和 Summary 的两个 TextView 的文本都设为 "Open by default" 184 | // 之后再调用 setSummary 设置 Summary 的 TextView 185 | cleanDefaultView::class.java.declaredFields.forEach { 186 | val textView = XposedHelpers.getObjectField(cleanDefaultView, it.name) 187 | if (textView !is TextView) return@forEach 188 | 189 | XposedHelpers.callMethod( 190 | textView, "setText", arrayOf(CharSequence::class.java), getOpenDefaultTitle() 191 | ) 192 | } 193 | 194 | // set summary 195 | XposedHelpers.callMethod( 196 | cleanDefaultView, "setSummary", getOpenDefaultState(pkgName) 197 | ) 198 | } 199 | 200 | // v3 201 | fun handleFragmentOnLoadDataFinish(prefFrag: PreferenceFragmentCompat) { 202 | val activity = prefFrag.requireActivity() 203 | val pkgName = activity.intent.getStringExtra("package_name")!! 204 | val pref: TextPreference = prefFrag.findPreference("app_default_pref")!! 205 | // 加载完毕数据后,修改“清除默认操作”按钮标题和描述为“默认打开” 206 | pref.title = getOpenDefaultTitle() 207 | pref.summary = getOpenDefaultState(pkgName) 208 | dlog("handleFragment: $pkgName") 209 | } 210 | 211 | // v3, 为了模块加载宿主 androidx 和 miuix 212 | @SuppressLint("DiscouragedPrivateApi") 213 | fun injectClassLoader() { 214 | val self = this::class.java.classLoader!! 215 | val loader = self.parent 216 | val host = classLoader 217 | val sBootClassLoader: ClassLoader = Context::class.java.classLoader!! 218 | 219 | val fParent = ClassLoader::class.java.getDeclaredField("parent") 220 | fParent.setAccessible(true) 221 | fParent.set(self, object : ClassLoader(sBootClassLoader) { 222 | 223 | override fun findClass(name: String?): Class<*> { 224 | dlog("findClass $name") 225 | try { 226 | return sBootClassLoader.loadClass(name) 227 | } catch (ignored: ClassNotFoundException) { 228 | } 229 | 230 | try { 231 | return loader.loadClass(name) 232 | } catch (ignored: ClassNotFoundException) { 233 | } 234 | try { 235 | return host.loadClass(name) 236 | } catch (ignored: ClassNotFoundException) { 237 | } 238 | 239 | throw ClassNotFoundException(name); 240 | } 241 | }) 242 | } 243 | } --------------------------------------------------------------------------------