Skip to content

Commit 0b64ebd

Browse files
fix FreeNotifications for older android versions (#38)
1 parent b1f0207 commit 0b64ebd

File tree

5 files changed

+251
-35
lines changed

5 files changed

+251
-35
lines changed

FreeNotifications/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ android {
88

99
defaultConfig {
1010
minSdk = 26
11-
targetSdk = 33
11+
targetSdk = 36
1212
}
1313
}
1414

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
de.binarynoise.freeNotifications.Hook
2+
de.binarynoise.freeNotifications.SettingsHook
Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package de.binarynoise.freeNotifications
22

33
import android.app.NotificationChannel
4+
import android.app.NotificationChannelGroup
5+
import android.os.Build
46
import de.binarynoise.logger.Logger.log
57
import de.binarynoise.reflection.findDeclaredField
68
import de.robv.android.xposed.IXposedHookLoadPackage
@@ -14,53 +16,93 @@ import de.robv.android.xposed.XC_MethodHook as MethodHook
1416
class Hook : IXposedHookLoadPackage {
1517

1618
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
17-
// AOSP: mBlockableSystem / setBlockable / isBlockable
19+
hookVariable(
20+
NotificationChannel::class.java,
21+
"mBlockableSystem",
22+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) "setBlockable" else "setBlockableSystem",
23+
true,
24+
)
1825

19-
val cls = NotificationChannel::class.java
20-
21-
val mBlockableSystem = cls.findDeclaredField("mBlockableSystem")
22-
23-
tryAndLog("hook NotificationChannel constructors") {
24-
XposedBridge.hookAllConstructors(cls, object : MethodHook() {
25-
override fun afterHookedMethod(param: MethodHookParam) {
26-
mBlockableSystem.set(param.thisObject, true)
27-
}
28-
})
29-
}
30-
tryAndLog("hook setBlockable") {
31-
XposedHelpers.findAndHookMethod(cls, "setBlockable", Boolean::class.java, DO_NOTHING)
26+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
27+
hookVariable(
28+
NotificationChannel::class.java,
29+
"mImportanceLockedDefaultApp",
30+
"setImportanceLockedByCriticalDeviceFunction",
31+
false,
32+
)
3233
}
33-
tryAndLog("hook isBlockable") {
34-
XposedHelpers.findAndHookMethod(cls, "isBlockable", returnConstant(true))
35-
}
36-
37-
// AOSP: mImportanceLockedDefaultApp / setImportanceLockedByCriticalDeviceFunction / isImportanceLockedByCriticalDeviceFunction
3834

39-
val mImportanceLockedDefaultApp = cls.findDeclaredField("mImportanceLockedDefaultApp")
35+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
36+
hookVariable(
37+
NotificationChannel::class.java,
38+
"mImportanceLockedByOEM",
39+
false,
40+
)
41+
}
4042

