Repository navigation
ForegroundServiceStartNotAllowedException crashes BackgroundService on Android 14+ #255
Description
Problem
The remaining top Play crash on v0.14.0b2 (versionCode 40) is an uncaught ForegroundServiceStartNotAllowedException while BackgroundService.onCreate() promotes itself with ServiceCompat.startForeground().
A live, time-bounded Play Developer Reporting API query on 2026-09-13 shows:
| Window (UTC) | Reports | Distinct users |
|---|---|---|
| Last 24h | 41 | 14 |
| Last 7d | 475 | 83 |
| Last 21d | 928 | 142 |
The cluster is still active (lastErrorReportTime: 2026-09-12T23:00:00Z), spans Android API 34–37, and only names versionCode 40. This is now larger than the residual old-version ChromeWatcher cluster.
Primary Play issue ID: b422f7bf3b71b095ad1cf06a3079e0f0.
Stack trace
Exception java.lang.RuntimeException:
at android.app.ActivityThread.handleCreateService (...)
Caused by android.app.ForegroundServiceStartNotAllowedException:
at android.app.IActivityManager$Stub$Proxy.setServiceForeground (...)
at android.app.Service.startForeground (...)
at androidx.core.app.ServiceCompat$Api34Impl.startForeground (ServiceCompat.java:241)
at androidx.core.app.ServiceCompat.startForeground (ServiceCompat.java:172)
at net.activitywatch.android.BackgroundService.onCreate (BackgroundService.kt:51)
There is a second, distinct cluster at the caller boundary:
Caused by android.app.ForegroundServiceStartNotAllowedException:
at android.app.ContextImpl.startForegroundService (...)
at net.activitywatch.android.watcher.AlarmReceiver.onReceive (AlarmReceiver.kt:22)
That companion cluster contributes another 7 reports / 5 users in 7d (26 / 10 in 21d), on API 33–37, also versionCode 40.
Current paths and likely trigger
Current master still has both unguarded boundaries:
AlarmReceiverstartsBackgroundServicefromBOOT_COMPLETEDwithstartForegroundService().BackgroundService.onCreate()always callsServiceCompat.startForeground().onStartCommand()returnsSTART_STICKY, allowing Android to recreate the service after process death while the app is backgrounded.
MainActivity also starts the service, but that path starts from a visible activity and is expected to be allowed.
The primary stack does not preserve the initiating intent, so Play data alone cannot prove whether every occurrence is a sticky restart, a boot start whose promotion is rejected, or an OEM lifecycle edge. The START_STICKY background-recreation path is the leading hypothesis; the separate AlarmReceiver.onReceive cluster proves that boot/background launch is independently unsafe. This issue should fix both exception boundaries rather than guessing one caller explains all reports.
Scope
- Gracefully handle a rejected
startForegroundService()inAlarmReceiveron API 31+; a boot broadcast must never crash the process. - Gracefully handle a rejected
ServiceCompat.startForeground()inBackgroundService; stop without running native/server initialization or entering a crash/restart loop. - Re-evaluate
START_STICKY: retain it only if a system-initiated background recreation can legally promote the service on supported Android versions. Otherwise use an explicit allowed restart path. - Preserve the normal foreground launch from
MainActivity. - Add lifecycle diagnostics identifying the start origin (
activity,boot,sticky/null-intent, settings) so future Play clusters can be attributed.
Restoring collection/server availability after an OEM kills the service is related but separate work in #252. This issue's minimum outcome is no process crash when Android refuses foreground-service startup; silently pretending the service is alive is not acceptable either.
Acceptance criteria
- A denied
startForegroundService()fromAlarmReceiveris caught and logged, with no app crash. - A denied
startForeground()during service creation stops cleanly before Rust/server/scheduler initialization, with no app crash or restart loop. - Normal launch from
MainActivitystill starts the foreground service and local server. - Boot behavior is verified on API 34 and API 36+ (or the unsupported boundary is documented and surfaced to the user).
- A simulated/system-initiated service recreation while backgrounded does not crash.
- After release, the two Play issue IDs are re-queried over a comparable 7-day window and show no new reports on the fixed versionCode.
Data reproduction
The bounded query is available on ActivityWatch/stats#28:
uv run vitals.py errors --days 7 --limit 100 --stacktraces --markdownRelated: #185, #176, #252, ActivityWatch/stats#28.
Activity
ErikBjare commentedon Sep 13, 2026
@TimeToBuildBob fix it
TimeToBuildBob commentedon Sep 13, 2026
Fixed in #257.
The PR guards both rejection boundaries, stops before native/server initialization if foreground promotion fails, removes unsafe sticky recreation, and logs the start origin. Targeted unit tests pass and the local AI review is 5/5. CI is running; API 34/36 device verification remains the post-build/release gate.