fix(notifications): handle FGS-not-allowed exception from background

AlwaysOnNotificationServiceManager calls NotificationRelayService.start()
from a flow collector that can fire during cold-start (before the activity
reaches foreground), where Android 12+ blocks startForegroundService() with
ForegroundServiceStartNotAllowedException. The exception was already caught
but logged as an error.

- Distinguish ForegroundServiceStartNotAllowedException from real failures
  and log it at WARN — the other layers (boot receiver, watchdog alarm,
  catch-up worker) retry from contexts that have FGS exemption.
- Retry the start in MainActivity.onResume() so the service comes up as
  soon as the user opens the app.
This commit is contained in:
Claude
2026-05-04 18:22:31 +00:00
parent aaa35b1a06
commit e693a954d1
2 changed files with 22 additions and 1 deletions
@@ -89,7 +89,20 @@ class NotificationRelayService : Service() {
try {
ContextCompat.startForegroundService(context, intent)
} catch (e: Exception) {
Log.e(TAG, "Failed to start foreground service", e)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
e is ForegroundServiceStartNotAllowedException
) {
// Android 12+ blocks startForegroundService() from the background unless
// the caller has a temporary FGS exemption (e.g. broadcast receiver,
// exact alarm, high-priority FCM). Cold-start triggered by a flow emission
// from AlwaysOnNotificationServiceManager hits this path. The other
// notification layers (BootCompletedReceiver, ServiceWatchdogManager,
// NotificationCatchUpWorker) plus MainActivity.onResume retry from
// contexts that are allowed.
Log.w(TAG) { "Foreground service start not allowed from background; will retry from another layer" }
} else {
Log.e(TAG, "Failed to start foreground service", e)
}
}
}
@@ -31,6 +31,7 @@ import com.vitorpamplona.amethyst.debugState
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
import com.vitorpamplona.amethyst.service.notifications.NotificationRelayService
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
import com.vitorpamplona.amethyst.service.playback.pip.BackgroundMedia
import com.vitorpamplona.amethyst.ui.navigation.findParameterValue
@@ -89,6 +90,13 @@ class MainActivity : AppCompatActivity() {
// starts muted every time
DEFAULT_MUTED_SETTING.value = true
// If always-on notifications are enabled but the foreground service couldn't be
// started from the background during cold-start (Android 12+ restriction), retry
// now that the activity is in the foreground.
if (NotificationRelayService.isEnabled(this)) {
NotificationRelayService.start(this)
}
}
override fun onPause() {