updated 151025-03
This commit is contained in:
parent
c306b59b9e
commit
a392b97e50
@ -427,6 +427,10 @@ class DumonGeolocation : Plugin() {
|
||||
BgPrefs.setBackgroundMinPostIntervalMs(context, v)
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,8 @@ import androidx.core.content.ContextCompat
|
||||
import com.dumon.plugin.geolocation.gps.GpsStatusManager
|
||||
import com.dumon.plugin.geolocation.gps.GpsTrackingMode
|
||||
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.LogUtils
|
||||
import com.dumon.plugin.geolocation.utils.AuthStore
|
||||
@ -27,12 +29,19 @@ import kotlin.math.*
|
||||
class BackgroundLocationService : Service() {
|
||||
|
||||
private var gpsManager: GpsStatusManager? = null
|
||||
private var imuManager: ImuSensorManager? = null
|
||||
private var postUrl: String? = null
|
||||
private val postExecutor = Executors.newSingleThreadExecutor()
|
||||
@Volatile private var pendingPostedLat: Double? = null
|
||||
@Volatile private var pendingPostedLon: Double? = null
|
||||
@Volatile private var pendingPostedTs: Long? = null
|
||||
@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() {
|
||||
super.onCreate()
|
||||
@ -56,12 +65,35 @@ class BackgroundLocationService : Service() {
|
||||
postUrl = intent?.getStringExtra(EXTRA_POST_URL)
|
||||
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(
|
||||
applicationContext,
|
||||
onSatelliteStatusUpdate = { /* no-op in background by default */ },
|
||||
onLocationUpdate = { location, isMocked ->
|
||||
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(
|
||||
context = applicationContext,
|
||||
latitude = location.latitude,
|
||||
@ -70,9 +102,9 @@ class BackgroundLocationService : Service() {
|
||||
timestamp = location.time,
|
||||
source = if (isMocked) "MOCK" else "GNSS",
|
||||
isMocked = isMocked,
|
||||
speed = null,
|
||||
acceleration = null,
|
||||
directionRad = null,
|
||||
speed = finalSpeed,
|
||||
acceleration = imuAccel,
|
||||
directionRad = imuDir,
|
||||
)
|
||||
|
||||
val endpoint = postUrl ?: BgPrefs.getPostUrl(applicationContext)
|
||||
@ -128,6 +160,7 @@ class BackgroundLocationService : Service() {
|
||||
latitude = location.latitude,
|
||||
longitude = location.longitude,
|
||||
accuracy = location.accuracy.toDouble(),
|
||||
speed = finalSpeed.toDouble(),
|
||||
timestamp = location.time,
|
||||
source = if (isMocked) "MOCK" else "GNSS",
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@ -157,6 +202,8 @@ class BackgroundLocationService : Service() {
|
||||
LogUtils.d("BG_SERVICE", "onDestroy")
|
||||
gpsManager?.stop()
|
||||
gpsManager = null
|
||||
try { imuManager?.stop() } catch (_: Exception) {}
|
||||
imuManager = null
|
||||
BgPrefs.setActive(this, false)
|
||||
try { postExecutor.shutdownNow() } catch (_: Exception) {}
|
||||
super.onDestroy()
|
||||
@ -209,11 +256,12 @@ class BackgroundLocationService : Service() {
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
accuracy: Double,
|
||||
speed: Double,
|
||||
timestamp: Long,
|
||||
source: String,
|
||||
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 conn = (url.openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "POST"
|
||||
|
||||
@ -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_ACC_M = "bg_post_min_acc_m"
|
||||
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) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
@ -146,6 +147,18 @@ object BgPrefs {
|
||||
.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?) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
|
||||
7
dist/docs.json
vendored
7
dist/docs.json
vendored
@ -565,6 +565,13 @@
|
||||
"docs": "",
|
||||
"complexTypes": [],
|
||||
"type": "number | undefined"
|
||||
},
|
||||
{
|
||||
"name": "backgroundUseImuFallback",
|
||||
"tags": [],
|
||||
"docs": "",
|
||||
"complexTypes": [],
|
||||
"type": "boolean | undefined"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
1
dist/esm/definitions.d.ts
vendored
1
dist/esm/definitions.d.ts
vendored
@ -41,6 +41,7 @@ export interface DumonGeoOptions {
|
||||
backgroundPostMinDistanceMeters?: number;
|
||||
backgroundPostMinAccuracyMeters?: number;
|
||||
backgroundMinPostIntervalMs?: number;
|
||||
backgroundUseImuFallback?: boolean;
|
||||
}
|
||||
export interface PermissionStatus {
|
||||
location: 'granted' | 'denied';
|
||||
|
||||
2
dist/esm/definitions.js.map
vendored
2
dist/esm/definitions.js.map
vendored
@ -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"]}
|
||||
@ -62,6 +62,7 @@ export interface DumonGeoOptions {
|
||||
backgroundPostMinDistanceMeters?: number; // Android: minimum perpindahan untuk POST (default 10m)
|
||||
backgroundPostMinAccuracyMeters?: number; // Android: minimum akurasi fix untuk POST (meter); kosong = tidak dibatasi
|
||||
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 {
|
||||
@ -131,6 +132,19 @@ getBackgroundPostUrl(): Promise<{ url: string | null }>
|
||||
```
|
||||
- Menjalankan tracking lokasi saat app di‑pause 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: no‑op.
|
||||
|
||||
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
|
||||
|
||||
```ts
|
||||
|
||||
@ -94,6 +94,7 @@ export interface DumonGeoOptions {
|
||||
backgroundPostMinDistanceMeters?: number; // Android background min distance to post
|
||||
backgroundPostMinAccuracyMeters?: number; // Android background min acceptable accuracy for POST (meters)
|
||||
backgroundMinPostIntervalMs?: number; // Android background minimum interval between POST attempts
|
||||
backgroundUseImuFallback?: boolean; // Android background: enable IMU-based speed fallback (default false)
|
||||
}
|
||||
|
||||
export interface PermissionStatus {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user