From 42d098e9f71af73a19d90f7aecca3ec80d01fd4b Mon Sep 17 00:00:00 2001 From: devthejo Date: Fri, 30 Jan 2026 15:58:28 +0100 Subject: [PATCH] chore: wip --- docs/location-tracking-qa.md | 18 ++++++++++ src/location/backgroundGeolocationConfig.js | 39 +++++++++++++++++---- src/location/trackLocation.js | 30 ++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/docs/location-tracking-qa.md b/docs/location-tracking-qa.md index 0c46fae..0f8e852 100644 --- a/docs/location-tracking-qa.md +++ b/docs/location-tracking-qa.md @@ -161,3 +161,21 @@ Applies to the BackgroundGeolocation integration: - `onLocation` events - `onHttp` events - pending queue (`BackgroundGeolocation.getCount()` in logs) + +## 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 uses `geolocation.useSignificantChangesOnly: true` to rely on OS-level significant movement events. + - See [`TRACKING_PROFILES.idle.geolocation.useSignificantChangesOnly`](src/location/backgroundGeolocationConfig.js:1). + +Diagnostics: + +- `onGeofence` events are logged (identifier/action/accuracy + current BGGeo state) to confirm whether the SDK is emitting stationary geofence events. + - See [`setBackgroundGeolocationEventHandlers({ onGeofence })`](src/location/trackLocation.js:1). diff --git a/src/location/backgroundGeolocationConfig.js b/src/location/backgroundGeolocationConfig.js index 38fcd28..1e6661d 100644 --- a/src/location/backgroundGeolocationConfig.js +++ b/src/location/backgroundGeolocationConfig.js @@ -1,7 +1,9 @@ +import { Platform } from "react-native"; import BackgroundGeolocation from "react-native-background-geolocation"; import env from "~/env"; const LOCATION_ACCURACY_GATE_M = 100; +const IS_ANDROID = Platform.OS === "android"; // Native filter to reduce GPS drift and suppress stationary jitter. // This is the primary mechanism to prevent unwanted persisted/uploaded points while the device @@ -39,12 +41,12 @@ export const BASE_GEOLOCATION_CONFIG = { // Logger config logger: { - // debug: true, + debug: true, // Logging can become large and also adds overhead; keep verbose logs to dev/staging. - logLevel: - __DEV__ || env.IS_STAGING - ? BackgroundGeolocation.LogLevel.Verbose - : BackgroundGeolocation.LogLevel.Error, + logLevel: BackgroundGeolocation.LogLevel.Verbose, + // __DEV__ || env.IS_STAGING + // ? BackgroundGeolocation.LogLevel.Verbose + // : BackgroundGeolocation.LogLevel.Error, }, // Geolocation config @@ -62,6 +64,12 @@ export const BASE_GEOLOCATION_CONFIG = { // Default to the IDLE profile behaviour. distanceFilter: 200, + // Ensure deterministic behavior when switching profiles. + // If we enable significant-changes mode in Android IDLE, we *must* explicitly + // restore it to `false` in ACTIVE (and other modes) to avoid the SDK keeping a + // previously-set `true` value. + useSignificantChangesOnly: false, + // Prevent dynamic distanceFilter shrink. // Elasticity can lower the effective threshold (eg while stationary), resulting in // unexpected frequent updates. @@ -73,7 +81,14 @@ export const BASE_GEOLOCATION_CONFIG = { // True-stationary strategy: once stop-detection decides we're stationary, stop active // tracking and rely on the stationary geofence to detect significant movement. // This is intended to eliminate periodic stationary updates on Android. - stopOnStationary: true, + // + // HOWEVER: On some Android devices, the stationary-geofence EXIT can be triggered by + // extremely poor "trigger" fixes (eg hAcc=500m), causing false motion transitions and + // periodic persisted uploads while the phone is actually stationary. + // + // Mitigation (Android IDLE): do NOT rely on stationary-geofence mode; instead rely on + // distanceFilter + native filtering. + stopOnStationary: IS_ANDROID ? false : true, stationaryRadius: 200, // Prevent identical/noise locations from being persisted. @@ -178,7 +193,14 @@ export const TRACKING_PROFILES = { // IMPORTANT: ACTIVE sets stopOnStationary:false. // Ensure we restore it when transitioning back to IDLE, otherwise the SDK may // continue recording while stationary. - stopOnStationary: true, + // + // Android mitigation: disable stopOnStationary to avoid stationary-geofence EXIT loops. + stopOnStationary: IS_ANDROID ? false : true, + + // Android IDLE: rely on OS-level significant movement only. + // This avoids periodic wakeups/records due to poor fused-location fixes while the phone + // is stationary (screen-off / locked scenarios). + useSignificantChangesOnly: IS_ANDROID, // QA helper: allow easier validation in dev/staging while keeping production at 200m. stationaryRadius: 200, @@ -198,6 +220,9 @@ export const TRACKING_PROFILES = { // ACTIVE target: frequent updates while moving. distanceFilter: 25, + // ACTIVE must not use significant-changes-only (we want continuous distance-based updates). + useSignificantChangesOnly: false, + // While ACTIVE, do not stop updates simply because the device appears stationary. // Motion-detection + distanceFilter should govern updates. stopOnStationary: false, diff --git a/src/location/trackLocation.js b/src/location/trackLocation.js index d936ef9..438a31f 100644 --- a/src/location/trackLocation.js +++ b/src/location/trackLocation.js @@ -1130,6 +1130,36 @@ export default function trackLocation() { } } }, + onGeofence: async (event) => { + // Diagnostic only: geofences are still used internally by the SDK (eg stationary geofence) + // even when we don't manage any app-defined geofences. + try { + const state = await BackgroundGeolocation.getState(); + locationLogger.info("Geofence event", { + identifier: event?.identifier, + action: event?.action, + accuracy: event?.location?.coords?.accuracy, + latitude: event?.location?.coords?.latitude, + longitude: event?.location?.coords?.longitude, + enabled: state?.enabled, + isMoving: state?.isMoving, + trackingMode: state?.trackingMode, + profile: currentProfile, + appState, + }); + } catch (e) { + locationLogger.info("Geofence event", { + identifier: event?.identifier, + action: event?.action, + accuracy: event?.location?.coords?.accuracy, + latitude: event?.location?.coords?.latitude, + longitude: event?.location?.coords?.longitude, + profile: currentProfile, + appState, + error: e?.message, + }); + } + }, onLocationError: (error) => { locationLogger.warn("Location error", { error: error?.message,