ActivityWatch / ActivityWatch/aw-android

ForegroundServiceStartNotAllowedException crashes BackgroundService on Android 14+

未关闭
#255 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
Kotlin
星标
266
派生
58
平均合并
1 天 18 小时
30 天内合并 PR
29

描述

## 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

```text
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:

```text
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:

1. `AlarmReceiver` starts `BackgroundService` from `BOOT_COMPLETED` with `startForegroundService()`.
2. `BackgroundService.onCreate()` always calls `ServiceCompat.startForeground()`.
3. `onStartCommand()` returns `START_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()` in `AlarmReceiver` on API 31+; a boot broadcast must never crash the process.
- Gracefully handle a rejected `ServiceCompat.startForeground()` in `BackgroundService`; 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()` from `AlarmReceiver` is 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 `MainActivity` still 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:

```shell
uv run vitals.py errors --days 7 --limit 100 --stacktraces --markdown
```

Related: #185, #176, #252, ActivityWatch/stats#28.

贡献指南

这个仓库没有索引到贡献指南

调研方向

Start with AlarmReceiver.kt:22 and BackgroundService.kt:51, then compare MainActivity’s normal foreground launch and the START_STICKY path in onStartCommand(). Use the listed Play query to establish the two crash clusters, and verify behavior on API 34 and 36+ for boot, sticky recreation, and activity launches. Done means both rejected foreground-service boundaries exit cleanly before initialization, normal activity startup still works, and diagnostics identify the start origin.

由索引模型根据 Issue 内容生成。

评估

技术栈
android, kotlin, rust
领域
backend, mobile
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
基本清楚
新手友好度
56/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。