react-native-nitro-location-tracking 0.1.10 → 0.1.11
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/android/src/main/java/com/margelo/nitro/nitrolocationtracking/PermissionStatusMonitor.kt
CHANGED
|
@@ -3,6 +3,8 @@ package com.margelo.nitro.nitrolocationtracking
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.content.pm.PackageManager
|
|
5
5
|
import android.os.Build
|
|
6
|
+
import android.os.Handler
|
|
7
|
+
import android.os.Looper
|
|
6
8
|
import android.util.Log
|
|
7
9
|
import androidx.core.content.ContextCompat
|
|
8
10
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
@@ -24,6 +26,7 @@ class PermissionStatusMonitor(private val context: Context) {
|
|
|
24
26
|
|
|
25
27
|
private var callback: ((PermissionStatus) -> Unit)? = null
|
|
26
28
|
private var lastStatus: PermissionStatus? = null
|
|
29
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
27
30
|
|
|
28
31
|
private val lifecycleObserver = object : DefaultLifecycleObserver {
|
|
29
32
|
override fun onStart(owner: LifecycleOwner) {
|
|
@@ -36,12 +39,14 @@ class PermissionStatusMonitor(private val context: Context) {
|
|
|
36
39
|
// Capture the current status so we only fire on actual changes
|
|
37
40
|
lastStatus = getCurrentPermissionStatus()
|
|
38
41
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// addObserver MUST be called on the main thread
|
|
43
|
+
mainHandler.post {
|
|
44
|
+
try {
|
|
45
|
+
ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)
|
|
46
|
+
Log.d(TAG, "Registered lifecycle observer for permission changes")
|
|
47
|
+
} catch (e: Exception) {
|
|
48
|
+
Log.e(TAG, "Failed to register lifecycle observer: ${e.message}")
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -76,9 +81,11 @@ class PermissionStatusMonitor(private val context: Context) {
|
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
fun destroy() {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
mainHandler.post {
|
|
85
|
+
try {
|
|
86
|
+
ProcessLifecycleOwner.get().lifecycle.removeObserver(lifecycleObserver)
|
|
87
|
+
} catch (_: Exception) {}
|
|
88
|
+
}
|
|
82
89
|
callback = null
|
|
83
90
|
lastStatus = null
|
|
84
91
|
}
|
package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/ProviderStatusMonitor.kt
CHANGED
|
@@ -1,27 +1,54 @@
|
|
|
1
1
|
package com.margelo.nitro.nitrolocationtracking
|
|
2
2
|
|
|
3
|
-
import android.content.BroadcastReceiver
|
|
4
3
|
import android.content.Context
|
|
5
|
-
import android.
|
|
6
|
-
import android.content.IntentFilter
|
|
4
|
+
import android.database.ContentObserver
|
|
7
5
|
import android.location.LocationManager
|
|
8
6
|
import android.os.Build
|
|
7
|
+
import android.os.Handler
|
|
8
|
+
import android.os.Looper
|
|
9
|
+
import android.provider.Settings
|
|
9
10
|
import android.util.Log
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Monitors GPS / network location provider status changes using a ContentObserver
|
|
14
|
+
* on Settings.Secure.LOCATION_MODE.
|
|
15
|
+
*
|
|
16
|
+
* Why ContentObserver instead of BroadcastReceiver?
|
|
17
|
+
* Several OEMs (notably Samsung) suppress PROVIDERS_CHANGED_ACTION broadcasts,
|
|
18
|
+
* making BroadcastReceiver unreliable. ContentObserver watches the Settings DB
|
|
19
|
+
* directly and fires on all devices.
|
|
20
|
+
*
|
|
21
|
+
* Implementation notes:
|
|
22
|
+
* - ContentObserver.onChange fires BEFORE the LocationManager reflects the new
|
|
23
|
+
* state, so we post a short delayed read (150 ms) to get the correct values.
|
|
24
|
+
* - onChange can fire multiple times per toggle; we debounce with a pending
|
|
25
|
+
* Runnable and deduplicate by comparing with lastGps / lastNetwork.
|
|
26
|
+
*/
|
|
11
27
|
class ProviderStatusMonitor(private val context: Context) {
|
|
12
28
|
|
|
13
29
|
companion object {
|
|
14
30
|
private const val TAG = "ProviderStatusMonitor"
|
|
31
|
+
/** Delay (ms) to let the system apply the setting before we read it. */
|
|
32
|
+
private const val READ_DELAY_MS = 150L
|
|
15
33
|
}
|
|
16
34
|
|
|
17
35
|
private val locationManager =
|
|
18
36
|
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
|
37
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
38
|
+
|
|
19
39
|
private var callback: ((LocationProviderStatus, LocationProviderStatus) -> Unit)? = null
|
|
20
|
-
private var
|
|
40
|
+
private var contentObserver: ContentObserver? = null
|
|
41
|
+
private var pendingNotify: Runnable? = null
|
|
42
|
+
|
|
43
|
+
// Track last-notified values to avoid duplicate callbacks
|
|
44
|
+
private var lastGps: LocationProviderStatus? = null
|
|
45
|
+
private var lastNetwork: LocationProviderStatus? = null
|
|
21
46
|
|
|
22
47
|
fun setCallback(callback: (LocationProviderStatus, LocationProviderStatus) -> Unit) {
|
|
23
48
|
this.callback = callback
|
|
24
|
-
|
|
49
|
+
registerObserver()
|
|
50
|
+
// Emit current status immediately (no delay needed — values are already settled)
|
|
51
|
+
emitCurrentStatus()
|
|
25
52
|
}
|
|
26
53
|
|
|
27
54
|
fun isLocationServicesEnabled(): Boolean {
|
|
@@ -33,41 +60,78 @@ class ProviderStatusMonitor(private val context: Context) {
|
|
|
33
60
|
}
|
|
34
61
|
}
|
|
35
62
|
|
|
36
|
-
|
|
37
|
-
if (receiver != null) return
|
|
63
|
+
// ── Observer registration ────────────────────────────────────────────
|
|
38
64
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
private fun registerObserver() {
|
|
66
|
+
if (contentObserver != null) return
|
|
67
|
+
|
|
68
|
+
contentObserver = object : ContentObserver(mainHandler) {
|
|
69
|
+
override fun onChange(selfChange: Boolean) {
|
|
70
|
+
super.onChange(selfChange)
|
|
71
|
+
scheduleNotify()
|
|
44
72
|
}
|
|
45
73
|
}
|
|
46
74
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
75
|
+
// LOCATION_MODE is the single authoritative setting for the global
|
|
76
|
+
// location toggle (GPS + Network). Watching only this avoids the
|
|
77
|
+
// double-fire that occurs when observing LOCATION_PROVIDERS_ALLOWED too.
|
|
78
|
+
context.contentResolver.registerContentObserver(
|
|
79
|
+
Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE),
|
|
80
|
+
false,
|
|
81
|
+
contentObserver!!
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
Log.d(TAG, "ContentObserver registered")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Debounced + delayed notification ─────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Schedule a delayed read of the provider status.
|
|
91
|
+
* If onChange fires multiple times in quick succession, the previous
|
|
92
|
+
* pending Runnable is cancelled so we only read once after things settle.
|
|
93
|
+
*/
|
|
94
|
+
private fun scheduleNotify() {
|
|
95
|
+
pendingNotify?.let { mainHandler.removeCallbacks(it) }
|
|
96
|
+
|
|
97
|
+
val runnable = Runnable { emitCurrentStatus() }
|
|
98
|
+
pendingNotify = runnable
|
|
99
|
+
mainHandler.postDelayed(runnable, READ_DELAY_MS)
|
|
54
100
|
}
|
|
55
101
|
|
|
56
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Read the current GPS + Network provider state and invoke the callback
|
|
104
|
+
* only if the values actually changed since the last notification.
|
|
105
|
+
*/
|
|
106
|
+
private fun emitCurrentStatus() {
|
|
57
107
|
val gps = if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))
|
|
58
108
|
LocationProviderStatus.ENABLED else LocationProviderStatus.DISABLED
|
|
59
109
|
val network = if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
|
|
60
110
|
LocationProviderStatus.ENABLED else LocationProviderStatus.DISABLED
|
|
111
|
+
|
|
112
|
+
// Deduplicate — only fire if something actually changed
|
|
113
|
+
if (gps == lastGps && network == lastNetwork) return
|
|
114
|
+
|
|
115
|
+
lastGps = gps
|
|
116
|
+
lastNetwork = network
|
|
117
|
+
Log.d(TAG, "Provider status changed: GPS=$gps, Network=$network")
|
|
61
118
|
callback?.invoke(gps, network)
|
|
62
119
|
}
|
|
63
120
|
|
|
121
|
+
// ── Cleanup ──────────────────────────────────────────────────────────
|
|
122
|
+
|
|
64
123
|
fun destroy() {
|
|
65
|
-
|
|
124
|
+
pendingNotify?.let { mainHandler.removeCallbacks(it) }
|
|
125
|
+
pendingNotify = null
|
|
126
|
+
|
|
127
|
+
contentObserver?.let {
|
|
66
128
|
try {
|
|
67
|
-
context.
|
|
129
|
+
context.contentResolver.unregisterContentObserver(it)
|
|
68
130
|
} catch (_: Exception) {}
|
|
69
131
|
}
|
|
70
|
-
|
|
132
|
+
contentObserver = null
|
|
71
133
|
callback = null
|
|
134
|
+
lastGps = null
|
|
135
|
+
lastNetwork = null
|
|
72
136
|
}
|
|
73
137
|
}
|
package/ios/LocationEngine.swift
CHANGED
|
@@ -21,8 +21,31 @@ class LocationEngine: NSObject, CLLocationManagerDelegate {
|
|
|
21
21
|
var rejectMockLocations: Bool = false
|
|
22
22
|
let speedMonitor = SpeedMonitor()
|
|
23
23
|
let tripCalculator = TripCalculator()
|
|
24
|
-
var providerStatusCallback: ((LocationProviderStatus, LocationProviderStatus) -> Void)?
|
|
25
|
-
|
|
24
|
+
var providerStatusCallback: ((LocationProviderStatus, LocationProviderStatus) -> Void)? {
|
|
25
|
+
didSet {
|
|
26
|
+
// Emit current status immediately when the callback is first set
|
|
27
|
+
if providerStatusCallback != nil {
|
|
28
|
+
let enabled = CLLocationManager.locationServicesEnabled()
|
|
29
|
+
let status: LocationProviderStatus = enabled ? .enabled : .disabled
|
|
30
|
+
lastProviderEnabled = enabled
|
|
31
|
+
providerStatusCallback?(status, status)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
var permissionStatusCallback: ((PermissionStatus) -> Void)? {
|
|
36
|
+
didSet {
|
|
37
|
+
// Emit current status immediately when the callback is first set
|
|
38
|
+
if permissionStatusCallback != nil {
|
|
39
|
+
let current = Self.mapAuthStatus(CLLocationManager.authorizationStatus())
|
|
40
|
+
lastPermissionStatus = current
|
|
41
|
+
permissionStatusCallback?(current)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Deduplication: track last-notified values
|
|
47
|
+
private var lastPermissionStatus: PermissionStatus?
|
|
48
|
+
private var lastProviderEnabled: Bool?
|
|
26
49
|
|
|
27
50
|
/// The most recently received location from Core Location (for distance calculations)
|
|
28
51
|
var lastCLLocation: CLLocation? {
|
|
@@ -193,6 +216,8 @@ class LocationEngine: NSObject, CLLocationManagerDelegate {
|
|
|
193
216
|
return CLLocationManager.locationServicesEnabled()
|
|
194
217
|
}
|
|
195
218
|
|
|
219
|
+
// MARK: - Authorization / Provider change delegate
|
|
220
|
+
|
|
196
221
|
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
197
222
|
guard manager === locationManager else { return }
|
|
198
223
|
|
|
@@ -213,27 +238,39 @@ class LocationEngine: NSObject, CLLocationManagerDelegate {
|
|
|
213
238
|
}
|
|
214
239
|
}
|
|
215
240
|
|
|
216
|
-
// Notify JS about permission status change
|
|
217
|
-
let permStatus
|
|
218
|
-
|
|
241
|
+
// Notify JS about permission status change (deduplicated)
|
|
242
|
+
let permStatus = Self.mapAuthStatus(authStatus)
|
|
243
|
+
if permStatus != lastPermissionStatus {
|
|
244
|
+
lastPermissionStatus = permStatus
|
|
245
|
+
permissionStatusCallback?(permStatus)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Notify JS about provider status change (deduplicated)
|
|
249
|
+
let enabled = CLLocationManager.locationServicesEnabled()
|
|
250
|
+
if enabled != lastProviderEnabled {
|
|
251
|
+
lastProviderEnabled = enabled
|
|
252
|
+
let status: LocationProviderStatus = enabled ? .enabled : .disabled
|
|
253
|
+
providerStatusCallback?(status, status)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// MARK: - Helpers
|
|
258
|
+
|
|
259
|
+
private static func mapAuthStatus(_ status: CLAuthorizationStatus) -> PermissionStatus {
|
|
260
|
+
switch status {
|
|
219
261
|
case .notDetermined:
|
|
220
|
-
|
|
262
|
+
return .notdetermined
|
|
221
263
|
case .restricted:
|
|
222
|
-
|
|
264
|
+
return .restricted
|
|
223
265
|
case .denied:
|
|
224
|
-
|
|
266
|
+
return .denied
|
|
225
267
|
case .authorizedWhenInUse:
|
|
226
|
-
|
|
268
|
+
return .wheninuse
|
|
227
269
|
case .authorizedAlways:
|
|
228
|
-
|
|
270
|
+
return .always
|
|
229
271
|
@unknown default:
|
|
230
|
-
|
|
272
|
+
return .notdetermined
|
|
231
273
|
}
|
|
232
|
-
permissionStatusCallback?(permStatus)
|
|
233
|
-
|
|
234
|
-
let enabled = CLLocationManager.locationServicesEnabled()
|
|
235
|
-
let status: LocationProviderStatus = enabled ? .enabled : .disabled
|
|
236
|
-
providerStatusCallback?(status, status)
|
|
237
274
|
}
|
|
238
275
|
}
|
|
239
276
|
|
package/package.json
CHANGED