Foreground location service: permissions, notification, battery exemption #15

Closed
opened 2026-08-31 17:14:39 +02:00 by robert · 1 comment
robert commented 2026-08-31 17:14:39 +02:00 (Migrated from git.butzei.de)

Goal

Position updates must survive a whole ride with the screen off. This is where most Android bike apps fail.

Acceptance criteria

  • Foreground service with FOREGROUND_SERVICE_LOCATION and a persistent notification showing ride status
  • ACCESS_FINE_LOCATION requested with a clear rationale
  • Battery-optimisation exemption prompted for, with an explanation of why
  • Partial wake lock held only while a ride is running
  • Service restarts and restores state if the system kills it
  • Verified over a 2 hour ride with the screen off

Files

  • companion/.../ride/RideService.kt

Update — 2026-08-31: battery target and minimum Android version

  • The phone must lose no more than 40% of its battery over a 4-hour ride with GPS at 1 Hz and
    the screen off (NFR-B2). Measure it; if the budget is missed, reduce the GPS rate or the map frame
    rate before giving up on the target.
  • Minimum supported Android is 12 / API 31 (NFR-C2), so only the modern
    BLUETOOTH_SCAN / BLUETOOTH_CONNECT permission model is needed - no legacy
    location-permission-for-BLE workaround. Foreground service types are declared but not yet enforced
    at this API level; declare them correctly anyway.
## Goal Position updates must survive a whole ride with the screen off. This is where most Android bike apps fail. ## Acceptance criteria - [ ] Foreground service with `FOREGROUND_SERVICE_LOCATION` and a persistent notification showing ride status - [ ] `ACCESS_FINE_LOCATION` requested with a clear rationale - [ ] Battery-optimisation exemption prompted for, with an explanation of why - [ ] Partial wake lock held only while a ride is running - [ ] Service restarts and restores state if the system kills it - [ ] Verified over a 2 hour ride with the screen off ## Files - `companion/.../ride/RideService.kt` ## Update — 2026-08-31: battery target and minimum Android version - **The phone must lose no more than 40% of its battery over a 4-hour ride** with GPS at 1 Hz and the screen off (NFR-B2). Measure it; if the budget is missed, reduce the GPS rate or the map frame rate before giving up on the target. - **Minimum supported Android is 12 / API 31** (NFR-C2), so only the modern `BLUETOOTH_SCAN` / `BLUETOOTH_CONNECT` permission model is needed - no legacy location-permission-for-BLE workaround. Foreground service types are declared but not yet enforced at this API level; declare them correctly anyway.
Owner

Closed by PR #97 (merged): RideService (:companion:ride, foregroundServiceType="location", API 34+ FOREGROUND_SERVICE_LOCATION), correctly-ordered permission flow (fine+coarse location together, background location as a separate second request only reachable after the first is granted, POST_NOTIFICATIONS gated to API 33+, battery-exemption last) via RideSetupActivity, each step with its own plain-language rationale (NFR-S6). Nothing requested at app launch \u2014 the whole flow triggers only when a ride is started. RideSetupState/RideSetupStep (:companion:core, zero android.* imports) is the pure decision logic, 8 host tests; RidePermissions is the thin Android-state bridge feeding it.\n\nVerified for real: ./gradlew :companion:assembleDebug succeeds, merged manifest inspected directly (all permissions present, service correctly declared not-exported with the right foregroundServiceType), :companion:core:test and :companion:lintDebug both green.\n\nOut of scope by design: real speed/distance/BLE arbitration (#17/#19) and full ride-session restoration across process death (#12) \u2014 this service's restart handling only proves the platform-survival shell resumes.

Closed by PR #97 (merged): RideService (:companion:ride, foregroundServiceType=\"location\", API 34+ FOREGROUND_SERVICE_LOCATION), correctly-ordered permission flow (fine+coarse location together, background location as a separate second request only reachable after the first is granted, POST_NOTIFICATIONS gated to API 33+, battery-exemption last) via RideSetupActivity, each step with its own plain-language rationale (NFR-S6). Nothing requested at app launch \u2014 the whole flow triggers only when a ride is started. RideSetupState/RideSetupStep (:companion:core, zero android.* imports) is the pure decision logic, 8 host tests; RidePermissions is the thin Android-state bridge feeding it.\n\n**Verified for real**: `./gradlew :companion:assembleDebug` succeeds, merged manifest inspected directly (all permissions present, service correctly declared not-exported with the right foregroundServiceType), `:companion:core:test` and `:companion:lintDebug` both green.\n\nOut of scope by design: real speed/distance/BLE arbitration (#17/#19) and full ride-session restoration across process death (#12) \u2014 this service's restart handling only proves the platform-survival shell resumes.
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
robert/PedalPebble#15
No description provided.