ADR-0026: Location capture (opt-in) and offline coordinate picker
- Status: Accepted (background-capture decision superseded by ADR-0034)
- Date: 2026-08-16
- Deciders: maintainers
- Related: ADR-0021, ADR-0015, ADR-0010
Context
ADR-0021 landed the storage (schema v6 location columns on transactions and
unmatched) and the pipeline seam (CapturePipeline.onNotification(...,
location?)), but capture never shipped — the listener passed null and no
permission was requested. This ADR ships capture: a device fix at
notification-parse time (opt-in) and a manual editor for correcting/adding
location.
Two constraints shape it:
- The app is offline-first with no INTERNET permission (ADR-0015), so an online tile map or geocoder is unavailable.
- The notification listener runs in the background. Actively requesting a
live GPS fix from a background service requires
ACCESS_BACKGROUND_LOCATION, a Play-restricted permission.
Decision
- Capture source: cached last-known location.
AndroidLocationProviderreadsLocationManager.getLastKnownLocation(GPS, network, passive — freshest wins, ≤24 h old). This needs onlyACCESS_COARSE_LOCATION/ACCESS_FINE_LOCATIONand avoidsACCESS_BACKGROUND_LOCATIONentirely. The trade-off — a possibly stale fix — is acceptable for an approximate "where did I spend" signal. - Gate: capture happens only when
KEY_CAPTURE_LOCATIONis"true"and a location permission is held; otherwise the listener passesnull(the no-location case the issue allows). - Opt-in: onboarding gains a fourth step (toggle + permission request);
Settings exposes the same toggle under Automation (
KEY_CAPTURE_LOCATION). - Manual edit:
TransactionDetailScreengains a location row (coords or "No location data") opening a full-screen offline coordinate picker (TransactionLocationScreen): aCanvasgrid, tap/drag pin, "locate current" button, Save/Cancel. No map tiles, no search, no network — honors ADR-0015. The picker projects a pixel offset from an anchor point into lat/lng via the purecommon/Geohelpers. - Retention: keep the raw fix;
placeName/placeIdremain null until reverse geocoding ships.
Consequences
- Play-restricted
ACCESS_BACKGROUND_LOCATIONis not requested, simplifying Play review (seedocs/PLAY_POLICY.md). - Location is approximate (last-known); no continuous tracking, no live fix in the background.
- Data Safety now discloses location collection (on-device, opt-in, not
shared);
docs/user-guide/security.mdanddocs/PLAY_POLICY.mddrop the "never reads location" claim. - The coordinate picker is deliberately coarse (no street context); real map tiles or search require revisiting ADR-0015.
Rejected alternatives
FusedLocationProviderClient.getCurrentLocation— addsplay-services-locationand, from a background listener, requiresACCESS_BACKGROUND_LOCATION.- OSMDroid/MapLibre with bundled offline tiles — larger APK and tile-set maintenance for marginal gain; a coordinate picker covers pin-a-point needs.
- Google Maps Compose — requires an API key + network tiles, conflicts with ADR-0015.
- Online geocoding search in the picker — requires INTERNET, rejected.