Android 14+: NtfyPushService killed by dataSync foreground-service timeout (top crash) #18

Closed
opened 2026-09-01 16:52:54 +00:00 by mohlec · 2 comments
Owner

The single largest crash source in the field: 26 of the last 40 crash reports, hitting
every Android 14+ device we have reports from.

android.app.RemoteServiceException$ForegroundServiceDidNotStopInTimeException:
A foreground service of type dataSync did not stop within its timeout:
ComponentInfo{com.ncwcom.slash/com.ncwcom.slash.NtfyPushService}

Cause. Android 14 caps dataSync foreground services at roughly 6 hours cumulative per
24-hour period; when the budget is exhausted the system throws and the process dies.
NtfyPushService is declared android:foregroundServiceType="dataSync" in
android/app/src/main/AndroidManifest.xml and returns START_STICKY from
onStartCommand, so it is restarted and re-accrues against the same budget. There is no
Service.onTimeout() override, so we never get the chance to shut down cleanly first.

dataSync is also arguably the wrong declaration: the service holds a long-lived SSE
subscription for push delivery, it is not performing a bounded data transfer.

Proposed fix.

  1. Declare the service FOREGROUND_SERVICE_TYPE_SPECIAL_USE, which is not subject to the
    dataSync time budget. Requires the matching FOREGROUND_SERVICE_SPECIAL_USE permission
    and a subtype justification, plus a Play Console declaration if we ever ship to Play.
  2. Implement Service.onTimeout(startId, fgsType) (API 35+) as a backstop so we stop and
    reschedule ourselves rather than being killed.
  3. Re-check the START_STICKY restart path against the new type.

Knock-on effect worth noting: every kill turns the user's next launch into a cold start,
so affected users pay cold-start latency far more often than anyone else. See the
channel-list startup issue filed alongside this one.

Observed: 20 reports from a single SM-S938U / Android 16 between 2026-08-13 and
2026-09-01, also seen on SM-S901U / Android 16. App v1.0.0+1.

The single largest crash source in the field: 26 of the last 40 crash reports, hitting every Android 14+ device we have reports from. ``` android.app.RemoteServiceException$ForegroundServiceDidNotStopInTimeException: A foreground service of type dataSync did not stop within its timeout: ComponentInfo{com.ncwcom.slash/com.ncwcom.slash.NtfyPushService} ``` **Cause.** Android 14 caps `dataSync` foreground services at roughly 6 hours cumulative per 24-hour period; when the budget is exhausted the system throws and the process dies. `NtfyPushService` is declared `android:foregroundServiceType="dataSync"` in `android/app/src/main/AndroidManifest.xml` and returns `START_STICKY` from `onStartCommand`, so it is restarted and re-accrues against the same budget. There is no `Service.onTimeout()` override, so we never get the chance to shut down cleanly first. `dataSync` is also arguably the wrong declaration: the service holds a long-lived SSE subscription for push delivery, it is not performing a bounded data transfer. **Proposed fix.** 1. Declare the service `FOREGROUND_SERVICE_TYPE_SPECIAL_USE`, which is not subject to the dataSync time budget. Requires the matching `FOREGROUND_SERVICE_SPECIAL_USE` permission and a subtype justification, plus a Play Console declaration if we ever ship to Play. 2. Implement `Service.onTimeout(startId, fgsType)` (API 35+) as a backstop so we stop and reschedule ourselves rather than being killed. 3. Re-check the `START_STICKY` restart path against the new type. **Knock-on effect worth noting:** every kill turns the user's next launch into a cold start, so affected users pay cold-start latency far more often than anyone else. See the channel-list startup issue filed alongside this one. **Observed:** 20 reports from a single SM-S938U / Android 16 between 2026-08-13 and 2026-09-01, also seen on SM-S901U / Android 16. App v1.0.0+1.
Author
Owner

Upstream context: the rebase does not fix this, and it changes the proposed approach

