Skip to content

Commit ed0fb8c

Browse files
committed
Use AlarmManager to execute DISMISS_STALE reliably
1 parent 2398042 commit ed0fb8c

8 files changed

Lines changed: 110 additions & 18 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@
3838
<category android:name="android.intent.category.LAUNCHER" />
3939
</intent-filter>
4040
</activity>
41+
42+
<receiver
43+
android:name=".AlarmReceiver"
44+
android:exported="false">
45+
<intent-filter>
46+
<action android:name="co.adityarajput.notifilter.DISMISS_STALE" />
47+
</intent-filter>
48+
</receiver>
4149
<receiver
4250
android:name=".WidgetReceiver"
4351
android:exported="true">
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package co.adityarajput.notifilter
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import co.adityarajput.notifilter.services.NotificationListener
7+
import co.adityarajput.notifilter.utils.Logger
8+
9+
class AlarmReceiver : BroadcastReceiver() {
10+
override fun onReceive(context: Context, intent: Intent) {
11+
Logger.d("AlarmReceiver", "Received intent with action: ${intent.action}")
12+
13+
if (intent.action == Constants.ACTION_DISMISS_STALE) {
14+
val key = intent.getStringExtra(Constants.EXTRA_SBN_KEY) ?: return
15+
val isClearable = intent.getBooleanExtra(Constants.EXTRA_SBN_IS_CLEARABLE, false)
16+
17+
NotificationListener.instance?.dismissNotification(key, isClearable)
18+
}
19+
}
20+
}

app/src/main/java/co/adityarajput/notifilter/Constants.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,9 @@ object Constants {
1313
const val FOREGROUND_NOTIFICATION_ID = 1001
1414
const val FOREGROUND_NOTIFICATION_CHANNEL_ID = "notifilter_foreground"
1515

16+
const val ACTION_DISMISS_STALE = "co.adityarajput.notifilter.DISMISS_STALE"
17+
const val EXTRA_SBN_KEY = "extra_sbn_key"
18+
const val EXTRA_SBN_IS_CLEARABLE = "extra_sbn_is_clearable"
19+
1620
const val LOG_SIZE = 100
1721
}

app/src/main/java/co/adityarajput/notifilter/services/NotificationListener.kt

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package co.adityarajput.notifilter.services
22

3+
import android.app.AlarmManager
34
import android.app.Notification.FLAG_GROUP_SUMMARY
45
import android.app.NotificationChannel
56
import android.app.NotificationManager
@@ -18,6 +19,7 @@ import co.adityarajput.notifilter.data.AppContainer
1819
import co.adityarajput.notifilter.data.Cache
1920
import co.adityarajput.notifilter.data.models.*
2021
import co.adityarajput.notifilter.utils.Logger
22+
import co.adityarajput.notifilter.utils.sendIntent
2123
import kotlinx.coroutines.*
2224
import kotlinx.coroutines.flow.collectLatest
2325
import kotlinx.coroutines.flow.first
@@ -65,6 +67,7 @@ class NotificationListener : NotificationListenerService() {
6567

6668
private val audioManager by lazy { getSystemService(AUDIO_SERVICE) as AudioManager }
6769
private val notificationManager by lazy { getSystemService(NOTIFICATION_SERVICE) as NotificationManager }
70+
private val alarmManager by lazy { getSystemService(ALARM_SERVICE) as AlarmManager }
6871

6972
@Volatile
7073
private var filters: List<Filter> = emptyList()
@@ -151,7 +154,7 @@ class NotificationListener : NotificationListenerService() {
151154
Logger.i("NotificationListener", "Matched $filter")
152155

153156
when (filter.action) {
154-
is Action.DISMISS -> dismissOrSnoozeFor5Hours(sbn)
157+
is Action.DISMISS -> dismissNotification(sbn.key, sbn.isClearable)
155158

156159
is Action.TAP_NOTIFICATION ->
157160
try {
@@ -265,9 +268,15 @@ class NotificationListener : NotificationListenerService() {
265268
retentionLength /= 5
266269
}
267270

268-
Logger.d("NotificationListener", "Waiting $retentionLength ms before removing")
269-
delay(retentionLength)
270-
dismissOrSnoozeFor5Hours(sbn)
271+
alarmManager.sendIntent(
272+
this@NotificationListener,
273+
retentionLength,
274+
sbn.key.hashCode(),
275+
) {
276+
action = Constants.ACTION_DISMISS_STALE
277+
putExtra(Constants.EXTRA_SBN_KEY, sbn.key)
278+
putExtra(Constants.EXTRA_SBN_IS_CLEARABLE, sbn.isClearable)
279+
}
271280
}
272281
}
273282
}
@@ -286,20 +295,17 @@ class NotificationListener : NotificationListenerService() {
286295
}
287296
}
288297

