Skip to content

Commit e706d62

Browse files
committed
add PersistentForegroundServiceNotifications
1 parent 522a3c2 commit e706d62

File tree

10 files changed

+142
-0
lines changed

10 files changed

+142
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# PersistentForegroundServiceNotifications
2+
3+
Since Android 14, users can remove all notifications, even if they come from foreground services.
4+
This module reverts that change by making notifications of foreground services non dismissible.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
plugins {
2+
id("com.android.application")
3+
kotlin("android")
4+
}
5+
6+
android {
7+
namespace = "de.binarynoise.persistentForegroundServiceNotifications"
8+
9+
defaultConfig {
10+
minSdk = 34
11+
targetSdk = 34
12+
}
13+
}
14+
15+
dependencies {
16+
implementation(project(":logger"))
17+
implementation(project(":reflection"))
18+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest
3+
xmlns:android="http://schemas.android.com/apk/res/android">
4+
5+
<application android:label="PersistentForegroundServiceNotifications">
6+
<meta-data
7+
android:name="xposedmodule"
8+
android:value="true"
9+
/>
10+
<meta-data
11+
android:name="xposeddescription"
12+
android:value="Make notifications of foreground services persistent again."
13+
/>
14+
<meta-data
15+
android:name="xposedminversion"
16+
android:value="53"
17+
/>
18+
<meta-data
19+
android:name="xposedscope"
20+
android:resource="@array/scope"
21+
/>
22+
</application>
23+
24+
</manifest>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
de.binarynoise.persistentForegroundServiceNotifications.Hook
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package de.binarynoise.persistentForegroundServiceNotifications
2+
3+
import android.annotation.SuppressLint
4+
import android.app.Notification
5+
import android.app.Notification.FLAG_AUTO_CANCEL
6+
import android.app.Notification.FLAG_BUBBLE
7+
import android.app.Notification.FLAG_FOREGROUND_SERVICE
8+
import android.app.Notification.FLAG_GROUP_SUMMARY
9+
import android.app.Notification.FLAG_HIGH_PRIORITY
10+
import android.app.Notification.FLAG_INSISTENT
11+
import android.app.Notification.FLAG_LOCAL_ONLY
12+
import android.app.Notification.FLAG_NO_CLEAR
13+
import android.app.Notification.FLAG_ONGOING_EVENT
14+
import android.app.Notification.FLAG_ONLY_ALERT_ONCE
15+
import android.app.Notification.FLAG_SHOW_LIGHTS
16+
import android.service.notification.StatusBarNotification
17+
import de.binarynoise.logger.Logger.log
18+
import de.binarynoise.reflection.method
19+
import de.robv.android.xposed.IXposedHookLoadPackage
20+
import de.robv.android.xposed.XposedBridge
21+
import de.robv.android.xposed.XposedHelpers
22+
import de.robv.android.xposed.callbacks.XC_LoadPackage
23+
import de.robv.android.xposed.XC_MethodHook as MethodHook
24+
25+
val FLAG_NO_DISMISS = XposedHelpers.getStaticIntField(Notification::class.java, "FLAG_NO_DISMISS")
26+
27+
@SuppressLint("PrivateApi", "MissingPermission")
28+
class Hook : IXposedHookLoadPackage {
29+
30+
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) = try {
31+
val statusBarNotificationClass = StatusBarNotification::class.java
32+
val isNonDismissableMethod by method(statusBarNotificationClass)
33+
34+
XposedBridge.hookMethod(isNonDismissableMethod, object : MethodHook() {
35+
override fun beforeHookedMethod(param: MethodHookParam) {
36+
with(param) {
37+
val sbn = thisObject as StatusBarNotification
38+
val isForeground = (sbn.notification.flags and FLAG_FOREGROUND_SERVICE) != 0
39+
val isNoDismiss = (sbn.notification.flags and FLAG_NO_DISMISS) != 0
40+
41+
val isNonDismissable = isNoDismiss || isForeground
42+
log("isNonDismissable: ${sbn.packageName}: ${sbn.notification.flags.flagsToString()} -> isNonDismissable: $isNonDismissable")
43+
44+
result = isNonDismissable
45+
}
46+
}
47+
})
48+
49+
log("hooked isNonDismissableMethod")
50+
} catch (e: Exception) {
51+
log("Failed to hook isOngoingMethod", e)
52+
}
53+
}
54+
55+
@Suppress("DEPRECATION")
56+
fun Int.flagsToString(): String {
57+
val showLights = this and FLAG_SHOW_LIGHTS != 0
58+
val ongoingEvent = this and FLAG_ONGOING_EVENT != 0
59+
val insistent = this and FLAG_INSISTENT != 0
60+
val onlyAlertOnce = this and FLAG_ONLY_ALERT_ONCE != 0
61+
val autoCancel = this and FLAG_AUTO_CANCEL != 0
62+
val noClear = this and FLAG_NO_CLEAR != 0
63+
val foregroundService = this and FLAG_FOREGROUND_SERVICE != 0
64+
val highPriority = this and FLAG_HIGH_PRIORITY != 0
65+
val localOnly = this and FLAG_LOCAL_ONLY != 0
66+
val groupSummary = this and FLAG_GROUP_SUMMARY != 0
67+
val bubble = this and FLAG_BUBBLE != 0
68+
val noDismiss = this and FLAG_NO_DISMISS != 0
69+
70+
return buildList {
71+
if (showLights) this.add("showLights")
72+
if (ongoingEvent) this.add("ongoingEvent")
73+
if (insistent) this.add("insistent")
74+
if (onlyAlertOnce) this.add("onlyAlertOnce")
75+
if (autoCancel) this.add("autoCancel")
76+
if (noClear) this.add("noClear")
77+
if (foregroundService) this.add("foregroundService")
78+
if (highPriority) this.add("highPriority")
79+
if (localOnly) this.add("localOnly")
80+
if (groupSummary) this.add("groupSummary")
81+
if (bubble) this.add("bubble")
82+
if (noDismiss) this.add("noDismiss")
83+
}.joinToString(", ", prefix = "[", postfix = "]")
84+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<string-array name="scope">
4+
<item>com.android.systemui</item>
5+
</string-array>
6+
</resources>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Since Android 14, users can remove all notifications, even if they come from foreground services.
2+
This module reverts that change by making notifications of foreground services non-dismissible again.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make notifications of foreground services persistent again
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PersistentForegroundServiceNotifications

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ include(":DontResetIfBootedAndConnected")
2626
include(":FreeNotifications")
2727
include(":MotionEventMod")
2828
include(":MuteSlf4jWarnings")
29+
include(":PersistentForegroundServiceNotifications")
2930
include(":ResetAllNotificationChannels")
3031
include(":RotationControl")
3132
include(":reflection")

0 commit comments

Comments
 (0)