Checked the 55 upstream commits since the fork point (1e169c78, 2026-04-15).

No upstream fix exists. NtfyPushService is ours alone, upstream defines onTimeout nowhere
in android/, and the only foreground-service change in that range is inside
b6fc85385 (Expo Router / RN 0.83 / New Architecture), which merely removes the nitro
background-timer service. So this is entirely on us either way.

Two upstream facts do change how we should implement it:

1. Upstream deliberately avoids SPECIAL_USE. b6fc85385 adds:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" tools:node="remove" />

with the comment "Play Store special-use review not needed." The SPECIAL_USE approach proposed
above runs directly against that stance. It remains fine for internal distribution, but it would
invite Play review if store submission ever becomes real. Worth evaluating
FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING as the semantically-honest alternative for a push
relay first -- unverified, someone needs to confirm it is genuinely exempt from the dataSync
time budget before we commit to it.

2. Timing argues for fixing this before the rebase, not after. onTimeout() (API 35) is
already usable today: the fork is at compileSdk 35. After the rebase we land on
compileSdk/targetSdk 36 (Android 16), where foreground-service restrictions tighten further. So
there is no tooling blocker now, and the cost of waiting only goes up.

### Upstream context: the rebase does not fix this, and it changes the proposed approach Checked the 55 upstream commits since the fork point (`1e169c78`, 2026-04-15). **No upstream fix exists.** `NtfyPushService` is ours alone, upstream defines `onTimeout` nowhere in `android/`, and the only foreground-service change in that range is inside `b6fc85385` (Expo Router / RN 0.83 / New Architecture), which merely *removes* the nitro background-timer service. So this is entirely on us either way. Two upstream facts do change how we should implement it: **1. Upstream deliberately avoids `SPECIAL_USE`.** `b6fc85385` adds: ```xml <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" tools:node="remove" /> ``` with the comment *"Play Store special-use review not needed."* The `SPECIAL_USE` approach proposed above runs directly against that stance. It remains fine for internal distribution, but it would invite Play review if store submission ever becomes real. Worth evaluating `FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING` as the semantically-honest alternative for a push relay first -- **unverified**, someone needs to confirm it is genuinely exempt from the dataSync time budget before we commit to it. **2. Timing argues for fixing this before the rebase, not after.** `onTimeout()` (API 35) is already usable today: the fork is at compileSdk 35. After the rebase we land on compileSdk/targetSdk 36 (Android 16), where foreground-service restrictions tighten further. So there is no tooling blocker now, and the cost of waiting only goes up.
Author
Owner

Correction: use specialUse, not remoteMessaging -- and this also fixes push after reboot

My earlier comment suggested evaluating FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING first and
treating the Play Store question as open. Both parts were wrong. Verified against the Android
docs and the platform PRD:

1. Play Store distribution is a committed target, not hypothetical. The platform PRD has
store submission in progress for both platforms, and the planned subscriber rollout uses the
same single store listing. So a specialUse declaration will be reviewed, and that is
affordable and expected rather than something to design around.

2. remoteMessaging does not fit and would be a mis-declaration. Its documented use case
is "Transfer text messages from one device to another. Assists with continuity of a user's
messaging tasks when they switch devices."
That is device-to-device handoff, not maintaining a
connection to a notification server. Declaring it here invites a policy rejection that is worse
than the review we would be avoiding.

3. specialUse is the honest fit. "Covers any valid foreground service use cases that
aren't covered by the other foreground service types."
It requires a
PROPERTY_SPECIAL_USE_FGS_SUBTYPE <property> justification, reviewed at submission.

4. Upstream's stance is not in tension with ours. Upstream strips
FOREGROUND_SERVICE_SPECIAL_USE because removing a library's foreground service lets them
skip a review they do not need -- upstream uses FCM and has no persistent service at all. This
fork needs one precisely because it does not use FCM. Different requirement, not a disagreement.

