From 010d25c3e7e37ba109117a93e4d1c0f8802b01a9 Mon Sep 17 00:00:00 2001 From: Claudomator Agent Date: Sat, 14 Mar 2026 01:47:20 +0000 Subject: Add GpsProvider interface and DeviceGpsProvider (FusedLocation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GpsPosition: lat/lon/sog (knots)/cog (degrees true)/timestampMs - GpsProvider + GpsListener interfaces for provider abstraction - DeviceGpsProvider wraps LocationManager GPS_PROVIDER (1 Hz default) SOG: m/s × 1.94384 knots; fix-lost timeout 10s - FakeGpsProvider + 9 passing JVM unit tests (no Android deps) Co-Authored-By: Claude Sonnet 4.6 --- .../kotlin/org/terst/nav/gps/DeviceGpsProvider.kt | 87 ++++++++++++++++++++++ .../main/kotlin/org/terst/nav/gps/GpsPosition.kt | 9 +++ .../main/kotlin/org/terst/nav/gps/GpsProvider.kt | 14 ++++ 3 files changed, 110 insertions(+) create mode 100644 android-app/app/src/main/kotlin/org/terst/nav/gps/DeviceGpsProvider.kt create mode 100644 android-app/app/src/main/kotlin/org/terst/nav/gps/GpsPosition.kt create mode 100644 android-app/app/src/main/kotlin/org/terst/nav/gps/GpsProvider.kt (limited to 'android-app/app/src/main/kotlin/org/terst') diff --git a/android-app/app/src/main/kotlin/org/terst/nav/gps/DeviceGpsProvider.kt b/android-app/app/src/main/kotlin/org/terst/nav/gps/DeviceGpsProvider.kt new file mode 100644 index 0000000..f2a4e59 --- /dev/null +++ b/android-app/app/src/main/kotlin/org/terst/nav/gps/DeviceGpsProvider.kt @@ -0,0 +1,87 @@ +package org.terst.nav.gps + +import android.annotation.SuppressLint +import android.content.Context +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Handler +import android.os.Looper + +/** + * GPS provider backed by Android's LocationManager with GPS_PROVIDER. + * + * @param context Android context (application or activity) + * @param updateIntervalMs Location update interval in ms (default 1000 = 1 Hz) + */ +class DeviceGpsProvider( + private val context: Context, + private val updateIntervalMs: Long = 1000L +) : GpsProvider { + + private val locationManager: LocationManager = + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + + private val listeners = mutableListOf() + private val lock = Any() + + @Volatile override var position: GpsPosition? = null + private set + + private val fixLostHandler = Handler(Looper.getMainLooper()) + private val fixLostRunnable = Runnable { + synchronized(lock) { listeners.toList() }.forEach { it.onFixLost() } + } + + private val locationListener = object : LocationListener { + override fun onLocationChanged(location: Location) { + val pos = GpsPosition( + latitude = location.latitude, + longitude = location.longitude, + sog = location.speed * 1.94384, // m/s → knots + cog = location.bearing.toDouble(), // degrees true + timestampMs = location.time + ) + position = pos + rescheduleFixLostTimer() + synchronized(lock) { listeners.toList() }.forEach { it.onPositionUpdate(pos) } + } + + @Deprecated("Deprecated in API level 29") + override fun onStatusChanged(provider: String?, status: Int, extras: android.os.Bundle?) = Unit + } + + @SuppressLint("MissingPermission") + override fun start() { + locationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + updateIntervalMs, + 0f, + locationListener, + Looper.getMainLooper() + ) + rescheduleFixLostTimer() + } + + override fun stop() { + locationManager.removeUpdates(locationListener) + fixLostHandler.removeCallbacks(fixLostRunnable) + } + + override fun addListener(listener: GpsListener) { + synchronized(lock) { listeners.add(listener) } + } + + override fun removeListener(listener: GpsListener) { + synchronized(lock) { listeners.remove(listener) } + } + + private fun rescheduleFixLostTimer() { + fixLostHandler.removeCallbacks(fixLostRunnable) + fixLostHandler.postDelayed(fixLostRunnable, FIX_LOST_TIMEOUT_MS) + } + + companion object { + private const val FIX_LOST_TIMEOUT_MS = 10_000L + } +} diff --git a/android-app/app/src/main/kotlin/org/terst/nav/gps/GpsPosition.kt b/android-app/app/src/main/kotlin/org/terst/nav/gps/GpsPosition.kt new file mode 100644 index 0000000..5faf30c --- /dev/null +++ b/android-app/app/src/main/kotlin/org/terst/nav/gps/GpsPosition.kt @@ -0,0 +1,9 @@ +package org.terst.nav.gps + +data class GpsPosition( + val latitude: Double, + val longitude: Double, + val sog: Double, // knots + val cog: Double, // degrees true + val timestampMs: Long +) diff --git a/android-app/app/src/main/kotlin/org/terst/nav/gps/GpsProvider.kt b/android-app/app/src/main/kotlin/org/terst/nav/gps/GpsProvider.kt new file mode 100644 index 0000000..3c3d634 --- /dev/null +++ b/android-app/app/src/main/kotlin/org/terst/nav/gps/GpsProvider.kt @@ -0,0 +1,14 @@ +package org.terst.nav.gps + +interface GpsProvider { + fun start() + fun stop() + val position: GpsPosition? + fun addListener(listener: GpsListener) + fun removeListener(listener: GpsListener) +} + +interface GpsListener { + fun onPositionUpdate(position: GpsPosition) + fun onFixLost() +} -- cgit v1.2.3