-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathLocationHandler.kt
More file actions
215 lines (192 loc) · 6.86 KB
/
LocationHandler.kt
File metadata and controls
215 lines (192 loc) · 6.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package com.rngooglemapsplus
import android.annotation.SuppressLint
import android.content.Intent
import android.location.Location
import android.os.Build
import android.os.Looper
import android.provider.Settings
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.LocationSettingsRequest
import com.google.android.gms.location.Priority
import com.google.android.gms.maps.LocationSource
import com.rngooglemapsplus.extensions.toLocationErrorCode
private const val REQ_LOCATION_SETTINGS = 2001
private const val PRIORITY_DEFAULT = Priority.PRIORITY_BALANCED_POWER_ACCURACY
private const val INTERVAL_DEFAULT = 600000L
private const val MIN_UPDATE_INTERVAL = 3600000L
class LocationHandler(
val context: ReactContext,
) : LocationSource {
private val fusedLocationClientProviderClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private var listener: LocationSource.OnLocationChangedListener? = null
private var locationRequest: LocationRequest? = null
private var locationCallback: LocationCallback? = null
private var lastLocation: Location? = null
private var isActive = false
private var priority: Int = PRIORITY_DEFAULT
private var interval: Long = INTERVAL_DEFAULT
private var minUpdateInterval: Long = MIN_UPDATE_INTERVAL
var onUpdate: ((Location) -> Unit)? = null
var onError: ((RNLocationErrorCode) -> Unit)? = null
init {
buildLocationRequest(priority, interval, minUpdateInterval)
}
fun updateConfig(
priority: Int? = null,
interval: Long? = null,
minUpdateInterval: Long? = null,
) {
this.priority = priority ?: PRIORITY_DEFAULT
this.interval = interval ?: INTERVAL_DEFAULT
this.minUpdateInterval = minUpdateInterval ?: MIN_UPDATE_INTERVAL
buildLocationRequest(this.priority, this.interval, this.minUpdateInterval)
if (!isActive) return
stop()
start()
}
fun showLocationDialog() {
onUi {
val activity = context.currentActivity ?: run { return@onUi }
val lr =
if (Build.VERSION.SDK_INT >= 31) {
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10_000L).build()
} else {
@Suppress("DEPRECATION")
LocationRequest.create().apply { priority = Priority.PRIORITY_HIGH_ACCURACY }
}
val req =
LocationSettingsRequest
.Builder()
.addLocationRequest(lr)
.setAlwaysShow(true)
.build()
val settingsClient = LocationServices.getSettingsClient(activity)
settingsClient
.checkLocationSettings(req)
.addOnSuccessListener {
}.addOnFailureListener { ex ->
if (ex is ResolvableApiException) {
try {
ex.startResolutionForResult(activity, REQ_LOCATION_SETTINGS)
} catch (_: Exception) {
onError?.invoke(RNLocationErrorCode.SETTINGS_NOT_SATISFIED)
}
} else {
onError?.invoke(RNLocationErrorCode.SETTINGS_NOT_SATISFIED)
openLocationSettings()
}
}
}
}
fun openLocationSettings() {
UiThreadUtil.runOnUiThread {
val intent =
Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.currentActivity?.startActivity(intent)
}
}
@Suppress("deprecation")
private fun buildLocationRequest(
priority: Int,
interval: Long,
minUpdateInterval: Long,
) {
locationRequest =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
LocationRequest
.Builder(priority, interval)
.setMinUpdateIntervalMillis(minUpdateInterval)
.build()
} else {
LocationRequest
.create()
.setPriority(priority)
.setInterval(interval)
.setFastestInterval(minUpdateInterval)
}
}
private fun isNewerLocation(location: Location): Boolean {
val prev = lastLocation ?: return true
return location.elapsedRealtimeNanos > prev.elapsedRealtimeNanos
}
private fun notifyListener(location: Location) {
lastLocation = location
listener?.onLocationChanged(location)
onUpdate?.invoke(location)
}
@SuppressLint("MissingPermission")
fun start() {
if (isActive) return
isActive = true
val playServicesStatus =
GoogleApiAvailability
.getInstance()
.isGooglePlayServicesAvailable(context)
if (playServicesStatus != ConnectionResult.SUCCESS) {
onError?.invoke(RNLocationErrorCode.PLAY_SERVICE_NOT_AVAILABLE)
return
}
try {
fusedLocationClientProviderClient
.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
.addOnSuccessListener { location ->
if (location == null) return@addOnSuccessListener
if (!isNewerLocation(location)) return@addOnSuccessListener
notifyListener(location)
}.addOnFailureListener { e ->
onError?.invoke(e.toLocationErrorCode(context))
}
locationCallback =
object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
val location = locationResult.lastLocation
if (location != null) {
if (!isNewerLocation(location)) return
notifyListener(location)
} else {
onError?.invoke(RNLocationErrorCode.POSITION_UNAVAILABLE)
}
}
}
val req = locationRequest ?: return
val callback = locationCallback ?: return
fusedLocationClientProviderClient
.requestLocationUpdates(req, callback, Looper.getMainLooper())
.addOnFailureListener { e ->
onError?.invoke(e.toLocationErrorCode(context))
}
} catch (_: SecurityException) {
onError?.invoke(RNLocationErrorCode.PERMISSION_DENIED)
} catch (ex: Exception) {
val error = ex.toLocationErrorCode(context)
onError?.invoke(error)
}
}
fun stop() {
if (!isActive) return
isActive = false
val callback = locationCallback ?: return
fusedLocationClientProviderClient.removeLocationUpdates(callback)
fusedLocationClientProviderClient.flushLocations()
locationCallback = null
}
override fun activate(listener: LocationSource.OnLocationChangedListener) {
this.listener = listener
lastLocation?.let {
listener.onLocationChanged(it)
}
}
override fun deactivate() {
listener = null
}
}