react-native-nitro-location-tracking 0.1.18 → 0.1.19
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.
|
@@ -21,12 +21,14 @@ class LocationEngine(private val context: Context) {
|
|
|
21
21
|
|
|
22
22
|
var onLocation: ((LocationData) -> Unit)? = null
|
|
23
23
|
var onMotionChange: ((Boolean) -> Unit)? = null
|
|
24
|
+
var onMockLocationChanged: ((Boolean) -> Unit)? = null
|
|
24
25
|
var dbWriter: NativeDBWriter? = null
|
|
25
26
|
// var currentRideId: String? = null
|
|
26
27
|
var rejectMockLocations: Boolean = false
|
|
27
28
|
val speedMonitor = SpeedMonitor()
|
|
28
29
|
val tripCalculator = TripCalculator()
|
|
29
30
|
private var lastSpeed = 0f
|
|
31
|
+
private var lastMockState: Boolean? = null
|
|
30
32
|
private var tracking = false
|
|
31
33
|
var lastLocation: Location? = null
|
|
32
34
|
private set
|
|
@@ -116,8 +118,16 @@ class LocationEngine(private val context: Context) {
|
|
|
116
118
|
lastLocation = location
|
|
117
119
|
val data = locationToData(location)
|
|
118
120
|
|
|
121
|
+
// Check mock location state change and notify
|
|
122
|
+
val isMock = data.isMockLocation == true
|
|
123
|
+
if (isMock != lastMockState) {
|
|
124
|
+
lastMockState = isMock
|
|
125
|
+
Log.d(TAG, "Mock location state changed: $isMock")
|
|
126
|
+
onMockLocationChanged?.invoke(isMock)
|
|
127
|
+
}
|
|
128
|
+
|
|
119
129
|
// Skip mock locations if rejection is enabled
|
|
120
|
-
if (rejectMockLocations &&
|
|
130
|
+
if (rejectMockLocations && isMock) {
|
|
121
131
|
Log.d(TAG, "Rejecting mock location")
|
|
122
132
|
return
|
|
123
133
|
}
|
package/android/src/main/java/com/margelo/nitro/nitrolocationtracking/MockLocationMonitor.kt
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
package com.margelo.nitro.nitrolocationtracking
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
-
import android.app.AppOpsManager
|
|
5
4
|
import android.content.Context
|
|
5
|
+
import android.location.Location
|
|
6
6
|
import android.os.Build
|
|
7
7
|
import android.os.Handler
|
|
8
8
|
import android.os.Looper
|
|
9
9
|
import android.provider.Settings
|
|
10
10
|
import android.util.Log
|
|
11
|
+
import com.google.android.gms.location.CurrentLocationRequest
|
|
12
|
+
import com.google.android.gms.location.LocationServices
|
|
13
|
+
import com.google.android.gms.location.Priority
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
|
-
* Periodically
|
|
14
|
-
*
|
|
15
|
-
* mock location apps (e.g. Fake GPS, Mock Locations) at the system level.
|
|
16
|
+
* Periodically checks whether mock/fake GPS is actively being used on the device.
|
|
17
|
+
* Works independently of location tracking — can be used on login/verify screens.
|
|
16
18
|
*
|
|
17
|
-
*
|
|
19
|
+
* Detection strategy:
|
|
18
20
|
* - Pre-API 23: Settings.Secure.ALLOW_MOCK_LOCATION
|
|
19
|
-
* - API 23+:
|
|
20
|
-
*
|
|
21
|
+
* - API 23+: Requests a single location via FusedLocationProviderClient and checks
|
|
22
|
+
* Location.isMock / isFromMockProvider. This is the only reliable way to know
|
|
23
|
+
* if mock location is currently ACTIVE (not just if a mock app is installed).
|
|
21
24
|
*
|
|
22
25
|
* The callback fires only when the state changes (deduplicated).
|
|
23
26
|
*/
|
|
@@ -25,11 +28,11 @@ class MockLocationMonitor(private val context: Context) {
|
|
|
25
28
|
|
|
26
29
|
companion object {
|
|
27
30
|
private const val TAG = "MockLocationMonitor"
|
|
28
|
-
/** Default poll interval in milliseconds */
|
|
29
31
|
private const val DEFAULT_POLL_INTERVAL_MS = 3000L
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
35
|
+
private val fusedClient = LocationServices.getFusedLocationProviderClient(context)
|
|
33
36
|
private var callback: ((Boolean) -> Unit)? = null
|
|
34
37
|
private var lastState: Boolean? = null
|
|
35
38
|
private var polling = false
|
|
@@ -38,18 +41,15 @@ class MockLocationMonitor(private val context: Context) {
|
|
|
38
41
|
private val pollRunnable = object : Runnable {
|
|
39
42
|
override fun run() {
|
|
40
43
|
if (!polling) return
|
|
41
|
-
|
|
44
|
+
checkMockLocation()
|
|
42
45
|
mainHandler.postDelayed(this, pollIntervalMs)
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
fun setCallback(callback: (Boolean) -> Unit) {
|
|
47
50
|
this.callback = callback
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
lastState = current
|
|
51
|
-
callback.invoke(current)
|
|
52
|
-
// Start polling
|
|
51
|
+
// Kick off an immediate check
|
|
52
|
+
checkMockLocation()
|
|
53
53
|
startPolling()
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -66,93 +66,66 @@ class MockLocationMonitor(private val context: Context) {
|
|
|
66
66
|
Log.d(TAG, "Mock location polling stopped")
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
private fun
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
callback?.invoke(current)
|
|
69
|
+
private fun notifyIfChanged(isMock: Boolean) {
|
|
70
|
+
if (isMock != lastState) {
|
|
71
|
+
lastState = isMock
|
|
72
|
+
Log.d(TAG, "Mock location state changed: isMockEnabled=$isMock")
|
|
73
|
+
callback?.invoke(isMock)
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@SuppressLint("DiscouragedPrivateApi")
|
|
82
|
-
fun isMockLocationActive(): Boolean {
|
|
83
|
-
// Pre-API 23: check the global mock location setting
|
|
77
|
+
@SuppressLint("MissingPermission")
|
|
78
|
+
private fun checkMockLocation() {
|
|
79
|
+
// Pre-API 23: use the global settings flag
|
|
84
80
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
85
81
|
@Suppress("DEPRECATION")
|
|
86
82
|
val mockSetting = Settings.Secure.getString(
|
|
87
83
|
context.contentResolver,
|
|
88
84
|
Settings.Secure.ALLOW_MOCK_LOCATION
|
|
89
85
|
)
|
|
90
|
-
|
|
86
|
+
notifyIfChanged(mockSetting == "1")
|
|
87
|
+
return
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
// API 23+:
|
|
94
|
-
// The previous approach only checked our own UID which always returned false
|
|
95
|
-
// since the mock location app (not ours) holds the permission.
|
|
90
|
+
// API 23+: request a current location and check isMock flag
|
|
96
91
|
try {
|
|
97
|
-
val
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Int::class.javaPrimitiveType,
|
|
102
|
-
Int::class.javaPrimitiveType,
|
|
103
|
-
String::class.java
|
|
104
|
-
)
|
|
92
|
+
val request = CurrentLocationRequest.Builder()
|
|
93
|
+
.setPriority(Priority.PRIORITY_HIGH_ACCURACY)
|
|
94
|
+
.setMaxUpdateAgeMillis(5000)
|
|
95
|
+
.build()
|
|
105
96
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
97
|
+
fusedClient.getCurrentLocation(request, null)
|
|
98
|
+
.addOnSuccessListener { location: Location? ->
|
|
99
|
+
if (location != null) {
|
|
100
|
+
val isMock = isLocationMock(location)
|
|
101
|
+
mainHandler.post { notifyIfChanged(isMock) }
|
|
102
|
+
} else {
|
|
103
|
+
// No location available — try last location as fallback
|
|
104
|
+
fusedClient.lastLocation.addOnSuccessListener { lastLoc: Location? ->
|
|
105
|
+
if (lastLoc != null) {
|
|
106
|
+
val isMock = isLocationMock(lastLoc)
|
|
107
|
+
mainHandler.post { notifyIfChanged(isMock) }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
119
110
|
}
|
|
120
|
-
} catch (_: Exception) {
|
|
121
|
-
// Some packages may not be queryable — skip
|
|
122
111
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Log.w(TAG, "Could not check mock location app ops: ${e.message}")
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Fallback: check for known mock location app package names
|
|
129
|
-
try {
|
|
130
|
-
val pm = context.packageManager
|
|
131
|
-
val knownMockApps = listOf(
|
|
132
|
-
"com.lexa.fakegps",
|
|
133
|
-
"com.incorporateapps.fakegps.fre",
|
|
134
|
-
"com.fakegps.mock",
|
|
135
|
-
"com.lkr.fakegps",
|
|
136
|
-
"com.fake.gps.go.location.spoofer.free",
|
|
137
|
-
"com.theappninjas.gpsjoystick",
|
|
138
|
-
"com.evezzon.fgl",
|
|
139
|
-
"com.mock.location"
|
|
140
|
-
)
|
|
141
|
-
for (pkg in knownMockApps) {
|
|
142
|
-
try {
|
|
143
|
-
@Suppress("DEPRECATION")
|
|
144
|
-
pm.getApplicationInfo(pkg, 0)
|
|
145
|
-
Log.d(TAG, "Known mock location app detected: $pkg")
|
|
146
|
-
return true
|
|
147
|
-
} catch (_: Exception) {
|
|
148
|
-
// Not installed
|
|
112
|
+
.addOnFailureListener { e ->
|
|
113
|
+
Log.w(TAG, "Failed to get current location for mock check: ${e.message}")
|
|
149
114
|
}
|
|
150
|
-
|
|
115
|
+
} catch (e: SecurityException) {
|
|
116
|
+
Log.w(TAG, "No location permission for mock check: ${e.message}")
|
|
151
117
|
} catch (e: Exception) {
|
|
152
|
-
Log.w(TAG, "
|
|
118
|
+
Log.w(TAG, "Error checking mock location: ${e.message}")
|
|
153
119
|
}
|
|
120
|
+
}
|
|
154
121
|
|
|
155
|
-
|
|
122
|
+
private fun isLocationMock(location: Location): Boolean {
|
|
123
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
124
|
+
location.isMock
|
|
125
|
+
} else {
|
|
126
|
+
@Suppress("DEPRECATION")
|
|
127
|
+
location.isFromMockProvider
|
|
128
|
+
}
|
|
156
129
|
}
|
|
157
130
|
|
|
158
131
|
fun destroy() {
|
package/package.json
CHANGED