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.
- package/CHANGELOG.md +11 -12
- package/android/build.gradle +3 -2
- package/android/src/main/java/expo/modules/location/next/Constants.kt +6 -0
- package/android/src/main/java/expo/modules/location/next/LocationModuleNext.kt +230 -0
- package/android/src/main/java/expo/modules/location/next/LocationPermissions.kt +157 -0
- package/android/src/main/java/expo/modules/location/next/LocationRecords.kt +201 -0
- package/android/src/main/java/expo/modules/location/next/WatchPosition.kt +168 -0
- package/android/src/main/java/expo/modules/location/next/locationProviders/AndroidLocationProvider.kt +230 -0
- package/android/src/main/java/expo/modules/location/next/locationProviders/FallbackLocationProvider.kt +35 -0
- package/android/src/main/java/expo/modules/location/next/locationProviders/GmsLocationProvider.kt +188 -0
- package/android/src/main/java/expo/modules/location/next/locationProviders/LocationProvider.kt +78 -0
- package/android/src/main/java/expo/modules/location/services/LocationTaskService.kt +5 -1
- package/android/src/main/java/expo/modules/location/taskConsumers/GeofencingTaskConsumer.kt +11 -1
- package/android/src/main/java/expo/modules/location/taskConsumers/LocationTaskConsumer.kt +11 -3
- package/expo-module.config.json +4 -2
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10-sources.jar.sha512 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.aar.sha512 +1 -0
- 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
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.module.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.module.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.module.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.module.sha512 +1 -0
- 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
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.pom.md5 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.pom.sha1 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.pom.sha256 +1 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.10/expo.modules.location-58.0.10.pom.sha512 +1 -0
- package/package.json +6 -5
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8-sources.jar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar +0 -0
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.aar.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.module.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.module.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.module.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.module.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.pom.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.pom.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.pom.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/58.0.8/expo.modules.location-58.0.8.pom.sha512 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml +0 -13
- package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml.md5 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml.sha1 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml.sha256 +0 -1
- package/local-maven-repo/host/exp/exponent/expo.modules.location/maven-metadata.xml.sha512 +0 -1
- package/prebuilds/output/debug/xcframeworks/ExpoLocation.tar.gz +0 -0
- package/prebuilds/output/release/xcframeworks/ExpoLocation.tar.gz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 58.0.10
|
|
4
4
|
|
|
5
|
-
###
|
|
5
|
+
### Patch Changes
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
11
|
+
## 58.0.9
|
|
10
12
|
|
|
11
|
-
###
|
|
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
|
package/android/build.gradle
CHANGED
|
@@ -4,17 +4,18 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'host.exp.exponent'
|
|
7
|
-
version = '58.0.
|
|
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.
|
|
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,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
|
+
}
|