Skip to content

fix(android): handle ForegroundServiceStartNotAllowedException on system-restart#538

Open
vairagadeharshwardhan wants to merge 1 commit into
ekasetiawans:masterfrom
vairagadeharshwardhan:fix/fgs-not-allowed-on-background-restart
Open

fix(android): handle ForegroundServiceStartNotAllowedException on system-restart#538
vairagadeharshwardhan wants to merge 1 commit into
ekasetiawans:masterfrom
vairagadeharshwardhan:fix/fgs-not-allowed-on-background-restart

Conversation

@vairagadeharshwardhan
Copy link
Copy Markdown

Problem

On Android 12+ (API 31+), when BackgroundService is recreated by the OS while the host app is in the background, the unconditional ServiceCompat.startForeground() call in updateNotificationInfo() can throw ForegroundServiceStartNotAllowedException. This is uncaught and crashes the host app.

Real-world stack from production (Firebase Crashlytics, Android 14, multiple OEMs):

Fatal Exception: java.lang.RuntimeException: Unable to create service ... BackgroundService:
  android.app.ForegroundServiceStartNotAllowedException: Service.startForeground()
    not allowed due to mAllowStartForeground false: service .../BackgroundService
    at android.app.ActivityThread.handleCreateService(ActivityThread.java:5690)
    ...
Caused by: android.app.ForegroundServiceStartNotAllowedException
    at androidx.core.app.ServiceCompat.startForeground(ServiceCompat.java:172)
    at id.flutter.flutter_background_service.BackgroundService
        .updateNotificationInfo(BackgroundService.java:173)
    at id.flutter.flutter_background_service.BackgroundService
        .onCreate(BackgroundService.java:101)

The existing try { ... } catch (SecurityException e) block around ServiceCompat.startForeground (added previously to handle missing-permission cases) only catches SecurityException. ForegroundServiceStartNotAllowedException is an IllegalStateException subclass introduced in API 31 and is not caught by the current code.

Setting AndroidConfiguration.isForegroundMode = false from Dart does not prevent this — that flag only controls how the host app calls startService vs startForegroundService. It does nothing about the OS-driven service recreation path, where onCreate runs updateNotificationInfo regardless.

Fix

Extend the existing catch in updateNotificationInfo() to also handle IllegalStateException — the API-level-stable parent class of ForegroundServiceStartNotAllowedException. When caught, log a warning at the same level as the existing SecurityException branch, then call stopSelf() so the service exits cleanly instead of crashing the host. The host app can restart the service the next time it returns to a state that permits FGS starts.

} catch (IllegalStateException e) {
  // Covers ForegroundServiceStartNotAllowedException (API 31+) raised
  // when Android recreates the service while the host app is in the
  // background and has no while-in-use exemption. ...
  Log.w(TAG, "Failed to start foreground service - likely a background-restart on Android 12+ ...");
  stopSelf();
}
  • On API < 31, IllegalStateException is essentially never thrown by startForeground, so behavior on older Androids is unchanged.
  • No public API change. No new permissions. No new dependencies.
  • The shape matches the existing catch (SecurityException e) immediately above: log + bail.

Validation

Production evidence: the crash signature above comes from a live Flutter app using this plugin. It reproduces frequently enough to be a top-N Crashlytics issue for that app (Android 12 / 13 / 14, mixed OEMs — Pixel, Xiaomi, Samsung).

Code-level reasoning: ForegroundServiceStartNotAllowedException is documented (https://developer.android.com/reference/android/app/ForegroundServiceStartNotAllowedException) as an IllegalStateException subclass thrown by Service.startForeground on API 31+. The catch block in this PR catches the documented parent type at the exact call site shown in the production stack.

On-device synthetic repro: I attempted a synthetic repro by adding a temporary throw new IllegalStateException(...) immediately before ServiceCompat.startForeground and observing the catch block log + stopSelf() path. On the test device available to me (Xiaomi / MIUI, Android 15) the OS's own process-management aggressively brought the service down before either the synthetic throw or the original startForeground could run, masking the validation. I did not have a non-OEM-customised Android 12+ device available, so this PR is submitted on the strength of:

  1. The production stack trace matching the documented exception path,
  2. The code change being a direct extension of the existing SecurityException catch the maintainer already added at the same call site.

Happy to add further repro evidence in whichever form you'd prefer if reviewing.

Notes

  • Did not touch the CHANGELOG — your release notes appear to be melos-generated, so I'll leave that to the release process.
  • No version bump in the package's pubspec.yaml.
  • Conventional-commit message format matches your existing history.

…tem-restart

On Android 12+ (API 31+), when the OS recreates BackgroundService after
killing it for memory pressure while the host app is in the background,
the unconditional ServiceCompat.startForeground() call in
updateNotificationInfo() throws ForegroundServiceStartNotAllowedException,
which is uncaught and crashes the host app.

Real-world stack (Crashlytics, Android 14, multiple OEMs):

  java.lang.RuntimeException: Unable to create service ... BackgroundService:
    android.app.ForegroundServiceStartNotAllowedException: Service.startForeground()
    not allowed due to mAllowStartForeground false
      at id.flutter.flutter_background_service.BackgroundService
        .updateNotificationInfo(BackgroundService.java:173)
      at id.flutter.flutter_background_service.BackgroundService
        .onCreate(BackgroundService.java:101)

The existing catch around ServiceCompat.startForeground only handles
SecurityException; ForegroundServiceStartNotAllowedException is an
IllegalStateException subclass and slips past.

Setting AndroidConfiguration.isForegroundMode=false in Dart does not
prevent this crash: that flag only affects how Dart starts the service,
not what the OS does when it restarts the service on its own.

Fix: extend the catch to also handle IllegalStateException (the parent
of ForegroundServiceStartNotAllowedException). When caught, log a
warning and stopSelf() so the service exits cleanly. The host app can
restart the service the next time it returns to a state where FGS
starts are permitted.

Behavior preserved on API < 31 - IllegalStateException is essentially
never thrown by startForeground on those versions.

No public API change. No new permissions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant