updated 151025-03

This commit is contained in:
wengki81 2025-10-16 01:20:23 +08:00
parent c306b59b9e
commit a392b97e50
8 changed files with 94 additions and 6 deletions

View File

@ -427,6 +427,10 @@ class DumonGeolocation : Plugin() {
BgPrefs.setBackgroundMinPostIntervalMs(context, v) BgPrefs.setBackgroundMinPostIntervalMs(context, v)
LogUtils.d("DUMON_GEOLOCATION", "Set background min post interval = ${v} ms") LogUtils.d("DUMON_GEOLOCATION", "Set background min post interval = ${v} ms")
} }
call.getBoolean("backgroundUseImuFallback")?.let {
BgPrefs.setBackgroundUseImuFallback(context, it)
LogUtils.d("DUMON_GEOLOCATION", "Set backgroundUseImuFallback = ${it}")
}
call.resolve() call.resolve()
} }

View File

@ -13,6 +13,8 @@ import androidx.core.content.ContextCompat
import com.dumon.plugin.geolocation.gps.GpsStatusManager import com.dumon.plugin.geolocation.gps.GpsStatusManager
import com.dumon.plugin.geolocation.gps.GpsTrackingMode import com.dumon.plugin.geolocation.gps.GpsTrackingMode
import com.dumon.plugin.geolocation.gps.SatelliteStatus import com.dumon.plugin.geolocation.gps.SatelliteStatus
import com.dumon.plugin.geolocation.imu.ImuData
import com.dumon.plugin.geolocation.imu.ImuSensorManager
import com.dumon.plugin.geolocation.utils.BgPrefs import com.dumon.plugin.geolocation.utils.BgPrefs
import com.dumon.plugin.geolocation.utils.LogUtils import com.dumon.plugin.geolocation.utils.LogUtils
import com.dumon.plugin.geolocation.utils.AuthStore import com.dumon.plugin.geolocation.utils.AuthStore
@ -27,12 +29,19 @@ import kotlin.math.*
class BackgroundLocationService : Service() { class BackgroundLocationService : Service() {
private var gpsManager: GpsStatusManager? = null private var gpsManager: GpsStatusManager? = null
private var imuManager: ImuSensorManager? = null
private var postUrl: String? = null private var postUrl: String? = null
private val postExecutor = Executors.newSingleThreadExecutor() private val postExecutor = Executors.newSingleThreadExecutor()
@Volatile private var pendingPostedLat: Double? = null @Volatile private var pendingPostedLat: Double? = null
@Volatile private var pendingPostedLon: Double? = null @Volatile private var pendingPostedLon: Double? = null
@Volatile private var pendingPostedTs: Long? = null @Volatile private var pendingPostedTs: Long? = null
@Volatile private var lastPostAttemptTs: Long = 0L @Volatile private var lastPostAttemptTs: Long = 0L
@Volatile private var latestImu: ImuData? = null
// Previous fix for derived speed
@Volatile private var prevLat: Double? = null
@Volatile private var prevLon: Double? = null
@Volatile private var prevTs: Long? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -56,12 +65,35 @@ class BackgroundLocationService : Service() {
postUrl = intent?.getStringExtra(EXTRA_POST_URL) postUrl = intent?.getStringExtra(EXTRA_POST_URL)
postUrl?.let { BgPrefs.setPostUrl(applicationContext, it) } postUrl?.let { BgPrefs.setPostUrl(applicationContext, it) }
// Start GNSS continuous updates; avoid Wi-Fi scans in background for efficiency // Start GNSS updates; avoid Wi-Fi scans in background for efficiency
gpsManager = GpsStatusManager( gpsManager = GpsStatusManager(
applicationContext, applicationContext,
onSatelliteStatusUpdate = { /* no-op in background by default */ }, onSatelliteStatusUpdate = { /* no-op in background by default */ },
onLocationUpdate = { location, isMocked -> onLocationUpdate = { location, isMocked ->
if (location.latitude == 0.0 && location.longitude == 0.0) return@GpsStatusManager if (location.latitude == 0.0 && location.longitude == 0.0) return@GpsStatusManager
// Compute speed with priority: GNSS > IMU > Derived > 0
val gnssSpeed: Float? = if (location.hasSpeed()) location.speed else null
val imuSnap = latestImu
val imuSpeed: Float? = imuSnap?.speed
val imuAccel: Float? = imuSnap?.acceleration
val imuDir: Float? = imuSnap?.directionRad
val derivedSpeed: Float? = run {
val pLat = prevLat; val pLon = prevLon; val pTs = prevTs
if (pLat != null && pLon != null && pTs != null) {
val dtSec = (location.time - pTs).toDouble() / 1000.0
if (dtSec >= 3.0 && dtSec <= 30.0) {
val dMeters = haversineMeters(pLat, pLon, location.latitude, location.longitude)
(dMeters / dtSec).toFloat()
} else null
} else null
}
val finalSpeed: Float = when {
gnssSpeed != null -> gnssSpeed
imuSpeed != null -> imuSpeed
derivedSpeed != null -> derivedSpeed
else -> 0f
}
BgPrefs.saveLatestFix( BgPrefs.saveLatestFix(
context = applicationContext, context = applicationContext,
latitude = location.latitude, latitude = location.latitude,
@ -70,9 +102,9 @@ class BackgroundLocationService : Service() {
timestamp = location.time, timestamp = location.time,
source = if (isMocked) "MOCK" else "GNSS", source = if (isMocked) "MOCK" else "GNSS",
isMocked = isMocked, isMocked = isMocked,
speed = null, speed = finalSpeed,
acceleration = null, acceleration = imuAccel,
directionRad = null, directionRad = imuDir,
) )
val endpoint = postUrl ?: BgPrefs.getPostUrl(applicationContext) val endpoint = postUrl ?: BgPrefs.getPostUrl(applicationContext)
@ -128,6 +160,7 @@ class BackgroundLocationService : Service() {
latitude = location.latitude, latitude = location.latitude,
longitude = location.longitude, longitude = location.longitude,
accuracy = location.accuracy.toDouble(), accuracy = location.accuracy.toDouble(),
speed = finalSpeed.toDouble(),
timestamp = location.time, timestamp = location.time,
source = if (isMocked) "MOCK" else "GNSS", source = if (isMocked) "MOCK" else "GNSS",
isMocked = isMocked isMocked = isMocked
@ -142,6 +175,10 @@ class BackgroundLocationService : Service() {
} }
} }
} }
// Update previous for next derived computation
prevLat = location.latitude
prevLon = location.longitude
prevTs = location.time
} }
) )
@ -150,6 +187,14 @@ class BackgroundLocationService : Service() {
val interval = BgPrefs.getBackgroundIntervalMs(applicationContext, 5000L) val interval = BgPrefs.getBackgroundIntervalMs(applicationContext, 5000L)
gpsManager?.setPollingInterval(interval) gpsManager?.setPollingInterval(interval)
// Start IMU to provide speed fallback in background (optional)
if (BgPrefs.getBackgroundUseImuFallback(applicationContext, false)) {
imuManager = ImuSensorManager(applicationContext) { data ->
latestImu = data
}
try { imuManager?.start() } catch (_: Exception) {}
}
return START_STICKY return START_STICKY
} }
@ -157,6 +202,8 @@ class BackgroundLocationService : Service() {
LogUtils.d("BG_SERVICE", "onDestroy") LogUtils.d("BG_SERVICE", "onDestroy")
gpsManager?.stop() gpsManager?.stop()
gpsManager = null gpsManager = null
try { imuManager?.stop() } catch (_: Exception) {}
imuManager = null
BgPrefs.setActive(this, false) BgPrefs.setActive(this, false)
try { postExecutor.shutdownNow() } catch (_: Exception) {} try { postExecutor.shutdownNow() } catch (_: Exception) {}
super.onDestroy() super.onDestroy()
@ -209,11 +256,12 @@ class BackgroundLocationService : Service() {
latitude: Double, latitude: Double,
longitude: Double, longitude: Double,
accuracy: Double, accuracy: Double,
speed: Double,
timestamp: Long, timestamp: Long,
source: String, source: String,
isMocked: Boolean isMocked: Boolean
) { ) {
val json = """{"source":"$source","timestamp":$timestamp,"latitude":$latitude,"longitude":$longitude,"accuracy":$accuracy,"isMocked":$isMocked}""" val json = """{"source":"$source","timestamp":$timestamp,"latitude":$latitude,"longitude":$longitude,"accuracy":$accuracy,"speed":$speed,"isMocked":$isMocked}"""
val url = URL(endpoint) val url = URL(endpoint)
val conn = (url.openConnection() as HttpURLConnection).apply { val conn = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "POST" requestMethod = "POST"

View File

@ -23,6 +23,7 @@ object BgPrefs {
private const val KEY_POST_MIN_DIST_M = "bg_post_min_dist_m" private const val KEY_POST_MIN_DIST_M = "bg_post_min_dist_m"
private const val KEY_POST_MIN_ACC_M = "bg_post_min_acc_m" private const val KEY_POST_MIN_ACC_M = "bg_post_min_acc_m"
private const val KEY_POST_MIN_INTERVAL_MS = "bg_post_min_interval_ms" private const val KEY_POST_MIN_INTERVAL_MS = "bg_post_min_interval_ms"
private const val KEY_BG_USE_IMU_FALLBACK = "bg_use_imu_fallback"
fun setActive(context: Context, active: Boolean) { fun setActive(context: Context, active: Boolean) {
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
@ -146,6 +147,18 @@ object BgPrefs {
.coerceAtLeast(0L) .coerceAtLeast(0L)
} }
fun setBackgroundUseImuFallback(context: Context, enabled: Boolean) {
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.edit()
.putBoolean(KEY_BG_USE_IMU_FALLBACK, enabled)
.apply()
}
fun getBackgroundUseImuFallback(context: Context, defaultValue: Boolean = false): Boolean {
return context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.getBoolean(KEY_BG_USE_IMU_FALLBACK, defaultValue)
}
fun setPostUrl(context: Context, url: String?) { fun setPostUrl(context: Context, url: String?) {
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.edit() .edit()

7
dist/docs.json vendored
View File

@ -565,6 +565,13 @@
"docs": "", "docs": "",
"complexTypes": [], "complexTypes": [],
"type": "number | undefined" "type": "number | undefined"
},
{
"name": "backgroundUseImuFallback",
"tags": [],
"docs": "",
"complexTypes": [],
"type": "boolean | undefined"
} }
] ]
}, },

View File

@ -41,6 +41,7 @@ export interface DumonGeoOptions {
backgroundPostMinDistanceMeters?: number; backgroundPostMinDistanceMeters?: number;
backgroundPostMinAccuracyMeters?: number; backgroundPostMinAccuracyMeters?: number;
backgroundMinPostIntervalMs?: number; backgroundMinPostIntervalMs?: number;
backgroundUseImuFallback?: boolean;
} }
export interface PermissionStatus { export interface PermissionStatus {
location: 'granted' | 'denied'; location: 'granted' | 'denied';

View File

@ -1 +1 @@
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// export interface SatelliteStatus {\n// satellitesInView: number;\n// usedInFix: number;\n// constellationCounts: { [key: string]: number };\n// }\n\n// export interface WifiAp {\n// ssid: string;\n// bssid: string;\n// rssi: number;\n// distance?: number;\n// }\n\n// export interface WifiScanResult {\n// apCount: number;\n// aps: WifiAp[];\n// }\n\n// export interface ImuData {\n// accelX: number;\n// accelY: number;\n// accelZ: number;\n// gyroX: number;\n// gyroY: number;\n// gyroZ: number;\n// speed?: number;\n// acceleration?: number;\n// directionRad?: number;\n// }\n\n// export interface GpsData {\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n// satellitesInView?: number;\n// usedInFix?: number;\n// constellationCounts?: { [key: string]: number };\n// }\n\n// export interface PositioningData {\n// source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n// timestamp: number;\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n\n// gnssData?: SatelliteStatus;\n// wifiData?: WifiAp[];\n// imuData?: ImuData;\n// }\n\nexport interface PositioningData {\n source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n timestamp: number;\n latitude: number;\n longitude: number;\n accuracy: number;\n speed: number;\n acceleration: number;\n directionRad: number;\n isMocked: boolean;\n mode: 'normal' | 'driving';\n predicted?: boolean;\n // Optional detailed speed fields and provenance\n speedSource?: 'GNSS' | 'IMU' | 'DELTA' | 'NONE';\n speedGnss?: number; // m/s from Location.getSpeed when available and fresh\n speedImu?: number; // m/s from IMU fusion (internal heuristic)\n speedDerived?: number; // m/s from delta-position / delta-time (coarse)\n}\n\nexport interface SatelliteStatus {\n satellitesInView: number;\n usedInFix: number;\n constellationCounts: { [key: string]: number };\n}\n\nexport interface DumonGeoOptions {\n distanceThresholdMeters?: number;\n speedChangeThreshold?: number;\n directionChangeThreshold?: number;\n emitDebounceMs?: number;\n drivingEmitIntervalMs?: number;\n wifiScanIntervalMs?: number;\n enableWifiRtt?: boolean;\n enableLogging?: boolean;\n enableForwardPrediction?: boolean;\n maxPredictionSeconds?: number;\n emitGnssStatus?: boolean;\n suppressMockedUpdates?: boolean;\n keepScreenOn?: boolean;\n backgroundPollingIntervalMs?: number; // Android background polling interval\n backgroundPostMinDistanceMeters?: number; // Android background min distance to post\n backgroundPostMinAccuracyMeters?: number; // Android background min acceptable accuracy for POST (meters)\n backgroundMinPostIntervalMs?: number; // Android background minimum interval between POST attempts\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise<void>;\n stopPositioning(): Promise<void>;\n getLatestPosition(): Promise<PositioningData>;\n checkAndRequestPermissions(): Promise<PermissionStatus>;\n setOptions(options: DumonGeoOptions): Promise<void>;\n getGnssStatus(): Promise<SatelliteStatus | null>;\n getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }>;\n // Background tracking (Android)\n startBackgroundTracking(options?: {\n title?: string;\n text?: string;\n channelId?: string;\n channelName?: string;\n postUrl?: string; // optional: service will POST latest fixes here as JSON\n }): Promise<void>;\n stopBackgroundTracking(): Promise<void>;\n isBackgroundTrackingActive(): Promise<{ active: boolean }>;\n getBackgroundLatestPosition(): Promise<PositioningData | null>;\n openBackgroundPermissionSettings(): Promise<void>;\n openNotificationPermissionSettings(): Promise<void>;\n // Auth token management for background posting\n setAuthTokens(tokens: { accessToken: string; refreshToken: string }): Promise<void>;\n clearAuthTokens(): Promise<void>;\n getAuthState(): Promise<{ present: boolean }>;\n setBackgroundPostUrl(options: { url?: string }): Promise<void>;\n getBackgroundPostUrl(): Promise<{ url: string | null }>;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise<void>;\n\n setGpsMode(options: { mode: 'normal' | 'driving' }): Promise<void>;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n\n addListener(\n eventName: 'onGnssStatus',\n listenerFunc: (data: SatelliteStatus) => void\n ): PluginListenerHandle;\n}\n"]} {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// export interface SatelliteStatus {\n// satellitesInView: number;\n// usedInFix: number;\n// constellationCounts: { [key: string]: number };\n// }\n\n// export interface WifiAp {\n// ssid: string;\n// bssid: string;\n// rssi: number;\n// distance?: number;\n// }\n\n// export interface WifiScanResult {\n// apCount: number;\n// aps: WifiAp[];\n// }\n\n// export interface ImuData {\n// accelX: number;\n// accelY: number;\n// accelZ: number;\n// gyroX: number;\n// gyroY: number;\n// gyroZ: number;\n// speed?: number;\n// acceleration?: number;\n// directionRad?: number;\n// }\n\n// export interface GpsData {\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n// satellitesInView?: number;\n// usedInFix?: number;\n// constellationCounts?: { [key: string]: number };\n// }\n\n// export interface PositioningData {\n// source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n// timestamp: number;\n// latitude: number;\n// longitude: number;\n// accuracy: number;\n\n// gnssData?: SatelliteStatus;\n// wifiData?: WifiAp[];\n// imuData?: ImuData;\n// }\n\nexport interface PositioningData {\n source: 'GNSS' | 'WIFI' | 'FUSED' | 'MOCK';\n timestamp: number;\n latitude: number;\n longitude: number;\n accuracy: number;\n speed: number;\n acceleration: number;\n directionRad: number;\n isMocked: boolean;\n mode: 'normal' | 'driving';\n predicted?: boolean;\n // Optional detailed speed fields and provenance\n speedSource?: 'GNSS' | 'IMU' | 'DELTA' | 'NONE';\n speedGnss?: number; // m/s from Location.getSpeed when available and fresh\n speedImu?: number; // m/s from IMU fusion (internal heuristic)\n speedDerived?: number; // m/s from delta-position / delta-time (coarse)\n}\n\nexport interface SatelliteStatus {\n satellitesInView: number;\n usedInFix: number;\n constellationCounts: { [key: string]: number };\n}\n\nexport interface DumonGeoOptions {\n distanceThresholdMeters?: number;\n speedChangeThreshold?: number;\n directionChangeThreshold?: number;\n emitDebounceMs?: number;\n drivingEmitIntervalMs?: number;\n wifiScanIntervalMs?: number;\n enableWifiRtt?: boolean;\n enableLogging?: boolean;\n enableForwardPrediction?: boolean;\n maxPredictionSeconds?: number;\n emitGnssStatus?: boolean;\n suppressMockedUpdates?: boolean;\n keepScreenOn?: boolean;\n backgroundPollingIntervalMs?: number; // Android background polling interval\n backgroundPostMinDistanceMeters?: number; // Android background min distance to post\n backgroundPostMinAccuracyMeters?: number; // Android background min acceptable accuracy for POST (meters)\n backgroundMinPostIntervalMs?: number; // Android background minimum interval between POST attempts\n backgroundUseImuFallback?: boolean; // Android background: enable IMU-based speed fallback (default true)\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise<void>;\n stopPositioning(): Promise<void>;\n getLatestPosition(): Promise<PositioningData>;\n checkAndRequestPermissions(): Promise<PermissionStatus>;\n setOptions(options: DumonGeoOptions): Promise<void>;\n getGnssStatus(): Promise<SatelliteStatus | null>;\n getLocationServicesStatus(): Promise<{ gpsEnabled: boolean; networkEnabled: boolean }>;\n // Background tracking (Android)\n startBackgroundTracking(options?: {\n title?: string;\n text?: string;\n channelId?: string;\n channelName?: string;\n postUrl?: string; // optional: service will POST latest fixes here as JSON\n }): Promise<void>;\n stopBackgroundTracking(): Promise<void>;\n isBackgroundTrackingActive(): Promise<{ active: boolean }>;\n getBackgroundLatestPosition(): Promise<PositioningData | null>;\n openBackgroundPermissionSettings(): Promise<void>;\n openNotificationPermissionSettings(): Promise<void>;\n // Auth token management for background posting\n setAuthTokens(tokens: { accessToken: string; refreshToken: string }): Promise<void>;\n clearAuthTokens(): Promise<void>;\n getAuthState(): Promise<{ present: boolean }>;\n setBackgroundPostUrl(options: { url?: string }): Promise<void>;\n getBackgroundPostUrl(): Promise<{ url: string | null }>;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise<void>;\n\n setGpsMode(options: { mode: 'normal' | 'driving' }): Promise<void>;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n\n addListener(\n eventName: 'onGnssStatus',\n listenerFunc: (data: SatelliteStatus) => void\n ): PluginListenerHandle;\n}\n"]}

View File

@ -62,6 +62,7 @@ export interface DumonGeoOptions {
backgroundPostMinDistanceMeters?: number; // Android: minimum perpindahan untuk POST (default 10m) backgroundPostMinDistanceMeters?: number; // Android: minimum perpindahan untuk POST (default 10m)
backgroundPostMinAccuracyMeters?: number; // Android: minimum akurasi fix untuk POST (meter); kosong = tidak dibatasi backgroundPostMinAccuracyMeters?: number; // Android: minimum akurasi fix untuk POST (meter); kosong = tidak dibatasi
backgroundMinPostIntervalMs?: number; // Android: interval minimum antar upaya POST (default ~10000 ms) backgroundMinPostIntervalMs?: number; // Android: interval minimum antar upaya POST (default ~10000 ms)
backgroundUseImuFallback?: boolean; // Android: aktif/nonaktifkan fallback speed via IMU saat background (default false)
} }
export interface PermissionStatus { export interface PermissionStatus {
@ -131,6 +132,19 @@ getBackgroundPostUrl(): Promise<{ url: string | null }>
``` ```
- Menjalankan tracking lokasi saat app dipause menggunakan Foreground Service. Menyimpan latest fix ke penyimpanan lokal agar bisa diambil saat kembali ke foreground. Opsi `postUrl` (opsional) akan mengirim setiap pembaruan lokasi via HTTP POST (JSON) ke endpoint tersebut. Untuk autentikasi header, panggil `setAuthTokens` (menyimpan token secara native) agar setiap POST menyertakan header `Authorization: Bearer <token>` dan `refresh-token: <token>`. Di web: noop. - Menjalankan tracking lokasi saat app dipause menggunakan Foreground Service. Menyimpan latest fix ke penyimpanan lokal agar bisa diambil saat kembali ke foreground. Opsi `postUrl` (opsional) akan mengirim setiap pembaruan lokasi via HTTP POST (JSON) ke endpoint tersebut. Untuk autentikasi header, panggil `setAuthTokens` (menyimpan token secara native) agar setiap POST menyertakan header `Authorization: Bearer <token>` dan `refresh-token: <token>`. Di web: noop.
Catatan baterai (Android, background):
- Secara default fallback IMU dimatikan. Aktifkan `backgroundUseImuFallback: true` bila Anda butuh estimasi speed yang lebih halus saat sinyal GNSS kurang baik, dengan konsekuensi konsumsi baterai meningkat. Saat dimatikan, speed di background hanya berasal dari GNSS (jika tersedia) atau turunan Δpos/Δt; `acceleration` dan `directionRad` akan terset ke 0.
Format payload POST (background):
- `source`: string (`GNSS` | `MOCK`)
- `timestamp`: number (epoch ms)
- `latitude`: number
- `longitude`: number
- `accuracy`: number (meter)
- `speed`: number (m/s)
- `isMocked`: boolean
### Events ### Events
```ts ```ts

View File

@ -94,6 +94,7 @@ export interface DumonGeoOptions {
backgroundPostMinDistanceMeters?: number; // Android background min distance to post backgroundPostMinDistanceMeters?: number; // Android background min distance to post
backgroundPostMinAccuracyMeters?: number; // Android background min acceptable accuracy for POST (meters) backgroundPostMinAccuracyMeters?: number; // Android background min acceptable accuracy for POST (meters)
backgroundMinPostIntervalMs?: number; // Android background minimum interval between POST attempts backgroundMinPostIntervalMs?: number; // Android background minimum interval between POST attempts
backgroundUseImuFallback?: boolean; // Android background: enable IMU-based speed fallback (default false)
} }
export interface PermissionStatus { export interface PermissionStatus {