expo-location 58.0.8 → 58.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +11 -12
  2. package/android/build.gradle +3 -2
  3. package/android/src/main/java/expo/modules/location/next/Constants.kt +6 -0
  4. package/android/src/main/java/expo/modules/location/next/LocationModuleNext.kt +230 -0
  5. package/android/src/main/java/expo/modules/location/next/LocationPermissions.kt +157 -0
  6. package/android/src/main/java/expo/modules/location/next/LocationRecords.kt +201 -0
  7. package/android/src/main/java/expo/modules/location/next/WatchPosition.kt +168 -0
  8. package/android/src/main/java/expo/modules/location/next/locationProviders/AndroidLocationProvider.kt +230 -0
  9. package/android/src/main/java/expo/modules/location/next/locationProviders/FallbackLocationProvider.kt +35 -0
  10. package/android/src/main/java/expo/modules/location/next/locationProviders/GmsLocationProvider.kt +188 -0
  11. package/android/src/main/java/expo/modules/location/next/locationProviders/LocationProvider.kt +78 -0
  12. package/android/src/main/java/expo/modules/location/services/LocationTaskService.kt +5 -1
  13. package/android/src/main/java/expo/modules/location/taskConsumers/GeofencingTaskConsumer.kt +11 -1
  14. package/android/src/main/java/expo/modules/location/taskConsumers/LocationTaskConsumer.kt +11 -3
  15. package/expo-module.config.json +4 -2
  16. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar +0 -0
  17. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar.md5 +1 -0
  18. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar.sha1 +1 -0
  19. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar.sha256 +1 -0
  20. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar.sha512 +1 -0
  21. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar +0 -0
  22. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar.md5 +1 -0
  23. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar.sha1 +1 -0
  24. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar.sha256 +1 -0
  25. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar.sha512 +1 -0
  26. package/local-maven-repo/host/exp/exponent/expo.modules.location/{58.0.8/expo.modules.location-58.0.8.module → 58.0.10/expo.modules.location-58.0.10.module} +29 -22
  27. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.module.md5 +1 -0
  28. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.module.sha1 +1 -0
  29. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.module.sha256 +1 -0
  30. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.module.sha512 +1 -0
  31. package/local-maven-repo/host/exp/exponent/expo.modules.location/{58.0.8/expo.modules.location-58.0.8.pom → 58.0.10/expo.modules.location-58.0.10.pom} +7 -1
  32. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.pom.md5 +1 -0
  33. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.pom.sha1 +1 -0
  34. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.pom.sha256 +1 -0
  35. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.pom.sha512 +1 -0
  36. package/package.json +6 -5
  37. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar +0 -0
  38. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar.md5 +0 -1
  39. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar.sha1 +0 -1
  40. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar.sha256 +0 -1
  41. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar.sha512 +0 -1
  42. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar +0 -0
  43. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar.md5 +0 -1
  44. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar.sha1 +0 -1
  45. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar.sha256 +0 -1
  46. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar.sha512 +0 -1
  47. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.module.md5 +0 -1
  48. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.module.sha1 +0 -1
  49. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.module.sha256 +0 -1
  50. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.module.sha512 +0 -1
  51. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.pom.md5 +0 -1
  52. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.pom.sha1 +0 -1
  53. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.pom.sha256 +0 -1
  54. package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.pom.sha512 +0 -1
  55. package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml +0 -13
  56. package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml.md5 +0 -1
  57. package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml.sha1 +0 -1
  58. package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml.sha256 +0 -1
  59. package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml.sha512 +0 -1
  60. package/prebuilds/output/debug/xcframeworks/ExpoLocation.tar.gz +0 -0
  61. package/prebuilds/output/release/xcframeworks/ExpoLocation.tar.gz +0 -0
package/CHANGELOG.md CHANGED
@@ -1,14 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## Unpublished
3
+ ## 58.0.10
4
4
 
5
- ### 🛠 Breaking changes
5
+ ### Patch Changes
6
6
 
