From a9439d014fb5f69716a6cbfaf4c4b2f19c134de0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 20:59:27 +0000 Subject: [PATCH] fix(notif): always stopSelf when foreground promotion fails ForegroundServiceDidNotStartInTimeException crashes the whole app when a service started via startForegroundService() never successfully calls startForeground() within Android's ~10s window. NotificationRelayService.initializeForeground() only called stopSelf() on ForegroundServiceStartNotAllowedException. Any other failure to promote to the foreground (OEM-specific RemoteException/IllegalStateException, a resource lookup failure while building the notification, etc.) was logged but left the service in a "started but not foregrounded" zombie state, guaranteeing the timeout crash. Now stopSelf() runs on every failure path, which clears the OS's fgRequired flag and cancels the pending timeout. onStartCommand also bails early (START_NOT_STICKY) when foreground promotion failed, so we don't spin up relay coroutines on a service that's tearing itself down. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_012hCJDhJmCNkzqb7SaMWQgB --- .../notifications/NotificationRelayService.kt | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationRelayService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationRelayService.kt index 36acb126e2..d9c69ce639 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationRelayService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationRelayService.kt @@ -152,6 +152,10 @@ class NotificationRelayService : Service() { // Safety: also call startForeground from onStartCommand in case // onCreate didn't complete before onStartCommand fired (ntfy #1520) initializeForeground() + // If we couldn't promote to the foreground, initializeForeground() already + // called stopSelf(); don't spin up relay coroutines on a service that's + // tearing itself down. + if (!foregroundStarted) return START_NOT_STICKY startRelayConnection() return START_STICKY } @@ -222,14 +226,23 @@ class NotificationRelayService : Service() { ) foregroundStarted = true } catch (e: Exception) { + // Any failure to promote to the foreground leaves the service in a + // "started but not foregrounded" zombie state. Once startForegroundService() + // has been called, Android expects startForeground() within ~10s; if it never + // lands it fires ForegroundServiceDidNotStartInTimeException and crashes the + // whole app. Tearing the service down clears the OS's fgRequired flag, which + // cancels that pending timeout, so we must stopSelf() on EVERY failure path — + // not just ForegroundServiceStartNotAllowedException. Other causes include + // OEM-specific RemoteException/IllegalStateException and resource lookup + // failures while building the notification. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e is ForegroundServiceStartNotAllowedException ) { Log.w(TAG, "Foreground service start not allowed, stopping self") - stopSelf() } else { - Log.e(TAG, "Failed to start foreground", e) + Log.e(TAG, "Failed to start foreground, stopping self", e) } + stopSelf() } }