5. Only dataSync and mediaProcessing carry the 6-hour budget. specialUse has no
timeout. Android 16 adds no new foreground-service restrictions, so this survives the
compileSdk/targetSdk 35 -> 36 move.

Second bug the same change fixes

On Android 15+, BOOT_COMPLETED receivers are not allowed to launch dataSync foreground
services
(dataSync, camera, mediaPlayback, phoneCall, mediaProjection, microphone),
and the system throws ForegroundServiceStartNotAllowedException. NtfyBootReceiver starts
NtfyPushService from exactly that receiver, so push has not been surviving a reboot on
Android 15/16 devices at all.
specialUse is not in the disallowed list, so the same
declaration change fixes the boot path.

Supporting evidence for the diagnosis

The crash distribution matches the platform behaviour precisely: the foreground-service timeout
arrived in Android 15, and every timeout report comes from the two Android 16 handsets. The
Android 13/14 rugged device has never produced one -- all of its reports are the unrelated OOM
signature. Expect this to spread as the fleet updates to 15+.

Sources: Android 15 behavior changes,
foreground service types,
Android 16 behavior changes

### Correction: use `specialUse`, not `remoteMessaging` -- and this also fixes push after reboot My earlier comment suggested evaluating `FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING` first and treating the Play Store question as open. Both parts were wrong. Verified against the Android docs and the platform PRD: **1. Play Store distribution is a committed target, not hypothetical.** The platform PRD has store submission in progress for both platforms, and the planned subscriber rollout uses the same single store listing. So a `specialUse` declaration will be reviewed, and that is affordable and expected rather than something to design around. **2. `remoteMessaging` does not fit and would be a mis-declaration.** Its documented use case is *"Transfer text messages from one device to another. Assists with continuity of a user's messaging tasks when they switch devices."* That is device-to-device handoff, not maintaining a connection to a notification server. Declaring it here invites a policy rejection that is worse than the review we would be avoiding. **3. `specialUse` is the honest fit.** *"Covers any valid foreground service use cases that aren't covered by the other foreground service types."* It requires a `PROPERTY_SPECIAL_USE_FGS_SUBTYPE` `<property>` justification, reviewed at submission. **4. Upstream's stance is not in tension with ours.** Upstream strips `FOREGROUND_SERVICE_SPECIAL_USE` because removing a *library's* foreground service lets them skip a review they do not need -- upstream uses FCM and has no persistent service at all. This fork needs one precisely because it does not use FCM. Different requirement, not a disagreement. **5. Only `dataSync` and `mediaProcessing` carry the 6-hour budget.** `specialUse` has no timeout. Android 16 adds no new foreground-service restrictions, so this survives the compileSdk/targetSdk 35 -> 36 move. ### Second bug the same change fixes On Android 15+, `BOOT_COMPLETED` receivers are **not allowed to launch `dataSync` foreground services** (`dataSync`, `camera`, `mediaPlayback`, `phoneCall`, `mediaProjection`, `microphone`), and the system throws `ForegroundServiceStartNotAllowedException`. `NtfyBootReceiver` starts `NtfyPushService` from exactly that receiver, so **push has not been surviving a reboot on Android 15/16 devices at all.** `specialUse` is not in the disallowed list, so the same declaration change fixes the boot path. ### Supporting evidence for the diagnosis The crash distribution matches the platform behaviour precisely: the foreground-service timeout arrived in Android 15, and every timeout report comes from the two **Android 16** handsets. The Android 13/14 rugged device has never produced one -- all of its reports are the unrelated OOM signature. Expect this to spread as the fleet updates to 15+. Sources: [Android 15 behavior changes](https://developer.android.com/about/versions/15/behavior-changes-15), [foreground service types](https://developer.android.com/develop/background-work/services/fgs/service-types), [Android 16 behavior changes](https://developer.android.com/about/versions/16/behavior-changes-16)
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
slash/slash-mobile#18
No description provided.