diff --git a/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt b/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt index 9f624d0..a5b25ce 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/DumonGeolocation.kt @@ -15,6 +15,7 @@ import com.getcapacitor.* import com.getcapacitor.annotation.CapacitorPlugin import com.getcapacitor.annotation.Permission 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 @@ -73,6 +74,8 @@ class DumonGeolocation : Plugin() { private var motionState: String = "idle" // 'idle', 'driving', 'mocked' + private var currentTrackingMode = GpsTrackingMode.NORMAL + override fun load() { gpsManager = GpsStatusManager( context, @@ -221,6 +224,19 @@ class DumonGeolocation : Plugin() { call.resolve() } + @PluginMethod + fun setGpsMode(call: PluginCall) { + val mode = call.getString("mode") ?: "normal" + if (mode == "driving") { + gpsManager?.startContinuousMode() + Log.d("DUMON_GEOLOCATION", "Switched to driving mode (continuous GPS)") + } else { + gpsManager?.startPollingMode() + Log.d("DUMON_GEOLOCATION", "Switched to normal mode (polling GPS)") + } + call.resolve() + } + @PermissionCallback private fun onPermissionResult(call: PluginCall) { val locationStatus = PermissionUtils.getPermissionStatus( @@ -275,17 +291,13 @@ class DumonGeolocation : Plugin() { } private fun adjustIntervalAndSensorRate(speed: Float) { - val targetInterval = when { - speed > 5f -> 1000L - speed > 1.5f -> 5000L - speed > 0.3f -> 15000L - else -> 30000L - } + val targetMode = if (speed > 3.0f) GpsTrackingMode.DRIVING else GpsTrackingMode.NORMAL - if (emitIntervalMs != targetInterval) { - emitIntervalMs = targetInterval - gpsManager?.setPollingInterval(targetInterval) - Log.d("DUMON_GEOLOCATION", "Auto-set emitIntervalMs = $emitIntervalMs ms") + if (currentTrackingMode != targetMode) { + currentTrackingMode = targetMode + gpsManager?.stop() + gpsManager?.start(currentTrackingMode) + Log.d("DUMON_GEOLOCATION", "Switched GPS mode to $currentTrackingMode") } imuManager?.setSensorDelayBySpeed(speed) diff --git a/android/src/main/java/com/dumon/plugin/geolocation/gps/GpsStatusManager.kt b/android/src/main/java/com/dumon/plugin/geolocation/gps/GpsStatusManager.kt index 814924d..42a2427 100644 --- a/android/src/main/java/com/dumon/plugin/geolocation/gps/GpsStatusManager.kt +++ b/android/src/main/java/com/dumon/plugin/geolocation/gps/GpsStatusManager.kt @@ -15,6 +15,11 @@ import android.text.format.DateFormat import android.util.Log import androidx.core.app.ActivityCompat +enum class GpsTrackingMode { + NORMAL, + DRIVING +} + class GpsStatusManager( private val context: Context, private val onSatelliteStatusUpdate: (SatelliteStatus) -> Unit = {}, @@ -23,8 +28,11 @@ class GpsStatusManager( private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager private val handler = Handler(Looper.getMainLooper()) - private var pollingIntervalMs: Long = 1000L // default 1 second + + private var pollingIntervalMs: Long = 1000L // Default NORMAL mode private var isPolling = false + private var currentMode = GpsTrackingMode.NORMAL + private var continuousListener: LocationListener? = null private val gnssStatusCallback = object : GnssStatus.Callback() { override fun onSatelliteStatusChanged(status: GnssStatus) { @@ -59,16 +67,15 @@ class GpsStatusManager( val timestamp = DateFormat.format("HH:mm:ss", System.currentTimeMillis()) val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - location.isMock // API 31+ + location.isMock } else { - location.isFromMockProvider // Fallback + location.isFromMockProvider } val info = "$providerTag Lat: ${location.latitude}, Lon: ${location.longitude}, Acc: ${location.accuracy} m @ $timestamp | Mock=$isMocked" Log.d("GPS_LOCATION", info) onLocationUpdate(location, isMocked) - locationManager.removeUpdates(this) } @@ -102,47 +109,86 @@ class GpsStatusManager( } @SuppressLint("MissingPermission") - fun start() { - try { - if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - Log.e("GPS_STATUS", "Missing location permissions") - return - } + fun start(mode: GpsTrackingMode = GpsTrackingMode.NORMAL) { + stop() // Reset dulu + currentMode = mode - if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { - Log.w("GPS_STATUS", "GPS Provider not enabled") - } - if (!locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { - Log.w("GPS_STATUS", "Network Provider not enabled") - } + if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + Log.e("GPS_STATUS", "Missing location permissions") + return + } - locationManager.registerGnssStatusCallback(gnssStatusCallback, null) + locationManager.registerGnssStatusCallback(gnssStatusCallback, null) + + if (mode == GpsTrackingMode.DRIVING) { + startContinuousUpdates() + } else { isPolling = true handler.post(pollingRunnable) + } - // Fallback lokasi terakhir - val lastKnown = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) - ?: locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER) + // Fallback lokasi terakhir + val lastKnown = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) + ?: locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER) - lastKnown?.let { location -> - if (location.latitude != 0.0 && location.longitude != 0.0) { - val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) location.isMock else location.isFromMockProvider - Log.d("GPS_STATUS", "Using last known location as fallback") - onLocationUpdate(location, isMocked) - } + lastKnown?.let { location -> + if (location.latitude != 0.0 && location.longitude != 0.0) { + val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) location.isMock else location.isFromMockProvider + Log.d("GPS_STATUS", "Using last known location as fallback") + onLocationUpdate(location, isMocked) + } + } + + Log.d("GPS_STATUS", "GPS started with mode: $mode") + } + + private fun startContinuousUpdates() { + if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + Log.e("GPS_STATUS", "Missing ACCESS_FINE_LOCATION permission") + return + } + + continuousListener = object : LocationListener { + override fun onLocationChanged(location: Location) { + val isMocked = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) location.isMock else location.isFromMockProvider + onLocationUpdate(location, isMocked) } - Log.d("GPS_STATUS", "One-shot GPS polling started") - } catch (e: SecurityException) { - Log.e("GPS_STATUS", "SecurityException", e) + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {} + override fun onProviderEnabled(provider: String) {} + override fun onProviderDisabled(provider: String) {} } + + locationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + 0L, + 0f, + continuousListener!! + ) + + Log.d("GPS_STATUS", "Started continuous location updates") + } + + fun startContinuousMode() { + stop() + startContinuousUpdates() + } + + fun startPollingMode() { + stop() + isPolling = true + handler.post(pollingRunnable) } fun stop() { isPolling = false handler.removeCallbacks(pollingRunnable) locationManager.unregisterGnssStatusCallback(gnssStatusCallback) - Log.d("GPS_STATUS", "GPS polling stopped") + continuousListener?.let { + locationManager.removeUpdates(it) + continuousListener = null + } + Log.d("GPS_STATUS", "GPS stopped for mode: $currentMode") } private fun getConstellationName(type: Int): String { diff --git a/dist/docs.json b/dist/docs.json index 79c9c1c..321edee 100644 --- a/dist/docs.json +++ b/dist/docs.json @@ -65,6 +65,22 @@ "complexTypes": [], "slug": "configureedgetoedge" }, + { + "name": "setGpsMode", + "signature": "(options: { mode: 'normal' | 'driving'; }) => Promise", + "parameters": [ + { + "name": "options", + "docs": "", + "type": "{ mode: 'normal' | 'driving'; }" + } + ], + "returns": "Promise", + "tags": [], + "docs": "", + "complexTypes": [], + "slug": "setgpsmode" + }, { "name": "addListener", "signature": "(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void) => PluginListenerHandle", diff --git a/dist/esm/definitions.d.ts b/dist/esm/definitions.d.ts index a0a1685..60dad03 100644 --- a/dist/esm/definitions.d.ts +++ b/dist/esm/definitions.d.ts @@ -25,5 +25,8 @@ export interface DumonGeolocationPlugin { style: 'DARK' | 'LIGHT'; overlay?: boolean; }): Promise; + setGpsMode(options: { + mode: 'normal' | 'driving'; + }): Promise; addListener(eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void): PluginListenerHandle; } diff --git a/dist/esm/definitions.js.map b/dist/esm/definitions.js.map index 67e9011..b75245f 100644 --- a/dist/esm/definitions.js.map +++ b/dist/esm/definitions.js.map @@ -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 predicted?: boolean;\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise;\n stopPositioning(): Promise;\n getLatestPosition(): Promise;\n checkAndRequestPermissions(): Promise;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n}"]} \ No newline at end of file +{"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 predicted?: boolean;\n}\n\nexport interface PermissionStatus {\n location: 'granted' | 'denied';\n wifi: 'granted' | 'denied';\n}\n\nexport interface DumonGeolocationPlugin {\n startPositioning(): Promise;\n stopPositioning(): Promise;\n getLatestPosition(): Promise;\n checkAndRequestPermissions(): Promise;\n\n configureEdgeToEdge(options: {\n bgColor: string;\n style: 'DARK' | 'LIGHT';\n overlay?: boolean;\n }): Promise;\n\n setGpsMode(options: { mode: 'normal' | 'driving' }): Promise;\n\n addListener(\n eventName: 'onPositionUpdate',\n listenerFunc: (data: PositioningData) => void\n ): PluginListenerHandle;\n}"]} \ No newline at end of file diff --git a/src/definitions.ts b/src/definitions.ts index 71f8356..f9107e6 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -81,6 +81,8 @@ export interface DumonGeolocationPlugin { overlay?: boolean; }): Promise; + setGpsMode(options: { mode: 'normal' | 'driving' }): Promise; + addListener( eventName: 'onPositionUpdate', listenerFunc: (data: PositioningData) => void