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 && data.isMockLocation == true) {
130
+ if (rejectMockLocations && isMock) {
121
131
  Log.d(TAG, "Rejecting mock location")
122
132
  return
123
133
  }
@@ -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,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 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+: scan ALL installed packages for OP_MOCK_LOCATION permission.
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 appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
98
- val opMockLocation = 58 // OP_MOCK_LOCATION
99
- val checkOp = AppOpsManager::class.java.getMethod(
100
- "checkOp",
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
- val pm = context.packageManager
107
- @Suppress("DEPRECATION")
108
- val packages = pm.getInstalledApplications(0)
109
- for (appInfo in packages) {
110
- // Skip our own package
111
- if (appInfo.packageName == context.packageName) continue
112
- try {
113
- val result = checkOp.invoke(
114
- appOps, opMockLocation, appInfo.uid, appInfo.packageName
115
- ) as Int
116
- if (result == AppOpsManager.MODE_ALLOWED) {
117
- Log.d(TAG, "Mock location provider detected: ${appInfo.packageName}")
118
- return true
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
- } catch (e: Exception) {
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, "Could not check for mock location apps: ${e.message}")
118
+ Log.w(TAG, "Error checking mock location: ${e.message}")
153
119
  }
120
+ }
154
121
 
155
- 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
+ }
156
129
  }
157
130
 
158
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.18",
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",