react-native-nitro-location-tracking 0.1.17 → 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.
@@ -9,6 +9,14 @@
9
9
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
10
10
  <uses-permission android:name="android.permission.WAKE_LOCK" />
11
11
 
12
+ <!-- Required on Android 11+ (API 30) to query all installed packages
13
+ for mock location detection via AppOpsManager -->
14
+ <queries>
15
+ <intent>
16
+ <action android:name="android.intent.action.MAIN" />
17
+ </intent>
18
+ </queries>
19
+
12
20
  <application>
13
21
  <service
14
22
  android:name=".LocationForegroundService"
@@ -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 && data.isMockLocation == true) {
130
+ if (rejectMockLocations && isMock) {
121
131
  Log.d(TAG, "Rejecting mock location")
122
132
  return
123
133
  }
@@ -164,18 +174,31 @@ class LocationEngine(private val context: Context) {
164
174
  return mockSetting == "1"
165
175
  }
166
176
 
167
- // API 23+: check if any app holds MOCK_LOCATION permission via AppOpsManager
177
+ // API 23+: scan all installed packages for OP_MOCK_LOCATION permission
168
178
  try {
169
179
  val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
170
- // Use reflection to access undocumented OP_MOCK_LOCATION (op code 58)
171
- val opMockLocation = 58
172
- val method = AppOpsManager::class.java.getMethod(
180
+ val opMockLocation = 58 // OP_MOCK_LOCATION
181
+ val checkOp = AppOpsManager::class.java.getMethod(
173
182
  "checkOp", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java
174
183
  )
175
- val result = method.invoke(
176
- appOps, opMockLocation, android.os.Process.myUid(), context.packageName
177
- ) as Int
178
- return result == AppOpsManager.MODE_ALLOWED
184
+
185
+ val pm = context.packageManager
186
+ @Suppress("DEPRECATION")
187
+ val packages = pm.getInstalledApplications(0)
188
+ for (appInfo in packages) {
189
+ if (appInfo.packageName == context.packageName) continue
190
+ try {
191
+ val result = checkOp.invoke(
192
+ appOps, opMockLocation, appInfo.uid, appInfo.packageName
193
+ ) as Int
194
+ if (result == AppOpsManager.MODE_ALLOWED) {
195
+ Log.d(TAG, "Mock location provider detected: ${appInfo.packageName}")
196
+ return true
197
+ }
198
+ } catch (_: Exception) {
199
+ // Some packages may not be queryable
200
+ }
201
+ }
179
202
  } catch (e: Exception) {
180
203
  Log.w(TAG, "Could not check mock location app ops: ${e.message}")
181
204
  }
@@ -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 polls whether a mock/fake GPS provider is active on the device.
14
- * This works independently of location tracking — it detects the presence of
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
- * On each poll it checks:
19
+ * Detection strategy:
18
20
  * - Pre-API 23: Settings.Secure.ALLOW_MOCK_LOCATION
19
- * - API 23+: AppOpsManager OP_MOCK_LOCATION (op code 58)
20
- * - Also checks all installed packages for mock location permission
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
- checkAndNotify()
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
- // Emit current state immediately
49
- val current = isMockLocationActive()
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,75 +66,66 @@ class MockLocationMonitor(private val context: Context) {
66
66
  Log.d(TAG, "Mock location polling stopped")
67
67
  }
68
68
 
69
- private fun checkAndNotify() {
70
- val current = isMockLocationActive()
71
- if (current != lastState) {
72
- lastState = current
73
- Log.d(TAG, "Mock location state changed: isMockEnabled=$current")
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
- * Comprehensive check for mock location activity on the device.
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
- return mockSetting == "1"
86
+ notifyIfChanged(mockSetting == "1")
87
+ return
91
88
  }
92
89
 
93
- // API 23+: check if any app holds MOCK_LOCATION permission via AppOpsManager
90
+ // API 23+: request a current location and check isMock flag
94
91
  try {
95
- val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
96
- val opMockLocation = 58 // OP_MOCK_LOCATION
97
- val method = AppOpsManager::class.java.getMethod(
98
- "checkOp",
99
- Int::class.javaPrimitiveType,
100
- Int::class.javaPrimitiveType,
101
- String::class.java
102
- )
103
- val result = method.invoke(
104
- appOps, opMockLocation, android.os.Process.myUid(), context.packageName
105
- ) as Int
106
- if (result == AppOpsManager.MODE_ALLOWED) return true
107
- } catch (e: Exception) {
108
- Log.w(TAG, "Could not check mock location app ops: ${e.message}")
109
- }
92
+ val request = CurrentLocationRequest.Builder()
93
+ .setPriority(Priority.PRIORITY_HIGH_ACCURACY)
94
+ .setMaxUpdateAgeMillis(5000)
95
+ .build()
110
96
 
111
- // Additional check: scan installed packages for known mock location apps
112
- try {
113
- val pm = context.packageManager
114
- val knownMockApps = listOf(
115
- "com.lexa.fakegps",
116
- "com.incorporateapps.fakegps.fre",
117
- "com.fakegps.mock",
118
- "com.lkr.fakegps",
119
- "com.fake.gps.go.location.spoofer.free",
120
- "com.theappninjas.gpsjoystick",
121
- "com.evezzon.fgl",
122
- "com.mock.location"
123
- )
124
- for (pkg in knownMockApps) {
125
- try {
126
- pm.getApplicationInfo(pkg, 0)
127
- Log.d(TAG, "Known mock location app detected: $pkg")
128
- return true
129
- } catch (_: Exception) {
130
- // Not installed
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
+ }
110
+ }
131
111
  }
132
- }
112
+ .addOnFailureListener { e ->
113
+ Log.w(TAG, "Failed to get current location for mock check: ${e.message}")
114
+ }
115
+ } catch (e: SecurityException) {
116
+ Log.w(TAG, "No location permission for mock check: ${e.message}")
133
117
  } catch (e: Exception) {
134
- Log.w(TAG, "Could not check for mock location apps: ${e.message}")
118
+ Log.w(TAG, "Error checking mock location: ${e.message}")
135
119
  }
120
+ }
136
121
 
137
- return false
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
+ }
138
129
  }
139
130
 
140
131
  fun destroy() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-location-tracking",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "A React Native Nitro module for location tracking",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",