7
- ### 🎉 New features
7
+ - Bump precompiled artifacts due to ABI breakage in expo-modules-jsi@58.0.6. ([#50881](https://github.com/expo/expo/pull/50881) by [@kitten](https://github.com/kitten))
8
+ - [Android] Add the core functionalities for the **next** version: permission getters and requesters, position getter, and `enableLocationServices` prompt. Introduce `LocationProvider` interface to allow for multiple implementations: for now `gms` and `android.location`. ([#49988](https://github.com/expo/expo/pull/49988) by [@HubertBer](https://github.com/HubertBer))
9
+ - [Android] Add the position watchers to the **next** implementation. Update the `LocationProvider` interface to also support watchers and implement it in both GMS and Android providers. ([#49992](https://github.com/expo/expo/pull/49992) by [@HubertBer](https://github.com/HubertBer))
8
10
 
9
- ### 🐛 Bug fixes
11
+ ## 58.0.9
10
12
 
11
- ### 💡 Others
13
+ ### Patch Changes
14
+
15
+ - Force-bump all packages, due to migration to changesets. ([#50762](https://github.com/expo/expo/pull/50762) by [@kitten](https://github.com/kitten))
16
+ - Updated dependencies. ([#50762](https://github.com/expo/expo/pull/50762))
17
+ - @expo/image-utils@0.12.2
12
18
 
13
19
  ## 58.0.8 — 2026-09-28
14
20
 
@@ -609,13 +615,6 @@ _This version does not introduce any user-facing changes._
609
615
 
610
616
  - Allow location to work on Android with only coarse location permission. All apps do not require fine/precise location permission, but in past Expo was enforcing fine/precise even if you only needed coarse level location. ([#15760](https://github.com/expo/expo/pull/15760) by [@Noitidart](https://github.com/Noitidart))
611
617
 
612
- ## Unpublished
613
-
614
- ### 🛠 Breaking changes
615
-
616
- - Add an option to whether kill or keep the foreground service when app is killed on Android. ([#15633](https://github.com/expo/expo/pull/15633) by [@islamouzou](https://github.com/islamouzou))
617
- - Updated `@expo/config-plugins` from `4.0.2` to `4.0.14` ([#15621](https://github.com/expo/expo/pull/15621) by [@EvanBacon](https://github.com/EvanBacon))
618
-
619
618
  ## 14.0.2 — 2022-02-01
620
619
 
621
620
  ### 🐛 Bug fixes
@@ -4,17 +4,18 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '58.0.8'
7
+ version = '58.0.10'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.location"
11
11
  defaultConfig {
12
12
  versionCode 29
13
- versionName "58.0.8"
13
+ versionName "58.0.10"
14
14
  consumerProguardFiles("proguard-rules.pro")
15
15
  }
16
16
  }
17
17
 
18
18
  dependencies {
19
19
  api 'com.google.android.gms:play-services-location:21.0.1'
20
+ implementation "androidx.core:core-ktx:1.17.0"
20
21
  }
@@ -0,0 +1,6 @@
1
+ package expo.modules.location.next
2
+
3
+ const val POSITION_CHANGED = "positionChanged"
4
+
5
+ /** A unique, arbitrary number used to recognise the result of the "enable location services" activity. */
6
+ const val SETTINGS_REQUEST_CODE = 1492
@@ -0,0 +1,230 @@
1
+ package expo.modules.location.next
2
+
3
+ import android.content.Context
4
+ import android.location.LocationManager
5
+ import androidx.core.location.LocationManagerCompat
6
+ import com.google.android.gms.common.ConnectionResult
7
+ import com.google.android.gms.common.GoogleApiAvailability
8
+ import com.google.android.gms.location.LocationServices
9
+ import expo.modules.interfaces.permissions.Permissions
10
+ import expo.modules.kotlin.exception.Exceptions
11
+ import expo.modules.kotlin.modules.Module
12
+ import expo.modules.kotlin.modules.ModuleDefinition
13
+ import expo.modules.kotlin.functions.Coroutine
14
+ import expo.modules.kotlin.sharedobjects.SharedRef
15
+ import expo.modules.location.next.locationProviders.AndroidLocationProvider
16
+ import expo.modules.location.next.locationProviders.EnableLocationServicesResult
17
+ import expo.modules.location.next.locationProviders.FallbackLocationProvider
18
+ import expo.modules.location.next.locationProviders.GmsLocationProvider
19
+ import expo.modules.location.next.locationProviders.LocationProvider
20
+ import kotlinx.coroutines.CompletableDeferred
21
+ import expo.modules.location.next.locationProviders.WatchPositionParameters
22
+ import expo.modules.location.next.locationProviders.PositionUpdatesSession
23
+ import java.lang.ref.WeakReference
24
+ import kotlin.time.Duration
25
+
26
+ class LocationModuleNext : Module() {
27
+ private val context: Context
28
+ get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
29
+
30
+ private val permissionsManager: Permissions
31
+ get() = appContext.permissions ?: throw NoPermissionsModuleException()
32
+
33
+ val watchSessions: MutableList<WeakReference<PausableWatchSession>> = mutableListOf()
34
+ val fusedLocationProviderInstance: SharedRef<LocationProvider> by lazy {
35
+ val fusedLocationProvider = LocationServices.getFusedLocationProviderClient(context)
36
+
37
+ val gmsLocationProvider = GmsLocationProvider(
38
+ fusedLocationProvider,
39
+ LocationServices.getSettingsClient(context)
40
+ ) { GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS }
41
+
42
+ SharedRef(gmsLocationProvider)
43
+ }
44
+
45
+ val androidLocationProviderInstance: SharedRef<LocationProvider> by lazy {
46
+ SharedRef(AndroidLocationProvider(context))
47
+ }
48
+
49
+ lateinit var currentLocationProvider: LocationProvider
50
+
51
+ lateinit var locationManager: LocationManager
52
+
53
+ @Volatile
54
+ private var locationServicesPrompt: CompletableDeferred<Boolean>? = null
55
+
56
+ fun createPositionWatchHandle(initialParameters: WatchPositionParameters, session: PositionUpdatesSession): PositionWatchHandle = synchronized(watchSessions) {
57
+ val pausableSession = PausableWatchSession(initialParameters, session)
58
+ watchSessions.add(WeakReference(pausableSession))
59
+ return@synchronized PositionWatchHandle(pausableSession)
60
+ }
61
+
62
+ override fun definition() = ModuleDefinition {
63
+ Name("LocationModuleNext")
64
+
65
+ OnCreate {
66
+ currentLocationProvider = FallbackLocationProvider(
67
+ listOf(fusedLocationProviderInstance.ref, androidLocationProviderInstance.ref)
68
+ )
69
+ locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
70
+ }
71
+
72
+ // Permissions
73
+ AsyncFunction("requestForegroundPermissions") Coroutine { options: RequestForegroundPermissionsOptions? ->
74
+ permissionsManager.requestForegroundPermissions(options)
75
+ return@Coroutine permissionsManager.getLocationPermissions(background = false)
76
+ }
77
+
78
+ AsyncFunction("getForegroundPermissions") Coroutine { ->
79
+ return@Coroutine permissionsManager.getLocationPermissions(background = false)
80
+ }
81
+
82
+ AsyncFunction("requestBackgroundPermissions") Coroutine { ->
83
+ permissionsManager.requestBackgroundPermissions()
84
+ return@Coroutine permissionsManager.getLocationPermissions(background = true)
85
+ }
86
+
87
+ AsyncFunction("getBackgroundPermissions") Coroutine { ->
88
+ return@Coroutine permissionsManager.getLocationPermissions(background = true)
89
+ }
90
+
91
+ // Location providers
92
+ Function("setLocationProvider") { locationProvider: SharedRef<LocationProvider> ->
93
+ currentLocationProvider = locationProvider.ref
94
+ }
95
+
96
+ Function("getSelectedLocationProviderName") {
97
+ currentLocationProvider.name
98
+ }
99
+
100
+ Class("LocationProvider") {
101
+ StaticFunction("Gms") { ->
102
+ fusedLocationProviderInstance
103
+ }
104
+ StaticFunction("Android") { ->
105
+ androidLocationProviderInstance
106
+ }
107
+ StaticFunction("Fallback") { providers: List<SharedRef<LocationProvider>> ->
108
+ SharedRef(FallbackLocationProvider(providers.map { it.ref }))
109
+ }
110
+ }
111
+
112
+ AsyncFunction("getPosition") Coroutine { options: GetPositionOptions? ->
113
+ permissionsManager.ensureForegroundPermissions()
114
+ val providerOptions = (options ?: GetPositionOptions()).toProviderOptions()
115
+ return@Coroutine currentLocationProvider.getPosition(providerOptions).getOrNull("getPosition")
116
+ }
117
+
118
+ Function("watchPosition") { profile: LocationProfile? ->
119
+ permissionsManager.ensureForegroundPermissions()
120
+ val parameters = (profile ?: LocationProfile.DEFAULT).watchParameters()
121
+ return@Function createPositionWatchHandle(parameters, currentLocationProvider.watchPosition().getOrThrow("watchPosition"))
122
+ }
123
+
124
+ Function("hasLocationServicesEnabled") { ->
125
+ hasLocationServicesEnabled()
126
+ }
127
+
128
+ AsyncFunction("enableLocationServices") Coroutine { ->
129
+ if (hasLocationServicesEnabled()) {
130
+ return@Coroutine true
131
+ }
132
+ locationServicesPrompt?.let {
133
+ return@Coroutine it.await()
134
+ }
135
+
136
+ val promptResult = CompletableDeferred<Boolean>()
137
+ locationServicesPrompt = promptResult
138
+ try {
139
+ val enableServicesResult = currentLocationProvider
140
+ .enableLocationServices(appContext.throwingActivity)
141
+ .getOrThrow("enableLocationServices")
142
+
143
+ when (enableServicesResult) {
144
+ is EnableLocationServicesResult.Disabled -> promptResult.complete(false)
145
+ is EnableLocationServicesResult.Enabled -> promptResult.complete(true)
146
+ is EnableLocationServicesResult.ResolutionPending -> {}
147
+ }
148
+
149
+ return@Coroutine promptResult.await()
150
+ } catch (cause: Throwable) {
151
+ promptResult.completeExceptionally(cause)
152
+ throw cause
153
+ } finally {
154
+ locationServicesPrompt = null
155
+ }
156
+ }
157
+
158
+ OnActivityResult { _, payload ->
159
+ if (payload.requestCode == SETTINGS_REQUEST_CODE) {
160
+ locationServicesPrompt?.complete(hasLocationServicesEnabled())
161
+ }
162
+ }
163
+
164
+ Class(PositionWatchHandle::class) {
165
+ Constructor {
166
+ throw PositionWatchHandleCreationException()
167
+ }
168
+
169
+ Events(POSITION_CHANGED)
170
+
171
+ Function("pause") { locationWatchHandle: PositionWatchHandle ->
172
+ locationWatchHandle.session.pause()
173
+ }
174
+
175
+ Function("resume") { locationWatchHandle: PositionWatchHandle ->
176
+ return@Function locationWatchHandle.session.resume()
177
+ }
178
+
179
+ Function("withProfile") { locationWatchHandle: PositionWatchHandle, profile: LocationProfile ->
180
+ locationWatchHandle.session.withProfile(profile)
181
+ locationWatchHandle
182
+ }
183
+
184
+ Function("withInterval") { locationWatchHandle: PositionWatchHandle, interval: Duration ->
185
+ if (interval < Duration.ZERO || interval == Duration.INFINITE) {
186
+ throw InvalidWatchIntervalException(interval)
187
+ }
188
+ locationWatchHandle.session.withInterval(interval)
189
+ locationWatchHandle
190
+ }
191
+
192
+ Function("restart") { locationWatchHandle: PositionWatchHandle ->
193
+ return@Function locationWatchHandle.session.restart()
194
+ }
195
+
196
+ Function("status") { locationWatchHandle: PositionWatchHandle ->
197
+ locationWatchHandle.session.status()
198
+ }
199
+ }
200
+
201
+ OnDestroy {
202
+ synchronized(watchSessions) {
203
+ for (session in watchSessions) {
204
+ session.get()?.release()
205
+ }
206
+ }
207
+ }
208
+
209
+ OnActivityEntersForeground {
210
+ synchronized(watchSessions) {
211
+ for (session in watchSessions) {
212
+ session.get()?.onLifecycleChange(true)
213
+ }
214
+ }
215
+ }
216
+
217
+ OnActivityEntersBackground {
218
+ synchronized(watchSessions) {
219
+ watchSessions.removeIf { it.get() == null }
220
+ for (session in watchSessions) {
221
+ session.get()?.onLifecycleChange(false)
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ private fun hasLocationServicesEnabled(): Boolean {
228
+ return LocationManagerCompat.isLocationEnabled(locationManager)
229
+ }
230
+ }
@@ -0,0 +1,157 @@
1
+ package expo.modules.location.next
2
+
3
+ import android.Manifest
4
+ import android.os.Build
5
+ import androidx.annotation.ChecksSdkIntAtLeast
6
+ import expo.modules.kotlin.exception.CodedException
7
+ import expo.modules.interfaces.permissions.Permissions
8
+ import expo.modules.interfaces.permissions.PermissionsResponse
9
+ import expo.modules.interfaces.permissions.PermissionsStatus
10
+ import kotlinx.coroutines.CompletableDeferred
11
+
12
+ class NoPermissionsModuleException :
13
+ CodedException("Permissions module is null. Are you sure all the installed Expo modules are properly linked?")
14
+
15
+ class NoPermissionInManifestException(permissionName: String) :
16
+ CodedException("You need to add `$permissionName` to the AndroidManifest")
17
+
18
+ class ForegroundLocationPermissionsNotGrantedException :
19
+ CodedException("Foreground location permissions are not granted. Call requestForegroundPermissions() and make sure the user grants precise or approximate location.")
20
+
21
+ class BackgroundLocationPermissionsNotGrantedException :
22
+ CodedException("Background location permissions are not granted. Call requestBackgroundPermissions() after foreground location has been granted.")
23
+
24
+ class RequestingBackgroundPermissionsWithoutForegroundGrantException :
25
+ CodedException("Need to have foreground permissions granted, before asking for background permissions. Call requestForegroundPermissions() first and make sure the foreground location is granted.")
26
+
27
+ private val FOREGROUND_PERMISSIONS = arrayOf(
28
+ Manifest.permission.ACCESS_COARSE_LOCATION,
29
+ Manifest.permission.ACCESS_FINE_LOCATION
30
+ )
31
+
32
+ private val LOCATION_PERMISSIONS = arrayOf(
33
+ Manifest.permission.ACCESS_COARSE_LOCATION,
34
+ Manifest.permission.ACCESS_FINE_LOCATION,
35
+ Manifest.permission.ACCESS_BACKGROUND_LOCATION
36
+ )
37
+
38
+ private val COARSE_PERMISSIONS = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
39
+
40
+ // Before Q there is no separate background permission -- foreground access covers it.
41
+ // ChecksSdkIntAtLeast keeps lint's API-level narrowing working through the call.
42
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
43
+ private fun supportsBackgroundPermission(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
44
+
45
+ internal suspend fun Permissions.requestBackgroundPermissions() {
46
+ if (!supportsBackgroundPermission()) {
47
+ return
48
+ }
49
+
50
+ if (!isPermissionPresentInManifest(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
51
+ throw NoPermissionInManifestException("ACCESS_BACKGROUND_LOCATION")
52
+ }
53
+
54
+ val foregroundPermission = queryPermissions(*FOREGROUND_PERMISSIONS).foregroundPermission()
55
+ if (foregroundPermission.status != PermissionsStatus.GRANTED) {
56
+ throw RequestingBackgroundPermissionsWithoutForegroundGrantException()
57
+ }
58
+
59
+ requestPermissions(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
60
+ }
61
+
62
+ internal suspend fun Permissions.requestForegroundPermissions(options: RequestForegroundPermissionsOptions?) {
63
+ val permissions = when (options?.accuracy ?: LocationAccuracyOption.FULL) {
64
+ LocationAccuracyOption.FULL -> FOREGROUND_PERMISSIONS
65
+ LocationAccuracyOption.REDUCED -> COARSE_PERMISSIONS
66
+ }
67
+ requestPermissions(*permissions)
68
+ }
69
+
70
+ internal suspend fun Permissions.getLocationPermissions(background: Boolean): LocationPermissionResponse {
71
+ val permissions = if (supportsBackgroundPermission()) {
72
+ LOCATION_PERMISSIONS
73
+ } else {
74
+ FOREGROUND_PERMISSIONS
75
+ }
76
+ val queried = queryPermissions(*permissions)
77
+
78
+ val foregroundPermission = queried.foregroundPermission()
79
+ val backgroundPermission = if (supportsBackgroundPermission()) {
80
+ queried.backgroundPermission()
81
+ } else {
82
+ foregroundPermission
83
+ }
84
+ val foregroundGranted = foregroundPermission.status == PermissionsStatus.GRANTED
85
+ val backgroundGranted = backgroundPermission.status == PermissionsStatus.GRANTED
86
+ val wantedPermission = if (background) {
87
+ backgroundPermission
88
+ } else {
89
+ foregroundPermission
90
+ }
91
+
92
+ return LocationPermissionResponse(
93
+ status = LocationPermissionStatus.fromString(wantedPermission.status.status),
94
+ granted = foregroundGranted && (!background || backgroundGranted),
95
+ canAskAgain = wantedPermission.canAskAgain,
96
+ scope = when {
97
+ backgroundGranted -> LocationScope.ALWAYS
98
+ foregroundGranted -> LocationScope.WHEN_IN_USE
99
+ else -> LocationScope.NOT_GRANTED
100
+ },
101
+ accuracy = queried.locationAccuracy(),
102
+ expires = "never"
103
+ )
104
+ }
105
+
106
+ internal fun Permissions.ensureForegroundPermissions() {
107
+ val hasFine = hasGrantedPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
108
+ val hasCoarse = hasGrantedPermissions(Manifest.permission.ACCESS_COARSE_LOCATION)
109
+ if (!hasFine && !hasCoarse) {
110
+ throw ForegroundLocationPermissionsNotGrantedException()
111
+ }
112
+ }
113
+
114
+ internal fun Permissions.ensureBackgroundPermissions() {
115
+ ensureForegroundPermissions()
116
+ if (!supportsBackgroundPermission()) {
117
+ return
118
+ }
119
+ if (!hasGrantedPermissions(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
120
+ throw BackgroundLocationPermissionsNotGrantedException()
121
+ }
122
+ }
123
+
124
+ private suspend fun Permissions.queryPermissions(vararg permissions: String): Map<String, PermissionsResponse> {
125
+ val result = CompletableDeferred<Map<String, PermissionsResponse>>()
126
+ getPermissions({ result.complete(it) }, *permissions)
127
+ return result.await()
128
+ }
129
+
130
+ private suspend fun Permissions.requestPermissions(vararg permissions: String): Map<String, PermissionsResponse> {
131
+ val result = CompletableDeferred<Map<String, PermissionsResponse>>()
132
+ askForPermissions({ result.complete(it) }, *permissions)
133
+ return result.await()
134
+ }
135
+
136
+ private fun Map<String, PermissionsResponse>.responseFor(permission: String): PermissionsResponse =
137
+ this[permission] ?: PermissionsResponse(PermissionsStatus.UNDETERMINED)
138
+
139
+ private fun Map<String, PermissionsResponse>.foregroundPermission(): PermissionsResponse {
140
+ val coarse = responseFor(Manifest.permission.ACCESS_COARSE_LOCATION)
141
+ val fine = responseFor(Manifest.permission.ACCESS_FINE_LOCATION)
142
+ val status = when {
143
+ coarse.status == PermissionsStatus.GRANTED || fine.status == PermissionsStatus.GRANTED -> PermissionsStatus.GRANTED
144
+ coarse.status == PermissionsStatus.DENIED || fine.status == PermissionsStatus.DENIED -> PermissionsStatus.DENIED
145
+ else -> PermissionsStatus.UNDETERMINED
146
+ }
147
+ return PermissionsResponse(status, coarse.canAskAgain || fine.canAskAgain)
148
+ }
149
+
150
+ private fun Map<String, PermissionsResponse>.backgroundPermission(): PermissionsResponse =
151
+ responseFor(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
152
+
153
+ private fun Map<String, PermissionsResponse>.locationAccuracy(): LocationAccuracy = when {
154
+ responseFor(Manifest.permission.ACCESS_FINE_LOCATION).status == PermissionsStatus.GRANTED -> LocationAccuracy.FULL
155
+ responseFor(Manifest.permission.ACCESS_COARSE_LOCATION).status == PermissionsStatus.GRANTED -> LocationAccuracy.REDUCED
156
+ else -> LocationAccuracy.NOT_GRANTED
157
+ }
@@ -0,0 +1,201 @@
1
+ package expo.modules.location.next
2
+
3
+ import android.location.Location
4
+ import android.os.Build
5
+ import expo.modules.kotlin.records.Field
6
+ import expo.modules.kotlin.records.Record
7
+ import expo.modules.kotlin.types.Enumerable
8
+ import expo.modules.kotlin.types.OptimizedRecord
9
+ import expo.modules.location.next.locationProviders.GetCurrentPositionOptions
10
+ import expo.modules.location.next.locationProviders.LocationPriority
11
+ import expo.modules.location.next.locationProviders.WatchPositionParameters
12
+ import kotlin.time.Duration
13
+ import kotlin.time.Duration.Companion.seconds
14
+
15
+ enum class LocationPermissionStatus(val value: String) : Enumerable {
16
+ GRANTED("granted"),
17
+ DENIED("denied"),
18
+ UNDETERMINED("undetermined");
19
+
20
+ companion object {
21
+ fun fromString(status: String?): LocationPermissionStatus = when (status) {
22
+ "granted" -> GRANTED
23
+ "denied" -> DENIED
24
+ else -> UNDETERMINED
25
+ }
26
+ }
27
+ }
28
+
29
+ enum class LocationScope(val value: String) : Enumerable {
30
+ ALWAYS("always"),
31
+ WHEN_IN_USE("whenInUse"),
32
+ NOT_GRANTED("notGranted")
33
+ }
34
+
35
+ enum class LocationAccuracy(val value: String) : Enumerable {
36
+ FULL("full"),
37
+ REDUCED("reduced"),
38
+ NOT_GRANTED("notGranted")
39
+ }
40
+
41
+ enum class LocationAccuracyOption(val value: String) : Enumerable {
42
+ FULL("full"),
43
+ REDUCED("reduced")
44
+ }
45
+
46
+ class RequestForegroundPermissionsOptions(
47
+ @Field val accuracy: LocationAccuracyOption? = null
48
+ ) : Record
49
+
50
+ class LocationPermissionResponse(
51
+ @Field val status: LocationPermissionStatus,
52
+ @Field val granted: Boolean,
53
+ @Field val canAskAgain: Boolean,
54
+ @Field val scope: LocationScope,
55
+ @Field val accuracy: LocationAccuracy,
56
+ @Field val expires: String = "never"
57
+ ) : Record
58
+
59
+ enum class LocationProfile(val value: String) : Enumerable {
60
+ DEFAULT("default"),
61
+ AUTOMOTIVE_NAVIGATION("automotiveNavigation"),
62
+ OTHER_NAVIGATION("otherNavigation"),
63
+ FITNESS("fitness"),
64
+ AIRBORNE("airborne"),
65
+ LOW_POWER("lowPower");
66
+
67
+ fun priority(): LocationPriority {
68
+ return when (this) {
69
+ DEFAULT -> LocationPriority.BALANCED_POWER_ACCURACY
70
+ AUTOMOTIVE_NAVIGATION -> LocationPriority.HIGH_ACCURACY
71
+ OTHER_NAVIGATION -> LocationPriority.HIGH_ACCURACY
72
+ FITNESS -> LocationPriority.HIGH_ACCURACY
73
+ AIRBORNE -> LocationPriority.HIGH_ACCURACY
74
+ LOW_POWER -> LocationPriority.LOW_POWER
75
+ }
76
+ }
77
+
78
+ fun watchParameters(): WatchPositionParameters {
79
+ return when (this) {
80
+ DEFAULT -> WatchPositionParameters(LocationPriority.BALANCED_POWER_ACCURACY, 5.seconds, Duration.ZERO)
81
+ AUTOMOTIVE_NAVIGATION -> WatchPositionParameters(LocationPriority.HIGH_ACCURACY, 1.seconds, Duration.ZERO)
82
+ OTHER_NAVIGATION -> WatchPositionParameters(LocationPriority.HIGH_ACCURACY, 2.seconds, Duration.ZERO)
83
+ FITNESS -> WatchPositionParameters(LocationPriority.HIGH_ACCURACY, 2.seconds, Duration.ZERO)
84
+ AIRBORNE -> WatchPositionParameters(LocationPriority.HIGH_ACCURACY, 1.seconds, Duration.ZERO)
85
+ LOW_POWER -> WatchPositionParameters(LocationPriority.LOW_POWER, 60.seconds, 300.seconds)
86
+ }
87
+ }
88
+ }
89
+
90
+ class GetPositionOptions(
91
+ @Field val maxCachedAge: Double? = null,
92
+ @Field val timeout: Double? = null,
93
+ @Field val profile: LocationProfile = LocationProfile.DEFAULT
94
+ ) : Record {
95
+ fun toProviderOptions() = GetCurrentPositionOptions(
96
+ maxCachedAge = (maxCachedAge ?: 0.0).seconds,
97
+ timeout = (timeout ?: 30.0).seconds,
98
+ priority = profile.priority()
99
+ )
100
+ }
101
+
102
+ @OptimizedRecord
103
+ class Coordinates(
104
+ @Field val latitude: Double,
105
+ @Field val longitude: Double
106
+ ) : Record
107
+
108
+ @OptimizedRecord
109
+ class Position(
110
+ @Field val coordinates: Coordinates,
111
+ @Field val timestamp: Double,
112
+ @Field val mocked: Boolean,
113
+
114
+ @Field val mslAltitude: Double? = null,
115
+ @Field val altitude: Double? = null,
116
+ @Field val speed: Double? = null,
117
+ @Field val heading: Double? = null,
118
+
119
+ @Field val horizontalAccuracy: Double? = null,
120
+ @Field val verticalAccuracy: Double? = null,
121
+ @Field val speedAccuracy: Double? = null,
122
+ @Field val headingAccuracy: Double? = null
123
+ ) : Record
124
+
125
+ fun Location.mslAltitude(): Double? {
126
+ return if (Build.VERSION.SDK_INT >= 34 && hasMslAltitude()) {
127
+ mslAltitudeMeters
128
+ } else {
129
+ null
130
+ }
131
+ }
132
+
133
+ fun Location.verticalAccuracy(): Double? {
134
+ return if (Build.VERSION.SDK_INT >= 26 && hasAltitude() && hasVerticalAccuracy()) {
135
+ verticalAccuracyMeters.toDouble()
136
+ } else {
137
+ null
138
+ }
139
+ }
140
+
141
+ fun Location.speedAccuracy(): Double? {
142
+ return if (Build.VERSION.SDK_INT >= 26 && hasSpeed() && hasSpeedAccuracy()) {
143
+ speedAccuracyMetersPerSecond.toDouble()
144
+ } else {
145
+ null
146
+ }
147
+ }
148
+
149
+ fun Location.headingAccuracy(): Double? {
150
+ return if (Build.VERSION.SDK_INT >= 26 && hasBearing() && hasBearingAccuracy()) {
151
+ bearingAccuracyDegrees.toDouble()
152
+ } else {
153
+ null
154
+ }
155
+ }
156
+
157
+ fun Location.mocked(): Boolean {
158
+ return if (Build.VERSION.SDK_INT >= 31) {
159
+ isMock
160
+ } else {
161
+ @Suppress("DEPRECATION")
162
+ isFromMockProvider
163
+ }
164
+ }
165
+
166
+ fun Location.toPosition(): Position {
167
+ return Position(
168
+ coordinates = Coordinates(
169
+ latitude,
170
+ longitude
171
+ ),
172
+ timestamp = time.toDouble(),
173
+ mocked = mocked(),
174
+
175
+ mslAltitude = mslAltitude(),
176
+ altitude = if (hasAltitude()) {
177
+ altitude
178
+ } else {
179
+ null
180
+ },
181
+ speed = if (hasSpeed()) {
182
+ speed.toDouble()
183
+ } else {
184
+ null
185
+ },
186
+ heading = if (hasBearing()) {
187
+ bearing.toDouble()
188
+ } else {
189
+ null
190
+ },
191
+
192
+ horizontalAccuracy = if (this.hasAccuracy()) {
193
+ this.accuracy.toDouble()
194
+ } else {
195
+ null
196
+ },
197
+ verticalAccuracy = verticalAccuracy(),
198
+ speedAccuracy = speedAccuracy(),
199
+ headingAccuracy = headingAccuracy()
200
+ )
201
+ }