289-
private fun dismissOrSnoozeFor5Hours(sbn: StatusBarNotification) {
290-
if (sbn.isClearable) {
298+
fun dismissNotification(key: String, isClearable: Boolean) {
299+
if (isClearable) {
291300
try {
292-
cancelNotification(sbn.key)
301+
Logger.d("NotificationListener", "Canceling")
302+
cancelNotification(key)
293303
} catch (e: Throwable) {
294-
Logger.e(
295-
"NotificationListener",
296-
"Failed to dismiss notification",
297-
e,
298-
)
304+
Logger.e("NotificationListener", "Failed to dismiss notification", e)
299305
}
300306
} else {
301-
Logger.d("NotificationListener", "Is unclearable")
302-
snoozeNotification(sbn.key, 5 * 60 * 60 * 1000L)
307+
Logger.d("NotificationListener", "Snoozing")
308+
snoozeNotification(key, 5 * 60 * 60 * 1000L)
303309
}
304310
}
305311

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package co.adityarajput.notifilter.utils
2+
3+
import android.annotation.SuppressLint
4+
import android.app.AlarmManager
5+
import android.app.PendingIntent
6+
import android.content.Context
7+
import android.content.Intent
8+
import android.os.SystemClock
9+
import co.adityarajput.notifilter.AlarmReceiver
10+
11+
@SuppressLint("MissingPermission")
12+
fun AlarmManager.sendIntent(
13+
context: Context,
14+
delay: Long,
15+
requestCode: Int,
16+
intentDetails: Intent.() -> Unit,
17+
) {
18+
Logger.d("Alarms", "Setting exact alarm in ${delay}ms")
19+
setExactAndAllowWhileIdle(
20+
AlarmManager.ELAPSED_REALTIME,
21+
SystemClock.elapsedRealtime() + delay,
22+
PendingIntent.getBroadcast(
23+
context, requestCode,
24+
Intent(context, AlarmReceiver::class.java).apply(intentDetails),
25+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
26+
),
27+
)
28+
}

app/src/main/java/co/adityarajput/notifilter/utils/Permissions.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package co.adityarajput.notifilter.utils
33
import android.Manifest
44
import android.annotation.SuppressLint
55
import android.app.Activity
6+
import android.app.AlarmManager
67
import android.app.NotificationManager
78
import android.content.Context
89
import android.content.Context.NOTIFICATION_SERVICE
@@ -23,6 +24,7 @@ enum class Permission {
2324
UNRESTRICTED_BACKGROUND_USAGE,
2425
POST_NOTIFICATIONS,
2526
NOTIFICATION_POLICY,
27+
SCHEDULE_EXACT_ALARM,
2628
}
2729

2830
fun Context.isGranted(permission: Permission) = when (permission) {
@@ -45,6 +47,11 @@ fun Context.isGranted(permission: Permission) = when (permission) {
4547
Permission.NOTIFICATION_POLICY ->
4648
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
4749
.isNotificationPolicyAccessGranted()
50+
51+
Permission.SCHEDULE_EXACT_ALARM ->
52+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) true else
53+
(getSystemService(Context.ALARM_SERVICE) as AlarmManager)
54+
.canScheduleExactAlarms()
4855
}
4956

5057
fun Context.isGranted(permissions: Iterable<Permission>) =
@@ -59,7 +66,7 @@ fun Context.request(permission: Permission, remove: Boolean = false) = try {
5966
Permission.ACCESSIBILITY_SERVICE ->
6067
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
6168

62-
Permission.UNRESTRICTED_BACKGROUND_USAGE ->
69+
Permission.UNRESTRICTED_BACKGROUND_USAGE, Permission.SCHEDULE_EXACT_ALARM ->
6370
startActivity(
6471
if (remove)
6572
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
@@ -100,4 +107,7 @@ fun permissionsRequired(filters: List<Filter>) = buildList {
100107

101108
if (filters.any { it.action is Action.DISTURB })
102109
add(Permission.NOTIFICATION_POLICY)
110+
111+
if (filters.any { it.action is Action.DISMISS_STALE })
112+
add(Permission.SCHEDULE_EXACT_ALARM)
103113
}

app/src/main/java/co/adityarajput/notifilter/views/screens/UpsertFilterScreen.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ private val permissions = listOf(
450450
Permission.ACCESSIBILITY_SERVICE,
451451
Permission.POST_NOTIFICATIONS,
452452
Permission.NOTIFICATION_POLICY,
453+
Permission.SCHEDULE_EXACT_ALARM,
453454
)
454455

455456
@Composable
@@ -789,6 +790,20 @@ private fun ColumnScope.ActionPage(viewModel: UpsertFilterViewModel) {
789790
)
790791
}
791792
}
793+
if (!hasPermissions.getValue(Permission.SCHEDULE_EXACT_ALARM)) {
794+
ErrorText(R.string.exact_alarm_permission_description)
795+
Button(
796+
{ context.request(Permission.SCHEDULE_EXACT_ALARM) },
797+
Modifier.align(Alignment.CenterHorizontally),
798+
colors = ButtonDefaults.buttonColors(contentColor = MaterialTheme.colorScheme.onPrimaryContainer),
799+
) {
800+
Text(
801+
stringResource(R.string.disable_optimization),
802+
style = MaterialTheme.typography.labelLarge,
803+
fontWeight = FontWeight.Normal,
804+
)
805+
}
806+
}
792807
}
793808
}
794809
}

