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/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 b2f5015..ca661d7 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,93 @@ import de.robv.android.xposed.XC_MethodHook as MethodHook class Hook : IXposedHookLoadPackage { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - // AOSP: mBlockableSystem / setBlockable / isBlockable + hookVariable( + NotificationChannel::class.java, + "mBlockableSystem", + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) "setBlockable" else "setBlockableSystem", + true, + ) - val cls = NotificationChannel::class.java - - val mBlockableSystem = cls.findDeclaredField("mBlockableSystem") - - tryAndLog("hook NotificationChannel constructors") { - XposedBridge.hookAllConstructors(cls, object : MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - mBlockableSystem.set(param.thisObject, true) - } - }) - } - tryAndLog("hook setBlockable") { - XposedHelpers.findAndHookMethod(cls, "setBlockable", Boolean::class.java, DO_NOTHING) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + hookVariable( + NotificationChannel::class.java, + "mImportanceLockedDefaultApp", + "setImportanceLockedByCriticalDeviceFunction", + false, + ) } - tryAndLog("hook isBlockable") { - XposedHelpers.findAndHookMethod(cls, "isBlockable", returnConstant(true)) - } - - // AOSP: mImportanceLockedDefaultApp / setImportanceLockedByCriticalDeviceFunction / isImportanceLockedByCriticalDeviceFunction - val mImportanceLockedDefaultApp = cls.findDeclaredField("mImportanceLockedDefaultApp") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + hookVariable( + NotificationChannel::class.java, + "mImportanceLockedByOEM", + false, + ) + } - tryAndLog("hook constructors") { - XposedBridge.hookAllConstructors(cls, object : MethodHook() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + hookVariable( + NotificationChannelGroup::class.java, + "mBlocked", + false, + ) + } + } + + 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) { - mImportanceLockedDefaultApp.set(param.thisObject, false) + mVariable.set(param.thisObject, value) } }) } - tryAndLog("hook setImportanceLockedByCriticalDeviceFunction") { - XposedHelpers.findAndHookMethod(cls, "setImportanceLockedByCriticalDeviceFunction", Boolean::class.java, DO_NOTHING) + tryAndLog("${clazz.simpleName} $getMethodName") { + XposedHelpers.findAndHookMethod(clazz, getMethodName, returnConstant(value)) } - tryAndLog("hook isImportanceLockedByCriticalDeviceFunction") { - XposedHelpers.findAndHookMethod(cls, "isImportanceLockedByCriticalDeviceFunction", returnConstant(false)) + 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) + } } -private inline fun tryAndLog(message: String, block: () -> Unit) { - log(message) - return try { +inline fun tryAndLog(message: String, block: () -> Unit) { + try { block() - log("done!") + log("hook $message successful!") } catch (t: Throwable) { - log("failed!") - XposedBridge.log(t) + log("hook $message failed!", t) } } 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..6e51248 --- /dev/null +++ b/FreeNotifications/src/main/java/de/binarynoise/freeNotifications/SettingsHook.kt @@ -0,0 +1,173 @@ +package de.binarynoise.freeNotifications + +import android.app.NotificationChannel +import android.app.NotificationChannelGroup +import android.content.Context +import android.content.pm.ApplicationInfo +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("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("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, + "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), + ) + } + } + } + } +} 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) |