fix(push): hold the ntfy connection as specialUse and restart it on boot #62

Merged
whitlocktech merged 1 commits from fix/push-special-use into edge 2026-10-29 08:10:19 -05:00
Member

Step 1 of M8's order from here (docs#390, PLAN.md M8, decided by the org lead 2026-10-29).

Why

PushService was a dataSync foreground service. Android 15 stops dataSync after six hours a day and expects the service to stop itself in onTimeout. Ours never did, so on edge (target 36) the app would crash once the limit was reached. A held-open push connection isn't a transfer either, and transfers are what Play reserves dataSync for.

What

  • specialUse foreground type, with the PROPERTY_SPECIAL_USE_FGS_SUBTYPE reason that Play reviews. This is the same approach ntfy takes on Play, and the type has no daily limit. The permission is now FOREGROUND_SERVICE_SPECIAL_USE instead of …_DATA_SYNC.
  • PushBootReceiver (not exported) restarts push after BOOT_COMPLETED and MY_PACKAGE_REPLACED, but only if push was on and a registration is still saved. A sign-out clears the registration. The receiver starts the service from the saved topic and doesn't re-register, because the network often isn't up at boot. The stream client's backoff covers the wait.
  • PushBootReceiverTest: 5 JVM tests for when it restarts and when it doesn't.

Checked

  • ./gradlew testDebugUnitTest lintDebug assembleDebug: BUILD SUCCESSFUL.

  • Walked on the s22_ultra emulator (API 35) against a local website whose push relay was set to https://ntfy.sh. I signed in as a throwaway admin and turned on news push.

    • dumpsys: PushService isForeground=true types=0x40000000 (specialUse).
    • A tickle to the app's topic produced a notification on push_messages.
    • After adb reboot, with the app not opened, the service was back as specialUse, and a tickle still arrived.
    • After adb install -r (an update), the service was back with a new record.
    • After signing out, then rebooting: the receiver's process started, and there was no service.
  • Not checked: Play's review of the declaration. That needs the video, which I'll record on the emulator for the console.

  • AI-assisted (Claude Code)

🤖 Generated with Claude Code

https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY

Step 1 of M8's order from here (docs#390, `PLAN.md` M8, decided by the org lead 2026-10-29). ## Why `PushService` was a `dataSync` foreground service. Android 15 stops `dataSync` after six hours a day and expects the service to stop itself in `onTimeout`. Ours never did, so on `edge` (target 36) the app would crash once the limit was reached. A held-open push connection isn't a transfer either, and transfers are what Play reserves `dataSync` for. ## What - **`specialUse`** foreground type, with the `PROPERTY_SPECIAL_USE_FGS_SUBTYPE` reason that Play reviews. This is the same approach ntfy takes on Play, and the type has no daily limit. The permission is now `FOREGROUND_SERVICE_SPECIAL_USE` instead of `…_DATA_SYNC`. - **`PushBootReceiver`** (not exported) restarts push after `BOOT_COMPLETED` and `MY_PACKAGE_REPLACED`, but only if push was on and a registration is still saved. A sign-out clears the registration. The receiver starts the service from the saved topic and doesn't re-register, because the network often isn't up at boot. The stream client's backoff covers the wait. - `PushBootReceiverTest`: 5 JVM tests for when it restarts and when it doesn't. ## Checked - `./gradlew testDebugUnitTest lintDebug assembleDebug`: BUILD SUCCESSFUL. - Walked on the `s22_ultra` emulator (API 35) against a local website whose push relay was set to `https://ntfy.sh`. I signed in as a throwaway admin and turned on news push. - `dumpsys`: `PushService isForeground=true types=0x40000000` (specialUse). - A tickle to the app's topic produced a notification on `push_messages`. - **After `adb reboot`**, with the app not opened, the service was back as specialUse, and a tickle still arrived. - **After `adb install -r`** (an update), the service was back with a new record. - **After signing out, then rebooting**: the receiver's process started, and there was no service. - Not checked: Play's review of the declaration. That needs the video, which I'll record on the emulator for the console. - [x] AI-assisted (Claude Code) 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
wtclaude added 1 commit 2026-10-29 07:48:00 -05:00
fix(push): hold the ntfy connection as specialUse and restart it on boot
All checks were successful
PR Checks / android-build (pull_request) Successful in 24m52s
690441a8c3
PushService was a dataSync foreground service. Android 15 stops dataSync
after six hours a day and expects the service to stop itself on
onTimeout; it never did, so with target 36 the app would crash. A held-open
push connection is not a transfer either, which is what Play reserves
dataSync for.

- The service is now specialUse, with the PROPERTY_SPECIAL_USE_FGS_SUBTYPE
  reason Play reviews (ntfy does the same on Play). No daily limit.
- PushBootReceiver restarts it after BOOT_COMPLETED and MY_PACKAGE_REPLACED
  when push was on and a registration is saved (a sign-out clears it). It
  starts the service from the saved topic instead of re-registering, since
  the network is often not up at boot.

PLAN.md M8, decided by the org lead 2026-10-29.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
whitlocktech merged commit 19c2f77a98 into edge 2026-10-29 08:10:19 -05:00
whitlocktech deleted branch fix/push-special-use 2026-10-29 08:10:20 -05:00
Sign in to join this conversation.
No description provided.