as-app/docs/location-tracking-qa.md

180 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Location tracking QA checklist
Applies to the BackgroundGeolocation integration:
- [`trackLocation()`](src/location/trackLocation.js:11)
- [`createTrackingController()`](src/location/bggeo/createTrackingController.js:1)
- [`TRACKING_PROFILES`](src/location/backgroundGeolocationConfig.js:190)
## Goals
1. Updates only when moved enough
- IDLE: record/upload only after moving ~200m.
- ACTIVE: record/upload after moving ~25m.
2. Works in foreground, background and terminated (Android + iOS).
3. Avoid uploads while stationary.
## Current implementation notes
- Movement-driven recording only:
- IDLE relies on the SDK's **stop-detection + stationary geofence** (`geolocation.stopOnStationary` + `geolocation.stationaryRadius`) to avoid periodic stationary updates on Android.
- We explicitly exit moving mode on entry to IDLE (`changePace(false)`) to prevent drift-generated periodic locations.
- If the SDK later reports a real move (`onMotionChange(isMoving:true)`), JS may request **one** persisted fix as a fallback.
- We intentionally do not rely on time-based updates.
- ACTIVE uses `geolocation.distanceFilter: 25`.
- JS may request a persisted fix when entering ACTIVE (see [`applyProfile()`](src/location/bggeo/createTrackingController.js:170)).
- Upload strategy is intentionally simple:
- Keep only the latest persisted geopoint: `persistence.maxRecordsToPersist: 1`.
- No batching / thresholds: `batchSync: false`, `autoSyncThreshold: 0`.
- When authenticated, each persisted location should upload immediately via native HTTP (works while JS is suspended).
- Pre-auth: BGGeo tracking is disabled (do not start). UI-only location uses `expo-location`.
- Stationary noise suppression:
- Native accuracy gate for persisted/uploaded locations: `geolocation.filter.trackingAccuracyThreshold: 100`.
- Identical location suppression: `geolocation.allowIdenticalLocations: false`.
- IDLE primarily relies on stop-detection + stationary geofence (`stopOnStationary: true`) to eliminate periodic stationary updates.
- Elasticity disabled (`disableElasticity: true`) to avoid dynamic distanceFilter shrink.
- Extra safety: any JS-triggered persisted fix requests are tagged and ignored if accuracy > 100m.
## Concise testing checklist (Android + iOS)
### 1) Baseline setup
- App has foreground + background location permissions.
- Motion/Activity permission granted (iOS motion, Android activity-recognition if prompted).
- Logged-in (to validate native HTTP uploads).
### 2) IDLE (no open alert)
1. Launch app and confirm there is **no open alert** owned by the current user.
2. Leave phone stationary for 10+ minutes (screen on and screen off).
- Expect: no periodic server uploads.
3. Walk/drive ~250m.
- Expect: a movement-triggered persisted location + upload.
### 3) ACTIVE (open alert)
1. Open an alert owned by the current user.
2. Move ~30m.
- Expect: at least one persisted location reaches server quickly.
3. Continue moving.
- Expect: updates align with movement (distanceFilter-based), not time.
### 4) Lifecycle coverage
- Foreground → background: repeat IDLE and ACTIVE steps.
- Terminated:
- Android: swipe-away from recents, then move the above distances and verify server updates.
- iOS: swipe-kill, then move significantly and verify app relaunch + upload after relaunch.
## Basic preconditions
- Location permissions: foreground + background granted.
- Motion permission granted.
- Network reachable.
## Foreground behavior
### IDLE (no open alert)
1. Launch app, ensure no open alert.
2. Stay stationary for 5+ minutes.
- Expect: no repeated server updates.
3. Walk/drive ~250m.
- Expect: `onMotionChange(isMoving:true)` then one persisted location + upload.
### ACTIVE (open alert)
1. Open an alert owned by the current user.
2. Move ~30m.
- Expect: at least one location persisted + uploaded.
3. Continue moving.
- Expect: periodic updates roughly aligned with movement, not time.
## Background behavior
### IDLE
1. Put app in background.
2. Stay stationary.
- Expect: no periodic uploads.
3. Move ~250m.
- Expect: `onMotionChange(isMoving:true)` then one persisted record and upload.
### ACTIVE
1. Put app in background.
2. Move ~30m.
- Expect: updates continue.
## Terminated behavior
### Android
1. Ensure tracking enabled and authenticated.
2. Swipe the app away from recents / kill the task.
3. Move ~250m in IDLE.
- Expect: native service still records + uploads.
4. Move ~30m in ACTIVE.
- Expect: native service still records + uploads.
> Note: This does **not** include Android Settings → **Force stop**.
> Force-stop prevents background services and receivers from running; no SDK can reliably track after that.
### iOS
1. Swipe-kill the app.
2. Move significantly (expect iOS to relaunch app on stationary-geofence exit).
- Expect: tracking resumes and uploads after movement.
## Test matrix (quick)
| Platform | App state | Profile | Move | Expected signals |
|---|---|---|---:|---|
| Android | foreground | IDLE | ~250m | [`onMotionChange`](src/location/bggeo/createTrackingController.js:311) then [`onLocation`](src/location/bggeo/createTrackingController.js:286) (sample=false), then [`onHttp`](src/location/bggeo/createTrackingController.js:302) |
| Android | background | IDLE | ~250m | same as above |
| Android | swipe-away | IDLE | ~250m | native geofence triggers; verify server update; app may relaunch to deliver JS logs |
| Android | foreground | ACTIVE | ~30m | location + upload continues |
| iOS | background | IDLE | ~250m | movement-driven update; no periodic uploads while stationary |
| iOS | swipe-killed | IDLE | significant | OS relaunch on movement; upload after relaunch |
## What to look for in logs
- No time-based uploads: heartbeat is disabled (`heartbeatInterval: 0`).
- Movement-only uploads:
- IDLE: look for `Motion change` (isMoving=true).
- ACTIVE distance threshold: `distanceFilter: 25` in [`TRACKING_PROFILES`](src/location/backgroundGeolocationConfig.js:148).
- Attribution for `getCurrentPosition`:
- `Location update received` logs include `extras.req_reason` and `extras.req_persist`.
- Persisted-fix reasons to expect: `active_profile_enter`, `moving_edge`, `startup_fix`, `identity_fix:*`.
- Accuracy gate signals:
- A persisted-fix request can be logged but later ignored due to accuracy > 100m.
- If the server still receives periodic updates while stationary, check that the uploaded record has acceptable accuracy and that the device isn't flapping between moving/stationary.
## Debugging tips
- Observe logs in app (dev/staging):
- `Motion change` edges
- `HTTP response` when uploads fail or in dev/staging
- pending queue (`BackgroundGeolocation.getCount()` via [`bggeoGetStatusSnapshot()`](src/location/bggeo/diagnostics.js:15))
## Android-specific note (stationary-geofence EXIT loop)
Some Android devices can repeatedly trigger the SDK's internal **stationary geofence EXIT** using a very poor "trigger" fix (eg `hAcc=500m`).
This can cause false motion transitions and periodic persisted uploads even when the phone is not moving.
Mitigation applied:
- Android IDLE disables `geolocation.stopOnStationary` (we do **not** rely on stationary-geofence mode in IDLE on Android).
- See [`BASE_GEOLOCATION_CONFIG.geolocation.stopOnStationary`](src/location/backgroundGeolocationConfig.js:1) and [`TRACKING_PROFILES.idle.geolocation.stopOnStationary`](src/location/backgroundGeolocationConfig.js:1).
- Android IDLE no longer uses `geolocation.useSignificantChangesOnly`.
- Reason: this mode can record only "several times/hour" and was observed to miss timely updates
after moving ~200300m while the app is backgrounded on some devices.
- IDLE now relies on `distanceFilter: 200` plus native drift filtering.
- See [`TRACKING_PROFILES.idle`](src/location/backgroundGeolocationConfig.js:190).
Diagnostics:
- `onGeofence` events are not explicitly logged anymore (we rely on motion/location/http + the in-app diagnostics helpers).