20260106-01
This commit is contained in:
parent
a392b97e50
commit
244f70b567
@ -40,13 +40,29 @@ import com.dumon.plugin.geolocation.utils.AuthStore
|
|||||||
@CapacitorPlugin(
|
@CapacitorPlugin(
|
||||||
name = "DumonGeolocation",
|
name = "DumonGeolocation",
|
||||||
permissions = [
|
permissions = [
|
||||||
Permission(strings = [
|
Permission(
|
||||||
|
alias = "location",
|
||||||
|
strings = [
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
]
|
||||||
|
),
|
||||||
|
Permission(
|
||||||
|
alias = "wifi",
|
||||||
|
strings = [
|
||||||
Manifest.permission.ACCESS_WIFI_STATE,
|
Manifest.permission.ACCESS_WIFI_STATE,
|
||||||
Manifest.permission.CHANGE_WIFI_STATE,
|
Manifest.permission.CHANGE_WIFI_STATE,
|
||||||
Manifest.permission.NEARBY_WIFI_DEVICES
|
Manifest.permission.NEARBY_WIFI_DEVICES
|
||||||
])
|
]
|
||||||
|
),
|
||||||
|
Permission(
|
||||||
|
alias = "backgroundLocation",
|
||||||
|
strings = [Manifest.permission.ACCESS_BACKGROUND_LOCATION]
|
||||||
|
),
|
||||||
|
Permission(
|
||||||
|
alias = "notifications",
|
||||||
|
strings = [Manifest.permission.POST_NOTIFICATIONS]
|
||||||
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
class DumonGeolocation : Plugin() {
|
class DumonGeolocation : Plugin() {
|
||||||
@ -76,6 +92,7 @@ class DumonGeolocation : Plugin() {
|
|||||||
private var prevTimestamp: Long = 0L
|
private var prevTimestamp: Long = 0L
|
||||||
// private val significantChangeThreshold = 0.00007 // ~7 meters
|
// private val significantChangeThreshold = 0.00007 // ~7 meters
|
||||||
private var significantChangeThreshold = 7.0 // ~7 meters
|
private var significantChangeThreshold = 7.0 // ~7 meters
|
||||||
|
private var gpsMinDistanceMeters = 7.0
|
||||||
private var speedChangeThreshold = 0.5f // m/s
|
private var speedChangeThreshold = 0.5f // m/s
|
||||||
private var directionChangeThreshold = 0.17f // ~10 deg
|
private var directionChangeThreshold = 0.17f // ~10 deg
|
||||||
|
|
||||||
@ -97,6 +114,10 @@ class DumonGeolocation : Plugin() {
|
|||||||
private var emitGnssStatus = false
|
private var emitGnssStatus = false
|
||||||
private var suppressMockedUpdates = false
|
private var suppressMockedUpdates = false
|
||||||
private var keepScreenOn = false
|
private var keepScreenOn = false
|
||||||
|
private var lastSingleFixRequestTs: Long = 0L
|
||||||
|
private val minSingleFixIntervalMs: Long = 1000L
|
||||||
|
private var pendingPermissionAlias: String? = null
|
||||||
|
private var pendingBackgroundPermissionAlias: String? = null
|
||||||
|
|
||||||
private var currentTrackingMode = GpsTrackingMode.NORMAL
|
private var currentTrackingMode = GpsTrackingMode.NORMAL
|
||||||
|
|
||||||
@ -183,15 +204,19 @@ class DumonGeolocation : Plugin() {
|
|||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun startPositioning(call: PluginCall) {
|
fun startPositioning(call: PluginCall) {
|
||||||
if (!PermissionUtils.hasLocationAndWifiPermissions(context)) {
|
if (!PermissionUtils.hasLocationPermissions(context)) {
|
||||||
call.reject("Required permissions not granted")
|
call.reject("Location permission not granted")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gpsManager?.start()
|
gpsManager?.start()
|
||||||
imuManager?.start()
|
imuManager?.start()
|
||||||
|
if (PermissionUtils.hasWifiScanPermissions(context)) {
|
||||||
wifiManager?.setEnableRtt(enableWifiRtt)
|
wifiManager?.setEnableRtt(enableWifiRtt)
|
||||||
wifiManager?.startPeriodicScan(wifiScanIntervalMs)
|
wifiManager?.startPeriodicScan(wifiScanIntervalMs)
|
||||||
|
} else {
|
||||||
|
LogUtils.w("DUMON_GEOLOCATION", "Wi-Fi scan permissions not granted; skipping Wi-Fi scans")
|
||||||
|
}
|
||||||
applyKeepScreenOn(keepScreenOn)
|
applyKeepScreenOn(keepScreenOn)
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
@ -212,6 +237,13 @@ class DumonGeolocation : Plugin() {
|
|||||||
val hasFine = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
val hasFine = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||||
val hasCoarse = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
val hasCoarse = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (now - lastSingleFixRequestTs < minSingleFixIntervalMs) {
|
||||||
|
call.resolve(buildPositionData())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastSingleFixRequestTs = now
|
||||||
|
|
||||||
val manager = gpsManager
|
val manager = gpsManager
|
||||||
if (manager == null || (!hasFine && !hasCoarse)) {
|
if (manager == null || (!hasFine && !hasCoarse)) {
|
||||||
// No manager or no permissions — fallback immediately
|
// No manager or no permissions — fallback immediately
|
||||||
@ -245,36 +277,10 @@ class DumonGeolocation : Plugin() {
|
|||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun checkAndRequestPermissions(call: PluginCall) {
|
fun checkAndRequestPermissions(call: PluginCall) {
|
||||||
// requestAllPermissions(call, "checkAndRequestPermissions")
|
if (!requestNextForegroundPermission(call)) {
|
||||||
val isLocationGranted =
|
|
||||||
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
||||||
val isWifiGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES) == PackageManager.PERMISSION_GRANTED
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLocationGranted || !isWifiGranted) {
|
|
||||||
requestAllPermissions(call, "onPermissionResult")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
call.resolve(buildForegroundPermissionResult())
|
||||||
val locationStatus = PermissionUtils.getPermissionStatus(
|
|
||||||
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
|
|
||||||
)
|
|
||||||
val wifiStatus = PermissionUtils.getPermissionStatus(
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
|
||||||
ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES)
|
|
||||||
else
|
|
||||||
PackageManager.PERMISSION_GRANTED
|
|
||||||
)
|
|
||||||
|
|
||||||
val result = JSObject().apply {
|
|
||||||
put("location", locationStatus)
|
|
||||||
put("wifi", wifiStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
call.resolve(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
@ -343,11 +349,13 @@ class DumonGeolocation : Plugin() {
|
|||||||
val mode = call.getString("mode") ?: "normal"
|
val mode = call.getString("mode") ?: "normal"
|
||||||
if (mode == "driving") {
|
if (mode == "driving") {
|
||||||
gpsManager?.startContinuousMode()
|
gpsManager?.startContinuousMode()
|
||||||
|
gpsManager?.setMinDistanceMeters(0.0)
|
||||||
currentTrackingMode = GpsTrackingMode.DRIVING
|
currentTrackingMode = GpsTrackingMode.DRIVING
|
||||||
startDrivingEmitLoop()
|
startDrivingEmitLoop()
|
||||||
LogUtils.d("DUMON_GEOLOCATION", "Switched to driving mode (continuous GPS)")
|
LogUtils.d("DUMON_GEOLOCATION", "Switched to driving mode (continuous GPS)")
|
||||||
} else {
|
} else {
|
||||||
gpsManager?.startPollingMode()
|
gpsManager?.startPollingMode()
|
||||||
|
gpsManager?.setMinDistanceMeters(gpsMinDistanceMeters)
|
||||||
currentTrackingMode = GpsTrackingMode.NORMAL
|
currentTrackingMode = GpsTrackingMode.NORMAL
|
||||||
stopDrivingEmitLoop()
|
stopDrivingEmitLoop()
|
||||||
LogUtils.d("DUMON_GEOLOCATION", "Switched to normal mode (polling GPS)")
|
LogUtils.d("DUMON_GEOLOCATION", "Switched to normal mode (polling GPS)")
|
||||||
@ -357,27 +365,21 @@ class DumonGeolocation : Plugin() {
|
|||||||
|
|
||||||
@PermissionCallback
|
@PermissionCallback
|
||||||
private fun onPermissionResult(call: PluginCall) {
|
private fun onPermissionResult(call: PluginCall) {
|
||||||
val locationStatus = PermissionUtils.getPermissionStatus(
|
if (!requestNextForegroundPermission(call)) {
|
||||||
ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
|
return
|
||||||
)
|
|
||||||
val wifiStatus = PermissionUtils.getPermissionStatus(
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
|
||||||
ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES)
|
|
||||||
else
|
|
||||||
PackageManager.PERMISSION_GRANTED
|
|
||||||
)
|
|
||||||
|
|
||||||
val result = JSObject().apply {
|
|
||||||
put("location", locationStatus)
|
|
||||||
put("wifi", wifiStatus)
|
|
||||||
}
|
}
|
||||||
|
call.resolve(buildForegroundPermissionResult())
|
||||||
call.resolve(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun setOptions(call: PluginCall) {
|
fun setOptions(call: PluginCall) {
|
||||||
call.getDouble("distanceThresholdMeters")?.let { significantChangeThreshold = it }
|
call.getDouble("distanceThresholdMeters")?.let {
|
||||||
|
significantChangeThreshold = it
|
||||||
|
gpsMinDistanceMeters = it
|
||||||
|
if (currentTrackingMode == GpsTrackingMode.NORMAL) {
|
||||||
|
gpsManager?.setMinDistanceMeters(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
call.getDouble("speedChangeThreshold")?.let { speedChangeThreshold = it.toFloat() }
|
call.getDouble("speedChangeThreshold")?.let { speedChangeThreshold = it.toFloat() }
|
||||||
call.getDouble("directionChangeThreshold")?.let { directionChangeThreshold = it.toFloat() }
|
call.getDouble("directionChangeThreshold")?.let { directionChangeThreshold = it.toFloat() }
|
||||||
call.getInt("emitDebounceMs")?.let {
|
call.getInt("emitDebounceMs")?.let {
|
||||||
@ -480,19 +482,15 @@ class DumonGeolocation : Plugin() {
|
|||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun startBackgroundTracking(call: PluginCall) {
|
fun startBackgroundTracking(call: PluginCall) {
|
||||||
|
if (!ensureBackgroundPermissions(call)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
val title = call.getString("title") ?: "Location tracking active"
|
val title = call.getString("title") ?: "Location tracking active"
|
||||||
val text = call.getString("text") ?: "Updating location in background"
|
val text = call.getString("text") ?: "Updating location in background"
|
||||||
val channelId = call.getString("channelId") ?: "DUMON_GEO_BG"
|
val channelId = call.getString("channelId") ?: "DUMON_GEO_BG"
|
||||||
val channelName = call.getString("channelName") ?: "Dumon Geolocation"
|
val channelName = call.getString("channelName") ?: "Dumon Geolocation"
|
||||||
val postUrl = call.getString("postUrl")
|
val postUrl = call.getString("postUrl")
|
||||||
|
|
||||||
val fine = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
||||||
val coarse = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
||||||
if (!fine && !coarse) {
|
|
||||||
call.reject("Location permission not granted")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val intent = Intent(context, BackgroundLocationService::class.java).apply {
|
val intent = Intent(context, BackgroundLocationService::class.java).apply {
|
||||||
putExtra(BackgroundLocationService.EXTRA_CHANNEL_ID, channelId)
|
putExtra(BackgroundLocationService.EXTRA_CHANNEL_ID, channelId)
|
||||||
putExtra(BackgroundLocationService.EXTRA_CHANNEL_NAME, channelName)
|
putExtra(BackgroundLocationService.EXTRA_CHANNEL_NAME, channelName)
|
||||||
@ -800,4 +798,88 @@ class DumonGeolocation : Plugin() {
|
|||||||
applyKeepScreenOn(false)
|
applyKeepScreenOn(false)
|
||||||
super.handleOnDestroy()
|
super.handleOnDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildForegroundPermissionResult(): JSObject {
|
||||||
|
val locationStatus = if (PermissionUtils.hasLocationPermissions(context)) "granted" else "denied"
|
||||||
|
val wifiStatus = if (PermissionUtils.hasWifiScanPermissions(context)) "granted" else "denied"
|
||||||
|
return JSObject().apply {
|
||||||
|
put("location", locationStatus)
|
||||||
|
put("wifi", wifiStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMissingForegroundPermissionAlias(): String? {
|
||||||
|
return when {
|
||||||
|
!PermissionUtils.hasLocationPermissions(context) -> "location"
|
||||||
|
!PermissionUtils.hasWifiScanPermissions(context) -> "wifi"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestNextForegroundPermission(call: PluginCall): Boolean {
|
||||||
|
val missing = getMissingForegroundPermissionAlias()
|
||||||
|
if (missing == null) {
|
||||||
|
pendingPermissionAlias = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingPermissionAlias == missing) {
|
||||||
|
pendingPermissionAlias = null
|
||||||
|
call.resolve(buildForegroundPermissionResult())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingPermissionAlias = missing
|
||||||
|
call.save()
|
||||||
|
requestPermissionForAlias(missing, call, "onPermissionResult")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMissingBackgroundPermissionAlias(): String? {
|
||||||
|
if (!PermissionUtils.hasLocationPermissions(context)) {
|
||||||
|
return "location"
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
val bgGranted = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (!bgGranted) return "backgroundLocation"
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
val notifGranted = ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (!notifGranted) return "notifications"
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureBackgroundPermissions(call: PluginCall): Boolean {
|
||||||
|
val missing = getMissingBackgroundPermissionAlias()
|
||||||
|
if (missing == null) {
|
||||||
|
pendingBackgroundPermissionAlias = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingBackgroundPermissionAlias == missing) {
|
||||||
|
pendingBackgroundPermissionAlias = null
|
||||||
|
val msg = when (missing) {
|
||||||
|
"location" -> "Location permission not granted"
|
||||||
|
"backgroundLocation" -> "Background location permission not granted"
|
||||||
|
"notifications" -> "Notification permission not granted"
|
||||||
|
else -> "Required permission not granted"
|
||||||
|
}
|
||||||
|
call.reject(msg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingBackgroundPermissionAlias = missing
|
||||||
|
call.save()
|
||||||
|
requestPermissionForAlias(missing, call, "onBackgroundPermissionResult")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@PermissionCallback
|
||||||
|
private fun onBackgroundPermissionResult(call: PluginCall) {
|
||||||
|
if (!ensureBackgroundPermissions(call)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startBackgroundTracking(call)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ class GpsStatusManager(
|
|||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
private var pollingIntervalMs: Long = 1000L // Default NORMAL mode
|
private var pollingIntervalMs: Long = 1000L // Default NORMAL mode
|
||||||
private var isPolling = false
|
private var minDistanceMeters: Float = 0f
|
||||||
private var currentMode = GpsTrackingMode.NORMAL
|
private var currentMode = GpsTrackingMode.NORMAL
|
||||||
private var continuousListener: LocationListener? = null
|
private var continuousListener: LocationListener? = null
|
||||||
|
|
||||||
@ -153,83 +153,17 @@ class GpsStatusManager(
|
|||||||
mainHandler.postDelayed(timeoutRunnable, timeoutMs)
|
mainHandler.postDelayed(timeoutRunnable, timeoutMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pollOnceAndEmit() {
|
|
||||||
val oneShotListener = object : LocationListener {
|
|
||||||
override fun onLocationChanged(location: Location) {
|
|
||||||
val providerTag = when (location.provider) {
|
|
||||||
LocationManager.GPS_PROVIDER -> "[GPS]"
|
|
||||||
LocationManager.NETWORK_PROVIDER -> "[NET]"
|
|
||||||
else -> "[OTHER]"
|
|
||||||
}
|
|
||||||
|
|
||||||
val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis())
|
|
||||||
val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
location.isMock
|
|
||||||
} else {
|
|
||||||
location.isFromMockProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp | Mock=$isMocked"
|
|
||||||
LogUtils.d("GPS_LOCATION", info)
|
|
||||||
|
|
||||||
onLocationUpdate(location, isMocked)
|
|
||||||
locationManager.removeUpdates(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
|
|
||||||
override fun onProviderEnabled(provider: String) {}
|
|
||||||
override fun onProviderDisabled(provider: String) {}
|
|
||||||
}
|
|
||||||
val hasFine = ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
||||||
val hasCoarse = ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
||||||
|
|
||||||
var requested = false
|
|
||||||
if (hasFine) {
|
|
||||||
try {
|
|
||||||
locationManager.requestLocationUpdates(
|
|
||||||
LocationManager.GPS_PROVIDER,
|
|
||||||
0L,
|
|
||||||
0f,
|
|
||||||
oneShotListener
|
|
||||||
)
|
|
||||||
requested = true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
LogUtils.e("GPS_STATUS", "Failed GPS one-shot request: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasCoarse) {
|
|
||||||
try {
|
|
||||||
locationManager.requestLocationUpdates(
|
|
||||||
LocationManager.NETWORK_PROVIDER,
|
|
||||||
0L,
|
|
||||||
0f,
|
|
||||||
oneShotListener
|
|
||||||
)
|
|
||||||
requested = true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
LogUtils.e("GPS_STATUS", "Failed NETWORK one-shot request: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!requested) {
|
|
||||||
LogUtils.e("GPS_STATUS", "pollOnceAndEmit: no provider requested (missing permissions)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val pollingRunnable = object : Runnable {
|
|
||||||
override fun run() {
|
|
||||||
if (!isPolling) return
|
|
||||||
pollOnceAndEmit()
|
|
||||||
handler.postDelayed(this, pollingIntervalMs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setPollingInterval(intervalMs: Long) {
|
fun setPollingInterval(intervalMs: Long) {
|
||||||
this.pollingIntervalMs = intervalMs
|
this.pollingIntervalMs = intervalMs
|
||||||
if (isPolling) {
|
if (currentMode == GpsTrackingMode.NORMAL) {
|
||||||
handler.removeCallbacks(pollingRunnable)
|
startContinuousUpdates(pollingIntervalMs, minDistanceMeters)
|
||||||
handler.postDelayed(pollingRunnable, pollingIntervalMs)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMinDistanceMeters(distanceMeters: Double) {
|
||||||
|
minDistanceMeters = distanceMeters.toFloat().coerceAtLeast(0f)
|
||||||
|
if (currentMode == GpsTrackingMode.NORMAL) {
|
||||||
|
startContinuousUpdates(pollingIntervalMs, minDistanceMeters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,11 +172,14 @@ class GpsStatusManager(
|
|||||||
stop() // Reset dulu
|
stop() // Reset dulu
|
||||||
currentMode = mode
|
currentMode = mode
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
val hasFine = ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||||
|
val hasCoarse = ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (!hasFine && !hasCoarse) {
|
||||||
LogUtils.e("GPS_STATUS", "Missing location permissions")
|
LogUtils.e("GPS_STATUS", "Missing location permissions")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
locationManager.registerGnssStatusCallback(
|
locationManager.registerGnssStatusCallback(
|
||||||
ContextCompat.getMainExecutor(context),
|
ContextCompat.getMainExecutor(context),
|
||||||
@ -251,12 +188,14 @@ class GpsStatusManager(
|
|||||||
} else {
|
} else {
|
||||||
locationManager.registerGnssStatusCallback(gnssStatusCallback, handler)
|
locationManager.registerGnssStatusCallback(gnssStatusCallback, handler)
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.e("GPS_STATUS", "Failed to register GNSS status callback: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
if (mode == GpsTrackingMode.DRIVING) {
|
if (mode == GpsTrackingMode.DRIVING) {
|
||||||
startContinuousUpdates()
|
startContinuousUpdates(0L, 0f)
|
||||||
} else {
|
} else {
|
||||||
isPolling = true
|
startContinuousUpdates(pollingIntervalMs, minDistanceMeters)
|
||||||
handler.post(pollingRunnable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback lokasi terakhir
|
// Fallback lokasi terakhir
|
||||||
@ -274,12 +213,25 @@ class GpsStatusManager(
|
|||||||
LogUtils.d("GPS_STATUS", "GPS started with mode: $mode")
|
LogUtils.d("GPS_STATUS", "GPS started with mode: $mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startContinuousUpdates() {
|
private fun startContinuousUpdates(minTimeMs: Long, minDistanceMeters: Float) {
|
||||||
if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
val hasFine = ActivityCompat.checkSelfPermission(
|
||||||
LogUtils.e("GPS_STATUS", "Missing ACCESS_FINE_LOCATION permission")
|
context,
|
||||||
|
android.Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
val hasCoarse = ActivityCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
|
if (!hasFine && !hasCoarse) {
|
||||||
|
LogUtils.e("GPS_STATUS", "Missing location permissions")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continuousListener?.let {
|
||||||
|
try { locationManager.removeUpdates(it) } catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
|
||||||
continuousListener = object : LocationListener {
|
continuousListener = object : LocationListener {
|
||||||
override fun onLocationChanged(location: Location) {
|
override fun onLocationChanged(location: Location) {
|
||||||
val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) location.isMock else location.isFromMockProvider
|
val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) location.isMock else location.isFromMockProvider
|
||||||
@ -291,31 +243,77 @@ class GpsStatusManager(
|
|||||||
override fun onProviderDisabled(provider: String) {}
|
override fun onProviderDisabled(provider: String) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val gpsEnabled = try {
|
||||||
|
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
val netEnabled = try {
|
||||||
|
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gpsEnabled && !netEnabled) {
|
||||||
|
LogUtils.w("GPS_STATUS", "No provider enabled; skip location requests")
|
||||||
|
}
|
||||||
|
|
||||||
|
var requested = false
|
||||||
|
if (hasFine && gpsEnabled) {
|
||||||
|
try {
|
||||||
locationManager.requestLocationUpdates(
|
locationManager.requestLocationUpdates(
|
||||||
LocationManager.GPS_PROVIDER,
|
LocationManager.GPS_PROVIDER,
|
||||||
0L,
|
minTimeMs,
|
||||||
0f,
|
minDistanceMeters,
|
||||||
continuousListener!!
|
continuousListener!!
|
||||||
)
|
)
|
||||||
|
requested = true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.e("GPS_STATUS", "Failed GPS request: ${e.message}")
|
||||||
|
}
|
||||||
|
} else if (hasFine) {
|
||||||
|
LogUtils.w("GPS_STATUS", "GPS provider disabled; skip GPS request")
|
||||||
|
}
|
||||||
|
|
||||||
LogUtils.d("GPS_STATUS", "Started continuous location updates")
|
if (hasCoarse && netEnabled) {
|
||||||
|
try {
|
||||||
|
locationManager.requestLocationUpdates(
|
||||||
|
LocationManager.NETWORK_PROVIDER,
|
||||||
|
minTimeMs,
|
||||||
|
minDistanceMeters,
|
||||||
|
continuousListener!!
|
||||||
|
)
|
||||||
|
requested = true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogUtils.e("GPS_STATUS", "Failed NETWORK request: ${e.message}")
|
||||||
|
}
|
||||||
|
} else if (hasCoarse) {
|
||||||
|
LogUtils.w("GPS_STATUS", "Network provider disabled; skip NETWORK request")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requested) {
|
||||||
|
LogUtils.d("GPS_STATUS", "Started continuous updates (minTimeMs=$minTimeMs, minDistanceMeters=$minDistanceMeters)")
|
||||||
|
} else {
|
||||||
|
LogUtils.e("GPS_STATUS", "No provider requested (permissions/providers)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startContinuousMode() {
|
fun startContinuousMode() {
|
||||||
stop()
|
stop()
|
||||||
startContinuousUpdates()
|
currentMode = GpsTrackingMode.DRIVING
|
||||||
|
startContinuousUpdates(0L, 0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startPollingMode() {
|
fun startPollingMode() {
|
||||||
stop()
|
stop()
|
||||||
isPolling = true
|
currentMode = GpsTrackingMode.NORMAL
|
||||||
handler.post(pollingRunnable)
|
startContinuousUpdates(pollingIntervalMs, minDistanceMeters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
isPolling = false
|
try {
|
||||||
handler.removeCallbacks(pollingRunnable)
|
|
||||||
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
|
locationManager.unregisterGnssStatusCallback(gnssStatusCallback)
|
||||||
|
} catch (_: Exception) {}
|
||||||
continuousListener?.let {
|
continuousListener?.let {
|
||||||
locationManager.removeUpdates(it)
|
locationManager.removeUpdates(it)
|
||||||
continuousListener = null
|
continuousListener = null
|
||||||
|
|||||||
@ -29,6 +29,7 @@ class ImuSensorManager(
|
|||||||
|
|
||||||
private var latestDirectionRad = 0f
|
private var latestDirectionRad = 0f
|
||||||
private var latestSpeed = 0f
|
private var latestSpeed = 0f
|
||||||
|
private var lastLogTimestampMs: Long = 0L
|
||||||
|
|
||||||
private var currentDelay: Int = SensorManager.SENSOR_DELAY_GAME
|
private var currentDelay: Int = SensorManager.SENSOR_DELAY_GAME
|
||||||
|
|
||||||
@ -111,9 +112,16 @@ class ImuSensorManager(
|
|||||||
|
|
||||||
emitCombinedImuData(speed, acceleration, latestDirectionRad)
|
emitCombinedImuData(speed, acceleration, latestDirectionRad)
|
||||||
|
|
||||||
LogUtils.d("IMU_SENSOR", "Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format(
|
val nowMs = System.currentTimeMillis()
|
||||||
|
if (nowMs - lastLogTimestampMs >= 1000L) {
|
||||||
|
lastLogTimestampMs = nowMs
|
||||||
|
LogUtils.d(
|
||||||
|
"IMU_SENSOR",
|
||||||
|
"Accel x: %.3f y: %.3f z: %.3f | Speed: %.3f | AccelMag: %.3f | Dir: %.2f rad".format(
|
||||||
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, latestDirectionRad
|
lastAccel[0], lastAccel[1], lastAccel[2], speed, acceleration, latestDirectionRad
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGyroscope(event: SensorEvent) {
|
private fun handleGyroscope(event: SensorEvent) {
|
||||||
|
|||||||
@ -57,11 +57,6 @@ object BgPrefs {
|
|||||||
.putLong(KEY_TS, timestamp)
|
.putLong(KEY_TS, timestamp)
|
||||||
.putString(KEY_SRC, source)
|
.putString(KEY_SRC, source)
|
||||||
.putBoolean(KEY_MOCK, isMocked)
|
.putBoolean(KEY_MOCK, isMocked)
|
||||||
.apply()
|
|
||||||
|
|
||||||
// Optional IMU fields
|
|
||||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
|
||||||
.edit()
|
|
||||||
.putFloat(KEY_SPEED, speed ?: 0f)
|
.putFloat(KEY_SPEED, speed ?: 0f)
|
||||||
.putFloat(KEY_ACCEL, acceleration ?: 0f)
|
.putFloat(KEY_ACCEL, acceleration ?: 0f)
|
||||||
.putFloat(KEY_DIR, directionRad ?: 0f)
|
.putFloat(KEY_DIR, directionRad ?: 0f)
|
||||||
|
|||||||
@ -7,18 +7,28 @@ import android.os.Build
|
|||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
|
||||||
object PermissionUtils {
|
object PermissionUtils {
|
||||||
fun hasLocationAndWifiPermissions(context: Context): Boolean {
|
fun hasLocationPermissions(context: Context): Boolean {
|
||||||
val fineLocation = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
|
val fineLocation = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
val coarseLocation = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
|
val coarseLocation = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
val nearbyWifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
return fineLocation == PackageManager.PERMISSION_GRANTED || coarseLocation == PackageManager.PERMISSION_GRANTED
|
||||||
ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES)
|
|
||||||
} else {
|
|
||||||
PackageManager.PERMISSION_GRANTED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fineLocation == PackageManager.PERMISSION_GRANTED &&
|
fun hasWifiScanPermissions(context: Context): Boolean {
|
||||||
coarseLocation == PackageManager.PERMISSION_GRANTED &&
|
val wifiState = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_WIFI_STATE)
|
||||||
nearbyWifi == PackageManager.PERMISSION_GRANTED
|
val changeWifi = ActivityCompat.checkSelfPermission(context, Manifest.permission.CHANGE_WIFI_STATE)
|
||||||
|
if (wifiState != PackageManager.PERMISSION_GRANTED || changeWifi != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ActivityCompat.checkSelfPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES) == PackageManager.PERMISSION_GRANTED
|
||||||
|
} else {
|
||||||
|
hasLocationPermissions(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasLocationAndWifiPermissions(context: Context): Boolean {
|
||||||
|
return hasLocationPermissions(context) && hasWifiScanPermissions(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPermissionStatus(granted: Int): String {
|
fun getPermissionStatus(granted: Int): String {
|
||||||
|
|||||||
@ -14,8 +14,8 @@ import android.net.wifi.rtt.WifiRttManager
|
|||||||
import android.os.*
|
import android.os.*
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.dumon.plugin.geolocation.utils.LogUtils
|
import com.dumon.plugin.geolocation.utils.LogUtils
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.dumon.plugin.geolocation.utils.PermissionUtils
|
||||||
|
|
||||||
class WifiPositioningManager(
|
class WifiPositioningManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@ -62,18 +62,20 @@ class WifiPositioningManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startWifiScan() {
|
fun startWifiScan() {
|
||||||
if (ActivityCompat.checkSelfPermission(
|
if (!PermissionUtils.hasWifiScanPermissions(context)) {
|
||||||
context,
|
LogUtils.e("WIFI_POSITION", "Missing Wi-Fi scan permission")
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION
|
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
return
|
||||||
) {
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
val success = wifiManager.startScan()
|
val success = wifiManager.startScan()
|
||||||
if (!success) {
|
if (!success) {
|
||||||
LogUtils.e("WIFI_POSITION", "Wi-Fi scan failed")
|
LogUtils.e("WIFI_POSITION", "Wi-Fi scan failed")
|
||||||
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
|
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e: SecurityException) {
|
||||||
LogUtils.e("WIFI_POSITION", "Missing ACCESS_FINE_LOCATION permission")
|
LogUtils.e("WIFI_POSITION", "SecurityException on Wi-Fi scan: ${e.message}")
|
||||||
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
|
onWifiPositioningUpdate(WifiScanResult(0, emptyList()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 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"]}
|
{"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 false)\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"]}
|
||||||
6
dist/esm/index.d.ts
vendored
6
dist/esm/index.d.ts
vendored
@ -1,4 +1,8 @@
|
|||||||
import type { DumonGeolocationPlugin } from './definitions';
|
import type { DumonGeolocationPlugin } from './definitions';
|
||||||
declare const DumonGeolocation: DumonGeolocationPlugin;
|
declare const DumonGeolocationPlugin: DumonGeolocationPlugin;
|
||||||
|
declare const DumonGeolocation: DumonGeolocationPlugin & {
|
||||||
|
getGnssStatus(): Promise<import("./definitions").SatelliteStatus | null>;
|
||||||
|
getBackgroundLatestPosition(): Promise<import("./definitions").PositioningData | null>;
|
||||||
|
};
|
||||||
export * from './definitions';
|
export * from './definitions';
|
||||||
export { DumonGeolocation };
|
export { DumonGeolocation };
|
||||||
|
|||||||
17
dist/esm/index.js
vendored
17
dist/esm/index.js
vendored
@ -1,7 +1,22 @@
|
|||||||
import { registerPlugin } from '@capacitor/core';
|
import { registerPlugin } from '@capacitor/core';
|
||||||
const DumonGeolocation = registerPlugin('DumonGeolocation', {
|
const DumonGeolocationPlugin = registerPlugin('DumonGeolocation', {
|
||||||
web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),
|
web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),
|
||||||
});
|
});
|
||||||
|
const isEmptyObject = (value) => {
|
||||||
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
||||||
|
return false;
|
||||||
|
return Object.keys(value).length === 0;
|
||||||
|
};
|
||||||
|
const DumonGeolocation = Object.assign(DumonGeolocationPlugin, {
|
||||||
|
async getGnssStatus() {
|
||||||
|
const result = await DumonGeolocationPlugin.getGnssStatus();
|
||||||
|
return isEmptyObject(result) ? null : result;
|
||||||
|
},
|
||||||
|
async getBackgroundLatestPosition() {
|
||||||
|
const result = await DumonGeolocationPlugin.getBackgroundLatestPosition();
|
||||||
|
return isEmptyObject(result) ? null : result;
|
||||||
|
},
|
||||||
|
});
|
||||||
export * from './definitions';
|
export * from './definitions';
|
||||||
export { DumonGeolocation };
|
export { DumonGeolocation };
|
||||||
//# sourceMappingURL=index.js.map
|
//# sourceMappingURL=index.js.map
|
||||||
2
dist/esm/index.js.map
vendored
2
dist/esm/index.js.map
vendored
@ -1 +1 @@
|
|||||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,gBAAgB,GAAG,cAAc,CAAyB,kBAAkB,EAAE;IAClF,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;CACpE,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { DumonGeolocationPlugin } from './definitions';\n\nconst DumonGeolocation = registerPlugin<DumonGeolocationPlugin>('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\n\nexport * from './definitions';\nexport { DumonGeolocation };"]}
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,sBAAsB,GAAG,cAAc,CAAyB,kBAAkB,EAAE;IACxF,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;CACpE,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,CAAC,KAAc,EAAW,EAAE;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,OAAO,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACpE,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAE;IAC7D,KAAK,CAAC,aAAa;QACjB,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,aAAa,EAAE,CAAC;QAC5D,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/C,CAAC;IACD,KAAK,CAAC,2BAA2B;QAC/B,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,2BAA2B,EAAE,CAAC;QAC1E,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/C,CAAC;CACF,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { DumonGeolocationPlugin } from './definitions';\n\nconst DumonGeolocationPlugin = registerPlugin<DumonGeolocationPlugin>('DumonGeolocation', {\n web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),\n});\n\nconst isEmptyObject = (value: unknown): boolean => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n return Object.keys(value as Record<string, unknown>).length === 0;\n};\n\nconst DumonGeolocation = Object.assign(DumonGeolocationPlugin, {\n async getGnssStatus() {\n const result = await DumonGeolocationPlugin.getGnssStatus();\n return isEmptyObject(result) ? null : result;\n },\n async getBackgroundLatestPosition() {\n const result = await DumonGeolocationPlugin.getBackgroundLatestPosition();\n return isEmptyObject(result) ? null : result;\n },\n});\n\nexport * from './definitions';\nexport { DumonGeolocation };\n"]}
|
||||||
105
dist/plugin.cjs.js
vendored
105
dist/plugin.cjs.js
vendored
@ -1,105 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var core = require('@capacitor/core');
|
|
||||||
|
|
||||||
const DumonGeolocation = core.registerPlugin('DumonGeolocation', {
|
|
||||||
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.DumonGeolocationWeb()),
|
|
||||||
});
|
|
||||||
|
|
||||||
class DumonGeolocationWeb extends core.WebPlugin {
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
this._mode = 'normal';
|
|
||||||
}
|
|
||||||
async startPositioning() {
|
|
||||||
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
|
|
||||||
}
|
|
||||||
async stopPositioning() {
|
|
||||||
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
|
|
||||||
}
|
|
||||||
async getLatestPosition() {
|
|
||||||
console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');
|
|
||||||
return {
|
|
||||||
source: 'GNSS',
|
|
||||||
timestamp: Date.now(),
|
|
||||||
latitude: 0,
|
|
||||||
longitude: 0,
|
|
||||||
accuracy: 999,
|
|
||||||
speed: 0,
|
|
||||||
acceleration: 0,
|
|
||||||
directionRad: 0,
|
|
||||||
isMocked: false,
|
|
||||||
mode: this._mode,
|
|
||||||
speedSource: 'NONE',
|
|
||||||
speedImu: 0,
|
|
||||||
speedGnss: 0,
|
|
||||||
speedDerived: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async checkAndRequestPermissions() {
|
|
||||||
console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');
|
|
||||||
return {
|
|
||||||
location: 'granted',
|
|
||||||
wifi: 'granted',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async configureEdgeToEdge(options) {
|
|
||||||
console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
async setOptions(_options) {
|
|
||||||
// No-op on web
|
|
||||||
}
|
|
||||||
async setGpsMode(options) {
|
|
||||||
this._mode = (options === null || options === void 0 ? void 0 : options.mode) === 'driving' ? 'driving' : 'normal';
|
|
||||||
}
|
|
||||||
async getGnssStatus() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
async getLocationServicesStatus() {
|
|
||||||
// Web stub; assume enabled
|
|
||||||
return { gpsEnabled: true, networkEnabled: true };
|
|
||||||
}
|
|
||||||
// Background tracking stubs (no-op on web)
|
|
||||||
async startBackgroundTracking(_options) {
|
|
||||||
console.info('[dumon-geolocation] startBackgroundTracking is not supported on web.');
|
|
||||||
}
|
|
||||||
async stopBackgroundTracking() {
|
|
||||||
console.info('[dumon-geolocation] stopBackgroundTracking is not supported on web.');
|
|
||||||
}
|
|
||||||
async isBackgroundTrackingActive() {
|
|
||||||
return { active: false };
|
|
||||||
}
|
|
||||||
async getBackgroundLatestPosition() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
async openBackgroundPermissionSettings() {
|
|
||||||
console.info('[dumon-geolocation] openBackgroundPermissionSettings is not supported on web.');
|
|
||||||
}
|
|
||||||
async openNotificationPermissionSettings() {
|
|
||||||
console.info('[dumon-geolocation] openNotificationPermissionSettings is not supported on web.');
|
|
||||||
}
|
|
||||||
async setAuthTokens(_tokens) {
|
|
||||||
console.info('[dumon-geolocation] setAuthTokens is a no-op on web.');
|
|
||||||
}
|
|
||||||
async clearAuthTokens() {
|
|
||||||
console.info('[dumon-geolocation] clearAuthTokens is a no-op on web.');
|
|
||||||
}
|
|
||||||
async getAuthState() {
|
|
||||||
return { present: false };
|
|
||||||
}
|
|
||||||
async setBackgroundPostUrl(_options) {
|
|
||||||
console.info('[dumon-geolocation] setBackgroundPostUrl is not supported on web.');
|
|
||||||
}
|
|
||||||
async getBackgroundPostUrl() {
|
|
||||||
return { url: null };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var web = /*#__PURE__*/Object.freeze({
|
|
||||||
__proto__: null,
|
|
||||||
DumonGeolocationWeb: DumonGeolocationWeb
|
|
||||||
});
|
|
||||||
|
|
||||||
exports.DumonGeolocation = DumonGeolocation;
|
|
||||||
//# sourceMappingURL=plugin.cjs.js.map
|
|
||||||
1
dist/plugin.cjs.js.map
vendored
1
dist/plugin.cjs.js.map
vendored
File diff suppressed because one or more lines are too long
108
dist/plugin.js
vendored
108
dist/plugin.js
vendored
@ -1,108 +0,0 @@
|
|||||||
var capacitorDumonGeolocation = (function (exports, core) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const DumonGeolocation = core.registerPlugin('DumonGeolocation', {
|
|
||||||
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.DumonGeolocationWeb()),
|
|
||||||
});
|
|
||||||
|
|
||||||
class DumonGeolocationWeb extends core.WebPlugin {
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
this._mode = 'normal';
|
|
||||||
}
|
|
||||||
async startPositioning() {
|
|
||||||
console.log('DumonGeolocationWeb: startPositioning() called (no-op)');
|
|
||||||
}
|
|
||||||
async stopPositioning() {
|
|
||||||
console.log('DumonGeolocationWeb: stopPositioning() called (no-op)');
|
|
||||||
}
|
|
||||||
async getLatestPosition() {
|
|
||||||
console.log('DumonGeolocationWeb: getLatestPosition() called (returning dummy data)');
|
|
||||||
return {
|
|
||||||
source: 'GNSS',
|
|
||||||
timestamp: Date.now(),
|
|
||||||
latitude: 0,
|
|
||||||
longitude: 0,
|
|
||||||
accuracy: 999,
|
|
||||||
speed: 0,
|
|
||||||
acceleration: 0,
|
|
||||||
directionRad: 0,
|
|
||||||
isMocked: false,
|
|
||||||
mode: this._mode,
|
|
||||||
speedSource: 'NONE',
|
|
||||||
speedImu: 0,
|
|
||||||
speedGnss: 0,
|
|
||||||
speedDerived: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async checkAndRequestPermissions() {
|
|
||||||
console.info('[dumon-geolocation] checkAndRequestPermissions mocked for web.');
|
|
||||||
return {
|
|
||||||
location: 'granted',
|
|
||||||
wifi: 'granted',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async configureEdgeToEdge(options) {
|
|
||||||
console.info('[dumon-geolocation] configureEdgeToEdge called on web with:', options);
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
async setOptions(_options) {
|
|
||||||
// No-op on web
|
|
||||||
}
|
|
||||||
async setGpsMode(options) {
|
|
||||||
this._mode = (options === null || options === void 0 ? void 0 : options.mode) === 'driving' ? 'driving' : 'normal';
|
|
||||||
}
|
|
||||||
async getGnssStatus() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
async getLocationServicesStatus() {
|
|
||||||
// Web stub; assume enabled
|
|
||||||
return { gpsEnabled: true, networkEnabled: true };
|
|
||||||
}
|
|
||||||
// Background tracking stubs (no-op on web)
|
|
||||||
async startBackgroundTracking(_options) {
|
|
||||||
console.info('[dumon-geolocation] startBackgroundTracking is not supported on web.');
|
|
||||||
}
|
|
||||||
async stopBackgroundTracking() {
|
|
||||||
console.info('[dumon-geolocation] stopBackgroundTracking is not supported on web.');
|
|
||||||
}
|
|
||||||
async isBackgroundTrackingActive() {
|
|
||||||
return { active: false };
|
|
||||||
}
|
|
||||||
async getBackgroundLatestPosition() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
async openBackgroundPermissionSettings() {
|
|
||||||
console.info('[dumon-geolocation] openBackgroundPermissionSettings is not supported on web.');
|
|
||||||
}
|
|
||||||
async openNotificationPermissionSettings() {
|
|
||||||
console.info('[dumon-geolocation] openNotificationPermissionSettings is not supported on web.');
|
|
||||||
}
|
|
||||||
async setAuthTokens(_tokens) {
|
|
||||||
console.info('[dumon-geolocation] setAuthTokens is a no-op on web.');
|
|
||||||
}
|
|
||||||
async clearAuthTokens() {
|
|
||||||
console.info('[dumon-geolocation] clearAuthTokens is a no-op on web.');
|
|
||||||
}
|
|
||||||
async getAuthState() {
|
|
||||||
return { present: false };
|
|
||||||
}
|
|
||||||
async setBackgroundPostUrl(_options) {
|
|
||||||
console.info('[dumon-geolocation] setBackgroundPostUrl is not supported on web.');
|
|
||||||
}
|
|
||||||
async getBackgroundPostUrl() {
|
|
||||||
return { url: null };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var web = /*#__PURE__*/Object.freeze({
|
|
||||||
__proto__: null,
|
|
||||||
DumonGeolocationWeb: DumonGeolocationWeb
|
|
||||||
});
|
|
||||||
|
|
||||||
exports.DumonGeolocation = DumonGeolocation;
|
|
||||||
|
|
||||||
return exports;
|
|
||||||
|
|
||||||
})({}, capacitorExports);
|
|
||||||
//# sourceMappingURL=plugin.js.map
|
|
||||||
1
dist/plugin.js.map
vendored
1
dist/plugin.js.map
vendored
File diff suppressed because one or more lines are too long
18
src/index.ts
18
src/index.ts
@ -2,9 +2,25 @@ import { registerPlugin } from '@capacitor/core';
|
|||||||
|
|
||||||
import type { DumonGeolocationPlugin } from './definitions';
|
import type { DumonGeolocationPlugin } from './definitions';
|
||||||
|
|
||||||
const DumonGeolocation = registerPlugin<DumonGeolocationPlugin>('DumonGeolocation', {
|
const DumonGeolocationPlugin = registerPlugin<DumonGeolocationPlugin>('DumonGeolocation', {
|
||||||
web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),
|
web: () => import('./web').then((m) => new m.DumonGeolocationWeb()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isEmptyObject = (value: unknown): boolean => {
|
||||||
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
||||||
|
return Object.keys(value as Record<string, unknown>).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DumonGeolocation = Object.assign(DumonGeolocationPlugin, {
|
||||||
|
async getGnssStatus() {
|
||||||
|
const result = await DumonGeolocationPlugin.getGnssStatus();
|
||||||
|
return isEmptyObject(result) ? null : result;
|
||||||
|
},
|
||||||
|
async getBackgroundLatestPosition() {
|
||||||
|
const result = await DumonGeolocationPlugin.getBackgroundLatestPosition();
|
||||||
|
return isEmptyObject(result) ? null : result;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export * from './definitions';
|
export * from './definitions';
|
||||||
export { DumonGeolocation };
|
export { DumonGeolocation };
|
||||||
Loading…
x
Reference in New Issue
Block a user