├── 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 |

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 | }
--------------------------------------------------------------------------------