11package org.maplibre.compose.location
22
3+ import android.Manifest
34import android.content.Context
45import android.content.Context.SENSOR_SERVICE
56import android.hardware.Sensor
67import android.hardware.SensorEvent
78import android.hardware.SensorEventListener
89import android.hardware.SensorManager
10+ import androidx.annotation.RequiresPermission
911import androidx.compose.runtime.Composable
1012import androidx.compose.runtime.remember
1113import androidx.compose.runtime.rememberCoroutineScope
@@ -23,8 +25,8 @@ import kotlinx.coroutines.flow.stateIn
2325 * A [LocationProvider] that enhances an existing [LocationProvider] with bearing information
2426 * derived from the device's rotation vector sensor.
2527 *
26- * This class listens to the `Sensor.TYPE_ROTATION_VECTOR` to get the device's orientation.
27- * It then combines this sensor-derived bearing with the location data from the wrapped [locationProvider].
28+ * This class listens to the `Sensor.TYPE_ROTATION_VECTOR` to get the device's orientation. It then
29+ * combines this sensor-derived bearing with the location data from the wrapped [locationProvider].
2830 *
2931 * The sensor-based bearing is only used if its accuracy is better than the bearing accuracy
3032 * provided by the original location provider.
@@ -33,106 +35,109 @@ import kotlinx.coroutines.flow.stateIn
3335 * @param locationProvider The underlying [LocationProvider] to be enhanced.
3436 * @param coroutineScope The [CoroutineScope] in which the location and bearing flows are combined.
3537 * @param sharingStarted The strategy for starting and stopping the collection of the location flow.
36- * Defaults to [SharingStarted.WhileSubscribed] with a 1-second stop timeout.
38+ * Defaults to [SharingStarted.WhileSubscribed] with a 1-second stop timeout.
3739 * @throws IllegalStateException if the rotation vector sensor is not available on the device.
3840 */
3941public class AndroidSensorEnhancedLocationProvider (
40- context : Context ,
41- locationProvider : LocationProvider ,
42- coroutineScope : CoroutineScope ,
43- sharingStarted : SharingStarted = SharingStarted .WhileSubscribed (stopTimeoutMillis = 1000),
42+ context : Context ,
43+ locationProvider : LocationProvider ,
44+ coroutineScope : CoroutineScope ,
45+ sharingStarted : SharingStarted = SharingStarted .WhileSubscribed (stopTimeoutMillis = 1000),
4446) : LocationProvider {
4547 private val sensorManager = context.getSystemService(SENSOR_SERVICE ) as SensorManager
4648
47- private fun accuracyToDegrees (accuracy : Int ): Double = when (accuracy) {
48- SensorManager .SENSOR_STATUS_ACCURACY_HIGH -> 5.0
49- SensorManager .SENSOR_STATUS_ACCURACY_MEDIUM -> 15.0
50- SensorManager .SENSOR_STATUS_ACCURACY_LOW -> 45.0
51- SensorManager .SENSOR_STATUS_UNRELIABLE -> 180.0
52- else -> 180.0
53- }
49+ private fun accuracyToDegrees (accuracy : Int ): Double =
50+ when (accuracy) {
51+ SensorManager .SENSOR_STATUS_ACCURACY_HIGH -> 5.0
52+ SensorManager .SENSOR_STATUS_ACCURACY_MEDIUM -> 15.0
53+ SensorManager .SENSOR_STATUS_ACCURACY_LOW -> 45.0
54+ SensorManager .SENSOR_STATUS_UNRELIABLE -> 180.0
55+ else -> 180.0
56+ }
5457
5558 private val bearing: Flow <Pair <Double , Double >> = callbackFlow {
5659 val rotationMatrix = FloatArray (9 )
5760 val orientationAngles = FloatArray (3 )
5861 var accuracyDegrees = accuracyToDegrees(SensorManager .SENSOR_STATUS_UNRELIABLE )
5962
60- val listener = object : SensorEventListener {
61- override fun onSensorChanged (event : SensorEvent ? ) {
62- if (event?.sensor?.type == Sensor .TYPE_ROTATION_VECTOR ) {
63- SensorManager .getRotationMatrixFromVector(
64- rotationMatrix,
65- event.values
66- )
67- SensorManager .getOrientation(
68- rotationMatrix,
69- orientationAngles
70- )
63+ val listener =
64+ object : SensorEventListener {
65+ override fun onSensorChanged (event : SensorEvent ? ) {
66+ if (event?.sensor?.type == Sensor .TYPE_ROTATION_VECTOR ) {
67+ SensorManager .getRotationMatrixFromVector(rotationMatrix, event.values)
68+ SensorManager .getOrientation(rotationMatrix, orientationAngles)
7169
72- trySend(
73- Math .toDegrees(orientationAngles[0 ].toDouble()) to accuracyDegrees
74- )
75- }
76- }
70+ trySend(Math .toDegrees(orientationAngles[0 ].toDouble()) to accuracyDegrees)
71+ }
72+ }
7773
78- override fun onAccuracyChanged (sensor : Sensor ? , accuracy : Int ) {
79- accuracyDegrees = accuracyToDegrees(accuracy)
80- }
81- }
74+ override fun onAccuracyChanged (sensor : Sensor ? , accuracy : Int ) {
75+ accuracyDegrees = accuracyToDegrees(accuracy)
76+ }
77+ }
8278
83- val sensor = sensorManager.getDefaultSensor(Sensor .TYPE_ROTATION_VECTOR )
84- ? : throw IllegalStateException (" Rotation vector sensor is not available" )
79+ val sensor =
80+ sensorManager.getDefaultSensor(Sensor .TYPE_ROTATION_VECTOR )
81+ ? : throw IllegalStateException (" Rotation vector sensor is not available" )
8582
8683 sensorManager.registerListener(
87- listener,
88- sensor,
89- SensorManager .SENSOR_DELAY_NORMAL ,
84+ listener,
85+ sensor,
86+ SensorManager .SENSOR_DELAY_NORMAL ,
9087 )
9188
92- awaitClose {
93- sensorManager.unregisterListener(listener)
94- }
89+ awaitClose { sensorManager.unregisterListener(listener) }
9590 }
9691
9792 override val location: StateFlow <Location ?> =
98- locationProvider.location.combine(bearing) { location, (sensorBearing, sensorAccuracy) ->
99- val bearingAccuracy = location?.bearingAccuracy
100- if (bearingAccuracy != null && bearingAccuracy > sensorAccuracy) {
101- location.copy(bearing = sensorBearing, accuracy = sensorAccuracy)
102- } else {
103- location
104- }
105- }.stateIn(coroutineScope, sharingStarted, null )
93+ locationProvider.location
94+ .combine(bearing) { location, (sensorBearing, sensorAccuracy) ->
95+ val bearingAccuracy = location?.bearingAccuracy
96+ if (bearingAccuracy != null && bearingAccuracy > sensorAccuracy) {
97+ location.copy(bearing = sensorBearing, accuracy = sensorAccuracy)
98+ } else {
99+ location
100+ }
101+ }
102+ .stateIn(coroutineScope, sharingStarted, null )
106103}
107104
108105@Composable
106+ @RequiresPermission(
107+ anyOf = [Manifest .permission.ACCESS_FINE_LOCATION , Manifest .permission.ACCESS_COARSE_LOCATION ]
108+ )
109109public actual fun rememberSensorEnhancedLocationProvider (
110- locationProvider : LocationProvider ,
110+ locationProvider : LocationProvider ,
111111): LocationProvider {
112112 return rememberAndroidSensorEnhancedLocationProvider(locationProvider = locationProvider)
113113}
114114
115- /* * Create and remember an [AndroidSensorEnhancedLocationProvider], a [LocationProvider] that enhances an existing [LocationProvider] with bearing information
116- * derived from the device's rotation vector sensor.
115+ /* *
116+ * Create and remember an [AndroidSensorEnhancedLocationProvider], a [LocationProvider] that
117+ * enhances an existing [LocationProvider] with bearing information derived from the device's
118+ * rotation vector sensor.
117119 */
118120@Composable
121+ @RequiresPermission(
122+ anyOf = [Manifest .permission.ACCESS_FINE_LOCATION , Manifest .permission.ACCESS_COARSE_LOCATION ]
123+ )
119124public fun rememberAndroidSensorEnhancedLocationProvider (
120- locationProvider : LocationProvider ,
121- context : Context = LocalContext .current,
122- coroutineScope : CoroutineScope = rememberCoroutineScope(),
123- sharingStarted : SharingStarted = SharingStarted .WhileSubscribed (stopTimeoutMillis = 1000),
125+ locationProvider : LocationProvider ,
126+ context : Context = LocalContext .current,
127+ coroutineScope : CoroutineScope = rememberCoroutineScope(),
128+ sharingStarted : SharingStarted = SharingStarted .WhileSubscribed (stopTimeoutMillis = 1000),
124129): AndroidSensorEnhancedLocationProvider {
125130 return remember(
126- context,
127- locationProvider,
128- coroutineScope,
129- sharingStarted,
131+ context,
132+ locationProvider,
133+ coroutineScope,
134+ sharingStarted,
130135 ) {
131136 AndroidSensorEnhancedLocationProvider (
132- context = context,
133- locationProvider = locationProvider,
134- coroutineScope = coroutineScope,
135- sharingStarted = sharingStarted,
137+ context = context,
138+ locationProvider = locationProvider,
139+ coroutineScope = coroutineScope,
140+ sharingStarted = sharingStarted,
136141 )
137142 }
138143}
0 commit comments