Stop notification listener when all logging is disabled

This commit is contained in:
ajp_anton
2026-07-23 14:02:58 +00:00
parent 6ce7d65409
commit 50aebbd42b
5 changed files with 75 additions and 1 deletions
@@ -5,7 +5,8 @@ import androidx.core.content.edit
/** Persists rule choices only; notification contents never enter SharedPreferences. */
class LoggingRuleStore(context: Context) {
private val preferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
private val appContext = context.applicationContext
private val preferences = appContext.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
fun ruleFor(type: LoggingType): LoggingRule = LoggingRule(
enabled = preferences.getBoolean(key(type, "enabled"), true),
@@ -24,6 +25,7 @@ class LoggingRuleStore(context: Context) {
preferences.edit {
write(type, rule)
}
updateListenerComponent()
}
fun copy(from: LoggingType, targets: Set<LoggingType>) {
@@ -33,6 +35,7 @@ class LoggingRuleStore(context: Context) {
write(target, source.copy(selectedPackages = source.selectedPackages.toSet()))
}
}
updateListenerComponent()
}
private fun android.content.SharedPreferences.Editor.write(type: LoggingType, rule: LoggingRule) {
@@ -46,6 +49,10 @@ class LoggingRuleStore(context: Context) {
private fun key(type: LoggingType, suffix: String) = "${type.name.lowercase()}.$suffix"
private fun updateListenerComponent() {
NotificationListenerComponentController.update(appContext, LoggingType.entries.map(::ruleFor))
}
private companion object {
const val FILE_NAME = "logging-rules"
}
@@ -0,0 +1,37 @@
package se.ajpanton.notificationlog.settings
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.service.notification.NotificationListenerService
import android.util.Log
import se.ajpanton.notificationlog.capture.NotificationCaptureService
/**
* Notification listeners are re-bound by Android after boot when their component
* is enabled and the user granted notification access. Disabling the component
* is therefore both the no-work battery mode and the no-start-on-boot mode.
*/
internal object NotificationListenerComponentController {
fun update(context: Context, rules: Collection<LoggingRule>) {
val applicationContext = context.applicationContext
val component = ComponentName(applicationContext, NotificationCaptureService::class.java)
val packageManager = applicationContext.packageManager
val desired = if (NotificationListenerPolicy.shouldRun(rules)) {
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
} else {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
}
if (packageManager.getComponentEnabledSetting(component) == desired) return
packageManager.setComponentEnabledSetting(component, desired, PackageManager.DONT_KILL_APP)
if (desired == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
try {
NotificationListenerService.requestRebind(component)
} catch (error: SecurityException) {
Log.w(TAG, "Notification access is not granted yet; Android will bind after the user grants it", error)
}
}
}
private const val TAG = "NotificationListenerControl"
}
@@ -0,0 +1,6 @@
package se.ajpanton.notificationlog.settings
/** The listener is useful only when at least one event type is enabled. */
object NotificationListenerPolicy {
fun shouldRun(rules: Collection<LoggingRule>): Boolean = rules.any { it.enabled }
}