Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion FreeNotifications/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android {

defaultConfig {
minSdk = 26
targetSdk = 33
targetSdk = 36
}
}

Expand Down
1 change: 1 addition & 0 deletions FreeNotifications/src/main/assets/xposed_init
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
de.binarynoise.freeNotifications.Hook
de.binarynoise.freeNotifications.SettingsHook
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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<String>::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),
)
}
}
}
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down