fix(notifications): stop resetting the watchdog alarm on every account change

The service layers were being re-enabled level-triggered. Before this branch the
inner flow was a Boolean behind distinctUntilChanged, so enableServiceLayers()
ran once per real transition. It now carries (all accounts, participating
accounts), and Account has no equals — so the flow re-emits whenever the account
map changes identity, and the outer collector restarts on every
foreground/background crossing, which includes every screen-off.

That matters because ServiceWatchdogManager.schedule() calls setInexactRepeating
with FLAG_UPDATE_CURRENT and a first fire of `elapsedRealtime() + 5min`. Every
call replaces the alarm and pushes that first fire out again. Screen-on happens
far more often than every five minutes, so L5 — the layer whose entire job is
noticing a dead service and restarting it — would effectively never have fired.

NotificationCatchUpWorker was unaffected: it enqueues with
ExistingPeriodicWorkPolicy.KEEP, so repeat calls leave the existing period alone.

Enable/disable is edge-triggered again, explicitly this time rather than as a
side effect of what the upstream flow happens to emit.

Also drops ActiveSubscriptionsState.busiestPurposeFilters, which has no readers —
the cards draw their share against attributedFilters.

Verified on emulator-5554: after a cold start, HOME, and return, the foreground
service is still running and the watchdog alarm is still registered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-07-31 18:54:46 -04:00
co-authored by Claude Opus 5
parent a295f31f94
commit e3e17e8ecb
2 changed files with 12 additions and 10 deletions
@@ -149,12 +149,17 @@ class AlwaysOnNotificationServiceManager(
// The service layers are a background concern, so they stay tied to
// the master switch and to somebody having opted in. A foreground-only
// account must not start a foreground service that outlives the screen.
if (masterEnabled && participating.isNotEmpty()) {
wasEnabled = true
enableServiceLayers()
} else if (wasEnabled) {
disableServiceLayers()
wasEnabled = false
//
// Edge-triggered, deliberately. This flow re-emits whenever the account
// map changes identity or the app crosses foreground — far more often
// than the old boolean did — and ServiceWatchdogManager.schedule()
// replaces its alarm with one starting `now + 5min`. Calling it on every
// emission pushed the watchdog's first fire past every screen-on, so the
// layer that exists to restart a dead service would never have run.
val shouldRun = masterEnabled && participating.isNotEmpty()
if (shouldRun != wasEnabled) {
if (shouldRun) enableServiceLayers() else disableServiceLayers()
wasEnabled = shouldRun
}
}
}
@@ -136,10 +136,7 @@ data class ActiveSubscriptionsState(
* overstate every card, which is the mistake the per-entity rows already taught once.
*/
val attributedFilters: Int = 0,
) {
/** The largest purpose, so every card can draw its share against a common scale. */
val busiestPurposeFilters: Int = accounts.flatMap { it.purposes }.maxOfOrNull { it.filterCount } ?: 0
}
)
class ActiveSubscriptionsViewModel : ViewModel() {
private val _state = MutableStateFlow(ActiveSubscriptionsState())