app/src/main/res/values/strings.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
<string name="action_page_title">Choose the action to take</string>
9898
<string name="dismiss_long">Dismiss the notification</string>
9999
<string name="tap_notification_long">Tap the notification</string>
100-
<string name="accessibility_service_description">NotiFilter requires an Accessibility Service to tap notifications</string>
100+
<string name="accessibility_service_description">NotiFilter requires an Accessibility Service to tap notifications.</string>
101101
<string name="enable_service">Enable service</string>
102102
<string name="tap_button_long">Tap a button</string>
103103
<string name="button_pattern">Button pattern</string>
@@ -113,9 +113,10 @@
113113
<string name="alert_notifications_description">NotiFilter sends short-lived notifications to play alerts.</string>
114114
<string name="disturb_long">Disable \"Do Not Disturb\"</string>
115115
<string name="pause_length">Enable after %1$s min(s)</string>
116-
<string name="notification_policy_permission_description">NotiFilter requires access to the Notification Policy to change DND status</string>
116+
<string name="notification_policy_permission_description">NotiFilter requires access to the Notification Policy to change DND status.</string>
117117
<string name="dismiss_stale_long">Remove stale notifications</string>
118118
<string name="retention_length">Dismiss after %1$s min(s)</string>
119+
<string name="exact_alarm_permission_description">NotiFilter requires exemption from battery optimization to execute delayed dismissal.</string>
119120
<string name="display_options">Choose where to display caught notification</string>
120121
<string name="describe_history_screen">In-app History screen</string>
121122
<string name="describe_widget">Homescreen Log widget</string>
@@ -154,7 +155,7 @@
154155
<string name="about">About</string>
155156
<string name="app_description">" listens to all device notifications and quietly manages those that match your filters."</string>
156157
<string name="app_links"><![CDATA[Licensed under <a href=\"https://github.com/BURG3R5/NotiFilter/blob/master/LICENSE\">GPLv3</a><br/>Source code available on <a href=\"https://github.com/BURG3R5/NotiFilter\">GitHub</a><br/>Visit the <a href=\"https://github.com/BURG3R5/NotiFilter/wiki\">project wiki</a> for more]]></string>
157-
<string name="app_permissions"><![CDATA[Permissions<small><ul><li>BIND_NOTIFICATION_LISTENER_SERVICE: to listen and react to notifications</li><li>QUERY_ALL_PACKAGES: to search through installed apps while adding filters</li><li>BIND_ACCESSIBILITY_SERVICE: to execute TAP_NOTIFICATION actions, if any</li><li>POST_NOTIFICATIONS: to execute ALERT actions, if any</li><li>ACCESS_NOTIFICATION_POLICY: to execute DISTURB actions, if any</li><li>REQUEST_IGNORE_BATTERY_OPTIMIZATIONS: to request exemption from background usage restrictions</li><li>FOREGROUND_SERVICE_SPECIAL_USE: to optionally promote to the core service to foreground, avoiding aggressive OS optimizations</li></ul>Other permissions may be used by libraries to manage tasks, such as widget updates.</small>]]></string>
158+
<string name="app_permissions"><![CDATA[Permissions<small><ul><li>BIND_NOTIFICATION_LISTENER_SERVICE: to listen and react to notifications</li><li>QUERY_ALL_PACKAGES: to search through installed apps while adding filters</li><li>BIND_ACCESSIBILITY_SERVICE: to execute TAP_NOTIFICATION actions, if any</li><li>POST_NOTIFICATIONS: to execute ALERT actions, if any</li><li>ACCESS_NOTIFICATION_POLICY: to execute DISTURB actions, if any</li><li>REQUEST_IGNORE_BATTERY_OPTIMIZATIONS: to execute DISMISS_STALE actions, if any</li><li>FOREGROUND_SERVICE_SPECIAL_USE: to optionally promote to the core service to foreground, avoiding aggressive OS optimizations</li></ul>Other permissions may be used by libraries to manage tasks, such as widget updates.</small>]]></string>
158159
<string name="dev_credit"><![CDATA[Developed by <a href=\"https://github.com/BURG3R5/\">BURG3R5</a>]]></string>
159160
<!-- endregion -->
160161

0 commit comments

Comments
 (0)