41-
tryAndLog("hook constructors") {
42-
XposedBridge.hookAllConstructors(cls, object : MethodHook() {
43+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
44+
hookVariable(
45+
NotificationChannelGroup::class.java,
46+
"mBlocked",
47+
false,
48+
)
49+
}
50+
}
51+
52+
fun String.isCharUpperCase(index: Int): Boolean {
53+
return this[index].isUpperCase()
54+
}
55+
56+
/**
57+
* Remove prefix only if it is used in a CamelCase sentence,
58+
* only if the letter following the prefix is uppercase.
59+
*/
60+
fun String.removeCamelCasePrefix(prefix: String): String {
61+
if (this.isCharUpperCase(prefix.length)) return this.removePrefix(prefix)
62+
return this
63+
}
64+
65+
fun sanitizeVariableName(name: String): String {
66+
return name.removeCamelCasePrefix("m")
67+
.removeCamelCasePrefix("set")
68+
.removeCamelCasePrefix("is")
69+
.removeCamelCasePrefix("get")
70+
.replaceFirstChar { it.uppercaseChar() }
71+
}
72+
73+
fun hookVariable(clazz: Class<*>, variableName: String, setMethodName: String, getMethodName: String, value: Boolean) {
74+
tryAndLog("${clazz.simpleName} after constructor $variableName") {
75+
val mVariable = clazz.findDeclaredField(variableName)
76+
XposedBridge.hookAllConstructors(clazz, object : MethodHook() {
4377
override fun afterHookedMethod(param: MethodHookParam) {
44-
mImportanceLockedDefaultApp.set(param.thisObject, false)
78+
mVariable.set(param.thisObject, value)
4579
}
4680
})
4781
}
48-
tryAndLog("hook setImportanceLockedByCriticalDeviceFunction") {
49-
XposedHelpers.findAndHookMethod(cls, "setImportanceLockedByCriticalDeviceFunction", Boolean::class.java, DO_NOTHING)
82+
tryAndLog("${clazz.simpleName} $getMethodName") {
83+
XposedHelpers.findAndHookMethod(clazz, getMethodName, returnConstant(value))
5084
}
51-
tryAndLog("hook isImportanceLockedByCriticalDeviceFunction") {
52-
XposedHelpers.findAndHookMethod(cls, "isImportanceLockedByCriticalDeviceFunction", returnConstant(false))
85+
tryAndLog("${clazz.simpleName} $setMethodName") {
86+
XposedHelpers.findAndHookMethod(clazz, setMethodName, Boolean::class.java, DO_NOTHING)
5387
}
5488
}
89+
90+
fun hookVariable(clazz: Class<*>, variableName: String, functionName: String, value: Boolean) {
91+
val sanitizedVariableName = sanitizeVariableName(variableName)
92+
val sanitizedFunctionName = sanitizeVariableName(functionName)
93+
return hookVariable(clazz, "m$sanitizedVariableName", "set$sanitizedFunctionName", "is$sanitizedFunctionName", value)
94+
}
95+
96+
fun hookVariable(clazz: Class<*>, name: String, value: Boolean) {
97+
return hookVariable(clazz, name, name, value)
98+
}
5599
}
56100

57-
private inline fun tryAndLog(message: String, block: () -> Unit) {
58-
log(message)
59-
return try {
101+
inline fun tryAndLog(message: String, block: () -> Unit) {
102+
try {
60103
block()
61-
log("done!")
104+
log("hook $message successful!")
62105
} catch (t: Throwable) {
63-
log("failed!")
64-
XposedBridge.log(t)
106+
log("hook $message failed!", t)
65107
}
66108
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package de.binarynoise.freeNotifications
2+
3+
import android.app.NotificationChannel
4+
import android.app.NotificationChannelGroup
5+
import android.content.Context
6+
import android.content.pm.ApplicationInfo
7+
import android.os.Build
8+
import de.robv.android.xposed.IXposedHookLoadPackage
9+
import de.robv.android.xposed.XC_MethodHook
10+
import de.robv.android.xposed.XC_MethodReplacement
11+
import de.robv.android.xposed.XC_MethodReplacement.DO_NOTHING
12+
import de.robv.android.xposed.XposedBridge
13+
import de.robv.android.xposed.XposedHelpers
14+
import de.robv.android.xposed.callbacks.XC_LoadPackage
15+
16+
class SettingsHook : IXposedHookLoadPackage {
17+
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
18+
if (lpparam.packageName != "com.android.settings") return
19+
20+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
21+
val NotificationPreferenceControllerClass = Class.forName(
22+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) "com.android.settings.notification.app.NotificationPreferenceController"
23+
else "com.android.settings.notification.NotificationPreferenceController",
24+
false,
25+
lpparam.classLoader,
26+
)
27+
28+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
29+
tryAndLog("isAppBlockable") {
30+
XposedHelpers.findAndHookMethod(
31+
NotificationPreferenceControllerClass,
32+
"isAppBlockable",
33+
XC_MethodReplacement.returnConstant(true),
34+
)
35+
}
36+
tryAndLog("${NotificationPreferenceControllerClass.simpleName} constructor overrideBlock overrideConfigure") {
37+
XposedBridge.hookAllConstructors(
38+
NotificationPreferenceControllerClass,
39+
object : XC_MethodHook() {
40+
override fun afterHookedMethod(param: MethodHookParam) {
41+
listOf("Block", "Configure").forEach {
42+
XposedHelpers.setBooleanField(param.thisObject, "overrideCan$it", true)
43+
XposedHelpers.setBooleanField(param.thisObject, "overrideCan${it}Value", true)
44+
}
45+
}
46+
},
47+
)
48+
}
49+
tryAndLog("setOverrideCanBlock") {
50+
XposedHelpers.findAndHookMethod(
51+
NotificationPreferenceControllerClass,
52+
"setOverrideCanBlock",
53+
Boolean::class.java,
54+
DO_NOTHING,
55+
)
56+
}
57+
tryAndLog("setOverrideCanConfigure") {
58+
XposedHelpers.findAndHookMethod(
59+
NotificationPreferenceControllerClass,
60+
"setOverrideCanConfigure",
61+
Boolean::class.java,
62+
DO_NOTHING,
63+
)
64+
}
65+
}
66+
67+
tryAndLog("isChannelBlockable") {
68+
XposedHelpers.findAndHookMethod(
69+
NotificationPreferenceControllerClass,
70+
"isChannelBlockable",
71+
XC_MethodReplacement.returnConstant(true),
72+
)
73+
}
74+
tryAndLog("isChannelBlockable") {
75+
XposedHelpers.findAndHookMethod(
76+
NotificationPreferenceControllerClass,
77+
"isChannelBlockable",
78+
NotificationChannel::class.java,
79+
XC_MethodReplacement.returnConstant(true),
80+
)
81+
}
82+
tryAndLog("isChannelConfigurable") {
83+
XposedHelpers.findAndHookMethod(
84+
NotificationPreferenceControllerClass,
85+
"isChannelConfigurable",
86+
NotificationChannel::class.java,
87+
XC_MethodReplacement.returnConstant(true),
88+
)
89+
}
90+
tryAndLog("isChannelGroupBlockable") {
91+
XposedHelpers.findAndHookMethod(
92+
NotificationPreferenceControllerClass,
93+
"isChannelGroupBlockable",
94+
XC_MethodReplacement.returnConstant(true),
95+
)
96+
}
97+
tryAndLog("isChannelGroupBlockable") {
98+
XposedHelpers.findAndHookMethod(
99+
NotificationPreferenceControllerClass,
100+
"isChannelGroupBlockable",
101+
NotificationChannelGroup::class.java,
102+
XC_MethodReplacement.returnConstant(true),
103+
)
104+
}
105+
}
106+
107+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
108+
val NotificationBackendClass = Class.forName("com.android.settings.notification.NotificationBackend", false, lpparam.classLoader)
109+
val AppRowClass = Class.forName(NotificationBackendClass.name + "\$AppRow", false, lpparam.classLoader)
110+
tryAndLog("loadAppRow") {
111+
NotificationBackendClass.declaredMethods.filter { it.name == "loadAppRow" }.forEach { method ->
112+
XposedBridge.hookMethod(
113+
method,
114+
object : XC_MethodHook() {
115+
override fun afterHookedMethod(param: MethodHookParam) {
116+
val row = param.result
117+
XposedHelpers.setBooleanField(row, "systemApp", false)
118+
}
119+
},
120+
)
121+
}
122+
}
123+
tryAndLog("markAppRowWithBlockables") {
124+
XposedHelpers.findAndHookMethod(
125+
NotificationBackendClass,
126+
"markAppRowWithBlockables",
127+
Array<String>::class.java,
128+
AppRowClass,
129+
String::class.java,
130+
object : XC_MethodHook() {
131+
override fun afterHookedMethod(param: MethodHookParam) {
132+
val row = param.args[1]
133+
XposedHelpers.setBooleanField(row, "systemApp", false)
134+
}
135+
},
136+
)
137+
}
138+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
139+
tryAndLog("recordCanBeBlocked") {
140+
XposedBridge.hookMethod(
141+
NotificationBackendClass.declaredMethods.single { it.name == "recordCanBeBlocked" },
142+
object : XC_MethodHook() {
143+
override fun afterHookedMethod(param: MethodHookParam) {
144+
val row = param.args.last()
145+
XposedHelpers.setBooleanField(row, "systemApp", false)
146+
}
147+
},
148+
)
149+
}
150+
tryAndLog("isSystemApp") {
151+
XposedHelpers.findAndHookMethod(
152+
NotificationBackendClass,
153+
"isSystemApp",
154+
Context::class.java,
155+
ApplicationInfo::class.java,
156+
XC_MethodReplacement.returnConstant(false),
157+
)
158+
}
159+
}
160+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
161+
tryAndLog("isBlockable") {
162+
XposedHelpers.findAndHookMethod(
163+
NotificationBackendClass,
164+
"isBlockable",
165+
Context::class.java,
166+
ApplicationInfo::class.java,
167+
XC_MethodReplacement.returnConstant(true),
168+
)
169+
}
170+
}
171+
}
172+
}
173+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ A collection of small Xposed Modules.
1414
| [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) |
1515
| [CodecMod](CodecMod) | [@programminghoch10](https://github.com/programminghoch10) | Selectively disable audio/video hardware/software encoders/decoders. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=CodecMod) |
1616
| [Don'tResetIfBootedAndConnected](DontResetIfBootedAndConnected) | [@binarynoise](https://github.com/binarynoise) | | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=dontResetIfBootedAndConnected) |
17-
| [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) |
17+
| [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) |
1818
| [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) |
1919
| [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) |
2020
| [MuteSlf4jWarnings](MuteSlf4jWarnings) | [@binarynoise](https://github.com/binarynoise) | Mutes all slf4j warnings | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=muteSlf4jWarnings) |

0 commit comments

Comments
 (0)