From 893af7479edf1838e337de277099d159ebd9a680 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:10:41 +0100 Subject: [PATCH 1/5] fix FreeNotifications for older android versions --- FreeNotifications/build.gradle.kts | 2 +- .../de/binarynoise/freeNotifications/Hook.kt | 116 +++++++++++++----- 2 files changed, 89 insertions(+), 29 deletions(-) diff --git a/FreeNotifications/build.gradle.kts b/FreeNotifications/build.gradle.kts index 822a0df..6a4d634 100644 --- a/FreeNotifications/build.gradle.kts +++ b/FreeNotifications/build.gradle.kts @@ -8,7 +8,7 @@ android { defaultConfig { minSdk = 26 - targetSdk = 33 + targetSdk = 36 } } diff --git a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt index b2f5015..66c2799 100644 --- a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt +++ b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt @@ -1,6 +1,8 @@ package de.binarynoise.freeNotifications import android.app.NotificationChannel +import android.app.NotificationChannelGroup +import android.os.Build import de.binarynoise.logger.Logger.log import de.binarynoise.reflection.findDeclaredField import de.robv.android.xposed.IXposedHookLoadPackage @@ -14,53 +16,111 @@ import de.robv.android.xposed.XC_MethodHook as MethodHook class Hook : IXposedHookLoadPackage { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - // AOSP: mBlockableSystem / setBlockable / isBlockable - val cls = NotificationChannel::class.java + fun hookVariable( + clazz: Class<*>, + variableName: String, + setMethodName: String, + getMethodName: String, + value: Boolean, + ) { + tryAndLog("${clazz.simpleName} after constructor $variableName") { + val mVariable = clazz.findDeclaredField(variableName) + XposedBridge.hookAllConstructors(clazz, object : MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + mVariable.set(param.thisObject, value) + } + }) + } + tryAndLog("${clazz.simpleName} $getMethodName") { + XposedHelpers.findAndHookMethod(clazz, getMethodName, returnConstant(value)) + } + tryAndLog("${clazz.simpleName} $setMethodName") { + XposedHelpers.findAndHookMethod(clazz, setMethodName, Boolean::class.java, DO_NOTHING) + } + } - val mBlockableSystem = cls.findDeclaredField("mBlockableSystem") + fun String.isCharUpperCase(index: Int): Boolean { + return this[index] == this[index].uppercaseChar() + } - tryAndLog("hook NotificationChannel constructors") { - XposedBridge.hookAllConstructors(cls, object : MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - mBlockableSystem.set(param.thisObject, true) - } - }) + /** + * Remove prefix only if it is used in a CamelCase sentence, + * only if the letter following the prefix is uppercase. + */ + fun String.removeCamelCasePrefix(prefix: String): String { + if (!this.isCharUpperCase(prefix.length + 1)) return this.removePrefix(prefix) + return this } - tryAndLog("hook setBlockable") { - XposedHelpers.findAndHookMethod(cls, "setBlockable", Boolean::class.java, DO_NOTHING) + + fun sanitizeVariableName(name: String): String { + return name.removeCamelCasePrefix("m") + .removeCamelCasePrefix("set") + .removeCamelCasePrefix("is") + .removeCamelCasePrefix("get") + .replaceFirstChar { it.uppercaseChar() } } - tryAndLog("hook isBlockable") { - XposedHelpers.findAndHookMethod(cls, "isBlockable", returnConstant(true)) + + fun hookVariable(clazz: Class<*>, variableName: String, functionName: String, value: Boolean) { + val sanitizedVariableName = sanitizeVariableName(variableName) + val sanitizedFunctionName = sanitizeVariableName(functionName) + return hookVariable( + clazz, + "m$sanitizedVariableName", + "set$sanitizedFunctionName", + "is$sanitizedFunctionName", + value, + ) } - // AOSP: mImportanceLockedDefaultApp / setImportanceLockedByCriticalDeviceFunction / isImportanceLockedByCriticalDeviceFunction + fun hookVariable(clazz: Class<*>, name: String, value: Boolean) { + return hookVariable(clazz, name, name, value) + } - val mImportanceLockedDefaultApp = cls.findDeclaredField("mImportanceLockedDefaultApp") + // AOSP + hookVariable( + NotificationChannel::class.java, + "mBlockableSystem", + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) "setBlockable" else "setBlockableSystem", + true, + ) - tryAndLog("hook constructors") { - XposedBridge.hookAllConstructors(cls, object : MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - mImportanceLockedDefaultApp.set(param.thisObject, false) - } - }) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // AOSP + hookVariable( + NotificationChannel::class.java, + "mImportanceLockedDefaultApp", + "setImportanceLockedByCriticalDeviceFunction", + false, + ) } - tryAndLog("hook setImportanceLockedByCriticalDeviceFunction") { - XposedHelpers.findAndHookMethod(cls, "setImportanceLockedByCriticalDeviceFunction", Boolean::class.java, DO_NOTHING) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + // AOSP + hookVariable( + NotificationChannel::class.java, + "mImportanceLockedByOEM", + false, + ) } - tryAndLog("hook isImportanceLockedByCriticalDeviceFunction") { - XposedHelpers.findAndHookMethod(cls, "isImportanceLockedByCriticalDeviceFunction", returnConstant(false)) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // AOSP + hookVariable( + NotificationChannelGroup::class.java, + "mBlocked", + false, + ) } } } private inline fun tryAndLog(message: String, block: () -> Unit) { - log(message) return try { block() - log("done!") + log("hook $message successful!") } catch (t: Throwable) { - log("failed!") + log("hook $message failed!", t) XposedBridge.log(t) } } From 99f4ce9d30f88ff3219c5a449d69c1896eb83dda Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:18:45 +0100 Subject: [PATCH 2/5] FreeNotifications: hook notification settings this enables the app-level master switch --- FreeNotifications/src/main/assets/xposed_init | 1 + .../de/binarynoise/freeNotifications/Hook.kt | 2 +- .../freeNotifications/SettingsHook.kt | 182 ++++++++++++++++++ 3 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt diff --git a/FreeNotifications/src/main/assets/xposed_init b/FreeNotifications/src/main/assets/xposed_init index 41bf3bd..7d975f9 100644 --- a/FreeNotifications/src/main/assets/xposed_init +++ b/FreeNotifications/src/main/assets/xposed_init @@ -1 +1,2 @@ de.binarynoise.freeNotifications.Hook +de.binarynoise.freeNotifications.SettingsHook diff --git a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt index 66c2799..820896c 100644 --- a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt +++ b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt @@ -115,7 +115,7 @@ class Hook : IXposedHookLoadPackage { } } -private inline fun tryAndLog(message: String, block: () -> Unit) { +inline fun tryAndLog(message: String, block: () -> Unit) { return try { block() log("hook $message successful!") diff --git a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt new file mode 100644 index 0000000..968dd88 --- /dev/null +++ b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt @@ -0,0 +1,182 @@ +package de.binarynoise.freeNotifications + +import android.app.NotificationChannel +import android.app.NotificationChannelGroup +import android.app.role.RoleManager +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XC_MethodReplacement +import de.robv.android.xposed.XC_MethodReplacement.DO_NOTHING +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers +import de.robv.android.xposed.callbacks.XC_LoadPackage + +class SettingsHook : IXposedHookLoadPackage { + override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { + if (lpparam.packageName != "com.android.settings") return + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val NotificationPreferenceControllerClass = Class.forName( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) "com.android.settings.notification.app.NotificationPreferenceController" + else "com.android.settings.notification.NotificationPreferenceController", + false, + lpparam.classLoader, + ) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + tryAndLog("isAppBlockable") { + XposedHelpers.findAndHookMethod( + NotificationPreferenceControllerClass, + "isAppBlockable", + XC_MethodReplacement.returnConstant(true), + ) + } + tryAndLog("${NotificationPreferenceControllerClass.simpleName} constructor overrideBlock overrideConfigure") { + XposedBridge.hookAllConstructors( + NotificationPreferenceControllerClass, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + listOf("Block", "Configure").forEach { + XposedHelpers.setBooleanField(param.thisObject, "overrideCan$it", true) + XposedHelpers.setBooleanField(param.thisObject, "overrideCan${it}Value", true) + } + } + }, + ) + } + tryAndLog("setOverrideCanBlock") { + XposedHelpers.findAndHookMethod( + NotificationPreferenceControllerClass, + "setOverrideCanBlock", + Boolean::class.java, + DO_NOTHING, + ) + } + tryAndLog("setOverrideCanConfigure") { + XposedHelpers.findAndHookMethod( + NotificationPreferenceControllerClass, + "setOverrideCanConfigure", + Boolean::class.java, + DO_NOTHING, + ) + } + } + + tryAndLog("isChannelBlockable") { + XposedHelpers.findAndHookMethod( + NotificationPreferenceControllerClass, + "isChannelBlockable", + XC_MethodReplacement.returnConstant(true), + ) + } + tryAndLog("isChannelBlockable") { + XposedHelpers.findAndHookMethod( + NotificationPreferenceControllerClass, + "isChannelBlockable", + NotificationChannel::class.java, + XC_MethodReplacement.returnConstant(true), + ) + } + tryAndLog("isChannelConfigurable") { + XposedHelpers.findAndHookMethod( + NotificationPreferenceControllerClass, + "isChannelConfigurable", + NotificationChannel::class.java, + XC_MethodReplacement.returnConstant(true), + ) + } + tryAndLog("isChannelGroupBlockable") { + XposedHelpers.findAndHookMethod( + NotificationPreferenceControllerClass, + "isChannelGroupBlockable", + XC_MethodReplacement.returnConstant(true), + ) + } + tryAndLog("isChannelGroupBlockable") { + XposedHelpers.findAndHookMethod( + NotificationPreferenceControllerClass, + "isChannelGroupBlockable", + NotificationChannelGroup::class.java, + XC_MethodReplacement.returnConstant(true), + ) + } + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + val NotificationBackendClass = Class.forName("com.android.settings.notification.NotificationBackend", false, lpparam.classLoader) + val AppRowClass = Class.forName(NotificationBackendClass.name + "\$AppRow", false, lpparam.classLoader) + tryAndLog("loadAppRow") { + NotificationBackendClass.declaredMethods.filter { it.name == "loadAppRow" }.forEach { method -> + XposedBridge.hookMethod( + method, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val row = param.result + XposedHelpers.setBooleanField(row, "systemApp", false) + } + }, + ) + } + } + tryAndLog("recordCanBeBlocked") { + XposedHelpers.findAndHookMethod( + NotificationBackendClass, + "recordCanBeBlocked", + Context::class.java, + PackageManager::class.java, + RoleManager::class.java, + PackageInfo::class.java, + AppRowClass, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val row = param.args[4] + XposedHelpers.setBooleanField(row, "systemApp", false) + } + }, + ) + } + tryAndLog("markAppRowWithBlockables") { + XposedHelpers.findAndHookMethod( + NotificationBackendClass, + "markAppRowWithBlockables", + Array::class.java, + AppRowClass, + String::class.java, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val row = param.args[1] + XposedHelpers.setBooleanField(row, "systemApp", false) + } + }, + ) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + tryAndLog("isSystemApp") { + XposedHelpers.findAndHookMethod( + NotificationBackendClass, + "isSystemApp", + Context::class.java, + ApplicationInfo::class.java, + XC_MethodReplacement.returnConstant(false), + ) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + tryAndLog("isBlockable") { + XposedHelpers.findAndHookMethod( + NotificationBackendClass, + "isBlockable", + Context::class.java, + ApplicationInfo::class.java, + XC_MethodReplacement.returnConstant(true), + ) + } + } + } + } +} From cc94dde98d052d350e160c8fc154cce57733ecf9 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Sun, 7 Dec 2025 21:14:40 +0100 Subject: [PATCH 3/5] fix method hook for `recordCanBeBlocked` in FreeNotifications --- .../freeNotifications/SettingsHook.kt | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt index 968dd88..6e51248 100644 --- a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt +++ b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt @@ -2,11 +2,8 @@ package de.binarynoise.freeNotifications import android.app.NotificationChannel import android.app.NotificationChannelGroup -import android.app.role.RoleManager import android.content.Context import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo -import android.content.pm.PackageManager import android.os.Build import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XC_MethodHook @@ -123,23 +120,6 @@ class SettingsHook : IXposedHookLoadPackage { ) } } - tryAndLog("recordCanBeBlocked") { - XposedHelpers.findAndHookMethod( - NotificationBackendClass, - "recordCanBeBlocked", - Context::class.java, - PackageManager::class.java, - RoleManager::class.java, - PackageInfo::class.java, - AppRowClass, - object : XC_MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - val row = param.args[4] - XposedHelpers.setBooleanField(row, "systemApp", false) - } - }, - ) - } tryAndLog("markAppRowWithBlockables") { XposedHelpers.findAndHookMethod( NotificationBackendClass, @@ -156,6 +136,17 @@ class SettingsHook : IXposedHookLoadPackage { ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + tryAndLog("recordCanBeBlocked") { + XposedBridge.hookMethod( + NotificationBackendClass.declaredMethods.single { it.name == "recordCanBeBlocked" }, + object : XC_MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val row = param.args.last() + XposedHelpers.setBooleanField(row, "systemApp", false) + } + }, + ) + } tryAndLog("isSystemApp") { XposedHelpers.findAndHookMethod( NotificationBackendClass, From b7ae0f3f155594fee541a5b9888fab80f6257aae Mon Sep 17 00:00:00 2001 From: binarynoise Date: Sun, 7 Dec 2025 21:09:23 +0100 Subject: [PATCH 4/5] add @programminghoch10 to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1341bd3..383ef0f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A collection of small Xposed Modules. | [BetterVerboseWiFiLogging](BetterVerboseWiFiLogging) | [@binarynoise](https://github.com/binarynoise) | Makes the verbose Wi-Fi information more readable | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=betterVerboseWiFiLogging) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.betterVerboseWiFiLogging) | | [CodecMod](CodecMod) | [@programminghoch10](https://github.com/programminghoch10) | Selectively disable audio/video hardware/software encoders/decoders. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=CodecMod) | | [Don'tResetIfBootedAndConnected](DontResetIfBootedAndConnected) | [@binarynoise](https://github.com/binarynoise) | | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=dontResetIfBootedAndConnected) | -| [FreeNotifications](FreeNotifications) | [@binarynoise](https://github.com/binarynoise) | Enables customization for all Notification Channels again | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=freeNotifications) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.freeNotifications) | +| [FreeNotifications](FreeNotifications) | [@binarynoise](https://github.com/binarynoise) & [@programminghoch10](https://github.com/programminghoch10) | Enables customization for all Notification Channels again | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=freeNotifications) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.freeNotifications) | | [GalaxyWearable](GalaxyWearable) | [@programminghoch10](https://github.com/programminghoch10) | Enables running Samsung's GalaxyWearable app and compantions on modded Samsung devices. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=GalaxyWearable) | | [MotionEventMod](MotionEventMod) | [@programminghoch10](https://github.com/programminghoch10) | Disable touch input for some seconds after the stylus was in use | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=MotionEventMod) | | [MuteSlf4jWarnings](MuteSlf4jWarnings) | [@binarynoise](https://github.com/binarynoise) | Mutes all slf4j warnings | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=muteSlf4jWarnings) | From 2d03de2b071ee003f93b6dea32e4340f0c0ece7f Mon Sep 17 00:00:00 2001 From: binarynoise Date: Sun, 7 Dec 2025 21:09:49 +0100 Subject: [PATCH 5/5] fix off-by-one --- .../de/binarynoise/freeNotifications/Hook.kt | 116 ++++++++---------- 1 file changed, 49 insertions(+), 67 deletions(-) diff --git a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt index 820896c..ca661d7 100644 --- a/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt +++ b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/Hook.kt @@ -16,68 +16,6 @@ import de.robv.android.xposed.XC_MethodHook as MethodHook class Hook : IXposedHookLoadPackage { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - - fun hookVariable( - clazz: Class<*>, - variableName: String, - setMethodName: String, - getMethodName: String, - value: Boolean, - ) { - tryAndLog("${clazz.simpleName} after constructor $variableName") { - val mVariable = clazz.findDeclaredField(variableName) - XposedBridge.hookAllConstructors(clazz, object : MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - mVariable.set(param.thisObject, value) - } - }) - } - tryAndLog("${clazz.simpleName} $getMethodName") { - XposedHelpers.findAndHookMethod(clazz, getMethodName, returnConstant(value)) - } - tryAndLog("${clazz.simpleName} $setMethodName") { - XposedHelpers.findAndHookMethod(clazz, setMethodName, Boolean::class.java, DO_NOTHING) - } - } - - fun String.isCharUpperCase(index: Int): Boolean { - return this[index] == this[index].uppercaseChar() - } - - /** - * Remove prefix only if it is used in a CamelCase sentence, - * only if the letter following the prefix is uppercase. - */ - fun String.removeCamelCasePrefix(prefix: String): String { - if (!this.isCharUpperCase(prefix.length + 1)) return this.removePrefix(prefix) - return this - } - - fun sanitizeVariableName(name: String): String { - return name.removeCamelCasePrefix("m") - .removeCamelCasePrefix("set") - .removeCamelCasePrefix("is") - .removeCamelCasePrefix("get") - .replaceFirstChar { it.uppercaseChar() } - } - - fun hookVariable(clazz: Class<*>, variableName: String, functionName: String, value: Boolean) { - val sanitizedVariableName = sanitizeVariableName(variableName) - val sanitizedFunctionName = sanitizeVariableName(functionName) - return hookVariable( - clazz, - "m$sanitizedVariableName", - "set$sanitizedFunctionName", - "is$sanitizedFunctionName", - value, - ) - } - - fun hookVariable(clazz: Class<*>, name: String, value: Boolean) { - return hookVariable(clazz, name, name, value) - } - - // AOSP hookVariable( NotificationChannel::class.java, "mBlockableSystem", @@ -86,7 +24,6 @@ class Hook : IXposedHookLoadPackage { ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // AOSP hookVariable( NotificationChannel::class.java, "mImportanceLockedDefaultApp", @@ -96,7 +33,6 @@ class Hook : IXposedHookLoadPackage { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - // AOSP hookVariable( NotificationChannel::class.java, "mImportanceLockedByOEM", @@ -105,7 +41,6 @@ class Hook : IXposedHookLoadPackage { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - // AOSP hookVariable( NotificationChannelGroup::class.java, "mBlocked", @@ -113,14 +48,61 @@ class Hook : IXposedHookLoadPackage { ) } } + + fun String.isCharUpperCase(index: Int): Boolean { + return this[index].isUpperCase() + } + + /** + * Remove prefix only if it is used in a CamelCase sentence, + * only if the letter following the prefix is uppercase. + */ + fun String.removeCamelCasePrefix(prefix: String): String { + if (this.isCharUpperCase(prefix.length)) return this.removePrefix(prefix) + return this + } + + fun sanitizeVariableName(name: String): String { + return name.removeCamelCasePrefix("m") + .removeCamelCasePrefix("set") + .removeCamelCasePrefix("is") + .removeCamelCasePrefix("get") + .replaceFirstChar { it.uppercaseChar() } + } + + fun hookVariable(clazz: Class<*>, variableName: String, setMethodName: String, getMethodName: String, value: Boolean) { + tryAndLog("${clazz.simpleName} after constructor $variableName") { + val mVariable = clazz.findDeclaredField(variableName) + XposedBridge.hookAllConstructors(clazz, object : MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + mVariable.set(param.thisObject, value) + } + }) + } + tryAndLog("${clazz.simpleName} $getMethodName") { + XposedHelpers.findAndHookMethod(clazz, getMethodName, returnConstant(value)) + } + tryAndLog("${clazz.simpleName} $setMethodName") { + XposedHelpers.findAndHookMethod(clazz, setMethodName, Boolean::class.java, DO_NOTHING) + } + } + + fun hookVariable(clazz: Class<*>, variableName: String, functionName: String, value: Boolean) { + val sanitizedVariableName = sanitizeVariableName(variableName) + val sanitizedFunctionName = sanitizeVariableName(functionName) + return hookVariable(clazz, "m$sanitizedVariableName", "set$sanitizedFunctionName", "is$sanitizedFunctionName", value) + } + + fun hookVariable(clazz: Class<*>, name: String, value: Boolean) { + return hookVariable(clazz, name, name, value) + } } inline fun tryAndLog(message: String, block: () -> Unit) { - return try { + try { block() log("hook $message successful!") } catch (t: Throwable) { log("hook $message failed!", t) - XposedBridge.log(t) } }