From 8f71af8c5d4d97fe46183c5896e2666f8eff0522 Mon Sep 17 00:00:00 2001 From: darken Date: Tue, 28 Jul 2026 20:21:56 +0200 Subject: [PATCH] fix(monitor): Re-satisfy foreground obligation on every start command Every startForegroundService() re-arms the 10s startForeground() deadline, even when the service is already foreground. The service only promoted in onCreate(), so repeated start requests could time out and ANR. --- .../monitor/core/worker/MonitorService.kt | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt index 13707672..ea2a8592 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorService.kt @@ -99,6 +99,8 @@ class MonitorService : Service() { private var foregroundStartFailed = false private var injectionComplete = false + @Volatile private var lastNotification: Notification? = null + @Volatile private var latestNotificationSettings: NotificationSettings = NotificationSettings(useExtraNotification = false, keepAfterDisconnect = false) @@ -130,6 +132,15 @@ class MonitorService : Service() { } } + /** + * Posts the primary monitor notification and remembers it, so a later [onStartCommand] can + * re-satisfy the foreground obligation with the notification the user is currently seeing. + */ + internal fun postPrimaryNotification(notification: Notification) { + lastNotification = notification + notificationManager.notify(MonitorNotifications.NOTIFICATION_ID, notification) + } + override fun onCreate() { // Promote to foreground BEFORE Hilt DI (triggered by super.onCreate()) to avoid // ForegroundServiceDidNotStartInTimeException when DI is slow on backgrounded cold starts. @@ -160,10 +171,19 @@ class MonitorService : Service() { // Replace early notification with the full one from injected MonitorNotifications. // Failure here is non-fatal — the service is already foreground from the early call. - promoteToForeground(notifications.getStartupNotification()) + val startupNotification = notifications.getStartupNotification() + lastNotification = startupNotification + promoteToForeground(startupNotification) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + // Every startForegroundService() re-arms the startForeground() obligation, + // so every onStartCommand has to satisfy it again, no matter how it exits. + if (!promoteToForeground(lastNotification ?: MonitorNotifications.createEarlyNotification(this))) { + stopSelf(startId) + return START_NOT_STICKY + } + log(TAG, VERBOSE) { "onStartCommand(intent=$intent, flags=$flags, startId=$startId)" } if (foregroundStartFailed) { @@ -250,13 +270,12 @@ class MonitorService : Service() { .onEach { (currentDevice, settings, estimate) -> latestNotificationSettings = settings - notificationManager.notify( - MonitorNotifications.NOTIFICATION_ID, + postPrimaryNotification( notifications.getNotification( currentDevice, estimate = estimate, showHint = settings.useExtraNotification, - ), + ) ) when (val action = decideExtraNotificationAction(currentDevice, settings)) {