react-native-nitro-geolocation 1.2.6 → 1.3.1

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.
Files changed (223) hide show
  1. package/README.md +61 -272
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/AndroidManifest.xml +39 -0
  4. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidFusedLocationProvider.kt +310 -0
  5. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidFusedRequestFactory.kt +31 -0
  6. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidGeolocationConversions.kt +71 -0
  7. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidGeolocationErrors.kt +85 -0
  8. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidGeolocationOptions.kt +92 -0
  9. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidLocationSettings.kt +3 -0
  10. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/AndroidProviderRoute.kt +74 -0
  11. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroBackgroundLocation.kt +132 -0
  12. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/NitroGeolocation.kt +219 -476
  13. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/AndroidBackgroundHttpSync.kt +134 -0
  14. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/AndroidBackgroundPermissions.kt +196 -0
  15. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/AndroidBackgroundSerialization.kt +266 -0
  16. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroActivityRecognitionReceiver.kt +13 -0
  17. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundEventHub.kt +65 -0
  18. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundHeadlessTaskService.kt +58 -0
  19. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundLocationController.kt +711 -0
  20. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundLocationService.kt +48 -0
  21. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundNotificationFactory.kt +37 -0
  22. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBackgroundStore.kt +655 -0
  23. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroBootReceiver.kt +26 -0
  24. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroGeofenceReceiver.kt +14 -0
  25. package/android/src/main/java/com/margelo/nitro/nitrogeolocation/background/NitroLocationUpdateReceiver.kt +37 -0
  26. package/android/src/test/java/com/margelo/nitro/nitrogeolocation/AndroidFusedLocationProviderTest.kt +64 -0
  27. package/android/src/test/java/com/margelo/nitro/nitrogeolocation/AndroidProviderRouteTest.kt +116 -0
  28. package/ios/IOSBackgroundHttpSync.swift +126 -0
  29. package/ios/IOSBackgroundLocationDelegate.swift +36 -0
  30. package/ios/IOSBackgroundMotion.swift +55 -0
  31. package/ios/IOSBackgroundSerialization.swift +539 -0
  32. package/ios/IOSGeolocationConversions.swift +131 -0
  33. package/ios/IOSGeolocationDelegate.swift +31 -0
  34. package/ios/IOSGeolocationErrors.swift +39 -0
  35. package/ios/IOSGeolocationOptions.swift +135 -0
  36. package/ios/NitroBackgroundLocation.swift +849 -0
  37. package/ios/NitroGeolocation.swift +14 -347
  38. package/nitrogen/generated/android/c++/JActivityRecognitionOptions.hpp +69 -0
  39. package/nitrogen/generated/android/c++/JAndroidBackgroundLocationOptions.hpp +74 -0
  40. package/nitrogen/generated/android/c++/JAndroidBackgroundLocationStatus.hpp +67 -0
  41. package/nitrogen/generated/android/c++/JAndroidBackgroundProvider.hpp +61 -0
  42. package/nitrogen/generated/android/c++/JAndroidForegroundServiceOptions.hpp +90 -0
  43. package/nitrogen/generated/android/c++/JBackgroundEventEnvelope.hpp +129 -0
  44. package/nitrogen/generated/android/c++/JBackgroundEventType.hpp +70 -0
  45. package/nitrogen/generated/android/c++/JBackgroundHttpMethod.hpp +61 -0
  46. package/nitrogen/generated/android/c++/JBackgroundHttpSyncOptions.hpp +131 -0
  47. package/nitrogen/generated/android/c++/JBackgroundHttpSyncResult.hpp +111 -0
  48. package/nitrogen/generated/android/c++/JBackgroundLocation.hpp +110 -0
  49. package/nitrogen/generated/android/c++/JBackgroundLocationOptions.hpp +162 -0
  50. package/nitrogen/generated/android/c++/JBackgroundLocationSource.hpp +73 -0
  51. package/nitrogen/generated/android/c++/JBackgroundLocationState.hpp +70 -0
  52. package/nitrogen/generated/android/c++/JBackgroundLocationStatus.hpp +126 -0
  53. package/nitrogen/generated/android/c++/JBackgroundPermissionResult.hpp +79 -0
  54. package/nitrogen/generated/android/c++/JBackgroundPermissionStatus.hpp +64 -0
  55. package/nitrogen/generated/android/c++/JBackgroundTrackingMode.hpp +61 -0
  56. package/nitrogen/generated/android/c++/JBatterySnapshot.hpp +61 -0
  57. package/nitrogen/generated/android/c++/JDetectedActivity.hpp +66 -0
  58. package/nitrogen/generated/android/c++/JDetectedActivityType.hpp +76 -0
  59. package/nitrogen/generated/android/c++/JFunc_void_BackgroundEventEnvelope.hpp +114 -0
  60. package/nitrogen/generated/android/c++/JFunc_void_BackgroundLocation.hpp +95 -0
  61. package/nitrogen/generated/android/c++/JGeofenceEvent.hpp +94 -0
  62. package/nitrogen/generated/android/c++/JGeofenceRegion.hpp +112 -0
  63. package/nitrogen/generated/android/c++/JGeofenceTransition.hpp +61 -0
  64. package/nitrogen/generated/android/c++/JGeofencingOptions.hpp +82 -0
  65. package/nitrogen/generated/android/c++/JGetStoredBackgroundEventsOptions.hpp +90 -0
  66. package/nitrogen/generated/android/c++/JGetStoredBackgroundLocationsOptions.hpp +69 -0
  67. package/nitrogen/generated/android/c++/JHybridNitroBackgroundLocationSpec.cpp +662 -0
  68. package/nitrogen/generated/android/c++/JHybridNitroBackgroundLocationSpec.hpp +89 -0
  69. package/nitrogen/generated/android/c++/JIOSBackgroundActivityType.hpp +67 -0
  70. package/nitrogen/generated/android/c++/JIOSBackgroundLocationOptions.hpp +79 -0
  71. package/nitrogen/generated/android/c++/JIOSBackgroundLocationStatus.hpp +61 -0
  72. package/nitrogen/generated/android/c++/JLocationProviderStatus.hpp +5 -1
  73. package/nitrogen/generated/android/c++/JStoredBackgroundEventEnvelope.hpp +115 -0
  74. package/nitrogen/generated/android/c++/JStoredBackgroundLocation.hpp +122 -0
  75. package/nitrogen/generated/android/c++/JVariant_NullType_Boolean_String_Double.cpp +34 -0
  76. package/nitrogen/generated/android/c++/JVariant_NullType_Boolean_String_Double.hpp +100 -0
  77. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/ActivityRecognitionOptions.kt +66 -0
  78. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidBackgroundLocationOptions.kt +66 -0
  79. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidBackgroundLocationStatus.kt +61 -0
  80. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidBackgroundProvider.kt +24 -0
  81. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/AndroidForegroundServiceOptions.kt +91 -0
  82. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundEventEnvelope.kt +96 -0
  83. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundEventType.kt +27 -0
  84. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundHttpMethod.kt +24 -0
  85. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundHttpSyncOptions.kt +101 -0
  86. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundHttpSyncResult.kt +71 -0
  87. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocation.kt +96 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocationOptions.kt +136 -0
  89. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocationSource.kt +28 -0
  90. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocationState.kt +27 -0
  91. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundLocationStatus.kt +116 -0
  92. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundPermissionResult.kt +71 -0
  93. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundPermissionStatus.kt +25 -0
  94. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BackgroundTrackingMode.kt +24 -0
  95. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/BatterySnapshot.kt +56 -0
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/DetectedActivity.kt +61 -0
  97. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/DetectedActivityType.kt +29 -0
  98. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_BackgroundEventEnvelope.kt +80 -0
  99. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Func_void_BackgroundLocation.kt +80 -0
  100. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeofenceEvent.kt +66 -0
  101. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeofenceRegion.kt +96 -0
  102. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeofenceTransition.kt +24 -0
  103. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GeofencingOptions.kt +56 -0
  104. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GetStoredBackgroundEventsOptions.kt +66 -0
  105. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/GetStoredBackgroundLocationsOptions.kt +66 -0
  106. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/HybridNitroBackgroundLocationSpec.kt +174 -0
  107. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSBackgroundActivityType.kt +26 -0
  108. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSBackgroundLocationOptions.kt +76 -0
  109. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/IOSBackgroundLocationStatus.kt +56 -0
  110. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/LocationProviderStatus.kt +7 -2
  111. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/StoredBackgroundEventEnvelope.kt +76 -0
  112. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/StoredBackgroundLocation.kt +111 -0
  113. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrogeolocation/Variant_NullType_Boolean_String_Double.kt +88 -0
  114. package/nitrogen/generated/android/nitrogeolocation+autolinking.cmake +3 -0
  115. package/nitrogen/generated/android/nitrogeolocationOnLoad.cpp +20 -0
  116. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.cpp +104 -15
  117. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Bridge.hpp +1107 -50
  118. package/nitrogen/generated/ios/NitroGeolocation-Swift-Cxx-Umbrella.hpp +102 -0
  119. package/nitrogen/generated/ios/NitroGeolocationAutolinking.mm +8 -0
  120. package/nitrogen/generated/ios/NitroGeolocationAutolinking.swift +15 -3
  121. package/nitrogen/generated/ios/c++/HybridNitroBackgroundLocationSpecSwift.cpp +11 -0
  122. package/nitrogen/generated/ios/c++/HybridNitroBackgroundLocationSpecSwift.hpp +416 -0
  123. package/nitrogen/generated/ios/swift/ActivityRecognitionOptions.swift +96 -0
  124. package/nitrogen/generated/ios/swift/AndroidBackgroundLocationOptions.swift +76 -0
  125. package/nitrogen/generated/ios/swift/AndroidBackgroundLocationStatus.swift +58 -0
  126. package/nitrogen/generated/ios/swift/AndroidBackgroundProvider.swift +44 -0
  127. package/nitrogen/generated/ios/swift/AndroidForegroundServiceOptions.swift +160 -0
  128. package/nitrogen/generated/ios/swift/BackgroundEventEnvelope.swift +110 -0
  129. package/nitrogen/generated/ios/swift/BackgroundEventType.swift +56 -0
  130. package/nitrogen/generated/ios/swift/BackgroundHttpMethod.swift +44 -0
  131. package/nitrogen/generated/ios/swift/BackgroundHttpSyncOptions.swift +259 -0
  132. package/nitrogen/generated/ios/swift/BackgroundHttpSyncResult.swift +87 -0
  133. package/nitrogen/generated/ios/swift/BackgroundLocation.swift +118 -0
  134. package/nitrogen/generated/ios/swift/BackgroundLocationOptions.swift +292 -0
  135. package/nitrogen/generated/ios/swift/BackgroundLocationSource.swift +60 -0
  136. package/nitrogen/generated/ios/swift/BackgroundLocationState.swift +56 -0
  137. package/nitrogen/generated/ios/swift/BackgroundLocationStatus.swift +124 -0
  138. package/nitrogen/generated/ios/swift/BackgroundPermissionResult.swift +81 -0
  139. package/nitrogen/generated/ios/swift/BackgroundPermissionStatus.swift +48 -0
  140. package/nitrogen/generated/ios/swift/BackgroundTrackingMode.swift +44 -0
  141. package/nitrogen/generated/ios/swift/BatterySnapshot.swift +60 -0
  142. package/nitrogen/generated/ios/swift/DetectedActivity.swift +39 -0
  143. package/nitrogen/generated/ios/swift/DetectedActivityType.swift +64 -0
  144. package/nitrogen/generated/ios/swift/Func_void_BackgroundEventEnvelope.swift +46 -0
  145. package/nitrogen/generated/ios/swift/Func_void_BackgroundHttpSyncResult.swift +46 -0
  146. package/nitrogen/generated/ios/swift/Func_void_BackgroundLocation.swift +46 -0
  147. package/nitrogen/generated/ios/swift/Func_void_BackgroundLocationStatus.swift +46 -0
  148. package/nitrogen/generated/ios/swift/Func_void_BackgroundPermissionResult.swift +46 -0
  149. package/nitrogen/generated/ios/swift/Func_void_std__optional_BackgroundLocationOptions_.swift +46 -0
  150. package/nitrogen/generated/ios/swift/Func_void_std__vector_GeofenceRegion_.swift +46 -0
  151. package/nitrogen/generated/ios/swift/Func_void_std__vector_StoredBackgroundEventEnvelope_.swift +46 -0
  152. package/nitrogen/generated/ios/swift/Func_void_std__vector_StoredBackgroundLocation_.swift +46 -0
  153. package/nitrogen/generated/ios/swift/GeofenceEvent.swift +50 -0
  154. package/nitrogen/generated/ios/swift/GeofenceRegion.swift +195 -0
  155. package/nitrogen/generated/ios/swift/GeofenceTransition.swift +44 -0
  156. package/nitrogen/generated/ios/swift/GeofencingOptions.swift +66 -0
  157. package/nitrogen/generated/ios/swift/GetStoredBackgroundEventsOptions.swift +102 -0
  158. package/nitrogen/generated/ios/swift/GetStoredBackgroundLocationsOptions.swift +96 -0
  159. package/nitrogen/generated/ios/swift/HybridNitroBackgroundLocationSpec.swift +81 -0
  160. package/nitrogen/generated/ios/swift/HybridNitroBackgroundLocationSpec_cxx.swift +654 -0
  161. package/nitrogen/generated/ios/swift/IOSBackgroundActivityType.swift +52 -0
  162. package/nitrogen/generated/ios/swift/IOSBackgroundLocationOptions.swift +125 -0
  163. package/nitrogen/generated/ios/swift/IOSBackgroundLocationStatus.swift +34 -0
  164. package/nitrogen/generated/ios/swift/LocationProviderStatus.swift +19 -1
  165. package/nitrogen/generated/ios/swift/StoredBackgroundEventEnvelope.swift +54 -0
  166. package/nitrogen/generated/ios/swift/StoredBackgroundLocation.swift +120 -0
  167. package/nitrogen/generated/ios/swift/Variant_NullType_Bool_String_Double.swift +34 -0
  168. package/nitrogen/generated/shared/c++/ActivityRecognitionOptions.hpp +95 -0
  169. package/nitrogen/generated/shared/c++/AndroidBackgroundLocationOptions.hpp +100 -0
  170. package/nitrogen/generated/shared/c++/AndroidBackgroundLocationStatus.hpp +93 -0
  171. package/nitrogen/generated/shared/c++/AndroidBackgroundProvider.hpp +80 -0
  172. package/nitrogen/generated/shared/c++/AndroidForegroundServiceOptions.hpp +116 -0
  173. package/nitrogen/generated/shared/c++/BackgroundEventEnvelope.hpp +140 -0
  174. package/nitrogen/generated/shared/c++/BackgroundEventType.hpp +92 -0
  175. package/nitrogen/generated/shared/c++/BackgroundHttpMethod.hpp +80 -0
  176. package/nitrogen/generated/shared/c++/BackgroundHttpSyncOptions.hpp +129 -0
  177. package/nitrogen/generated/shared/c++/BackgroundHttpSyncResult.hpp +101 -0
  178. package/nitrogen/generated/shared/c++/BackgroundLocation.hpp +134 -0
  179. package/nitrogen/generated/shared/c++/BackgroundLocationOptions.hpp +174 -0
  180. package/nitrogen/generated/shared/c++/BackgroundLocationSource.hpp +96 -0
  181. package/nitrogen/generated/shared/c++/BackgroundLocationState.hpp +92 -0
  182. package/nitrogen/generated/shared/c++/BackgroundLocationStatus.hpp +158 -0
  183. package/nitrogen/generated/shared/c++/BackgroundPermissionResult.hpp +107 -0
  184. package/nitrogen/generated/shared/c++/BackgroundPermissionStatus.hpp +84 -0
  185. package/nitrogen/generated/shared/c++/BackgroundTrackingMode.hpp +80 -0
  186. package/nitrogen/generated/shared/c++/BatterySnapshot.hpp +87 -0
  187. package/nitrogen/generated/shared/c++/DetectedActivity.hpp +92 -0
  188. package/nitrogen/generated/shared/c++/DetectedActivityType.hpp +100 -0
  189. package/nitrogen/generated/shared/c++/GeofenceEvent.hpp +103 -0
  190. package/nitrogen/generated/shared/c++/GeofenceRegion.hpp +123 -0
  191. package/nitrogen/generated/shared/c++/GeofenceTransition.hpp +80 -0
  192. package/nitrogen/generated/shared/c++/GeofencingOptions.hpp +90 -0
  193. package/nitrogen/generated/shared/c++/GetStoredBackgroundEventsOptions.hpp +98 -0
  194. package/nitrogen/generated/shared/c++/GetStoredBackgroundLocationsOptions.hpp +95 -0
  195. package/nitrogen/generated/shared/c++/HybridNitroBackgroundLocationSpec.cpp +47 -0
  196. package/nitrogen/generated/shared/c++/HybridNitroBackgroundLocationSpec.hpp +133 -0
  197. package/nitrogen/generated/shared/c++/IOSBackgroundActivityType.hpp +88 -0
  198. package/nitrogen/generated/shared/c++/IOSBackgroundLocationOptions.hpp +105 -0
  199. package/nitrogen/generated/shared/c++/IOSBackgroundLocationStatus.hpp +87 -0
  200. package/nitrogen/generated/shared/c++/LocationProviderStatus.hpp +5 -1
  201. package/nitrogen/generated/shared/c++/StoredBackgroundEventEnvelope.hpp +108 -0
  202. package/nitrogen/generated/shared/c++/StoredBackgroundLocation.hpp +146 -0
  203. package/package.json +17 -6
  204. package/src/NitroGeolocation.nitro.ts +4 -4
  205. package/src/api/getCurrentPosition.ts +1 -1
  206. package/src/api/requestLocationSettings.ts +3 -2
  207. package/src/background/NitroBackgroundLocation.nitro.ts +83 -0
  208. package/src/background/index.ts +200 -0
  209. package/src/background/index.web.ts +120 -0
  210. package/src/background/task.ts +13 -0
  211. package/src/background/types.ts +328 -0
  212. package/src/compat/index.web.ts +141 -0
  213. package/src/index.tsx +3 -0
  214. package/src/index.web.tsx +41 -0
  215. package/src/publicTypes.ts +6 -5
  216. package/src/types.ts +3 -0
  217. package/src/web/browser.ts +171 -0
  218. package/src/web/index.ts +167 -0
  219. package/src/web/useWatchPosition.ts +76 -0
  220. package/src/web/watch.ts +81 -0
  221. package/src/devtools/index.test.ts +0 -54
  222. package/src/utils/errors.test.ts +0 -65
  223. package/src/utils/provider.test.ts +0 -303
@@ -3,7 +3,6 @@ package com.margelo.nitro.nitrogeolocation
3
3
  import android.Manifest
4
4
  import android.content.Context
5
5
  import android.content.pm.PackageManager
6
- import android.location.Address
7
6
  import android.location.Geocoder
8
7
  import android.location.Location
9
8
  import android.location.LocationListener
@@ -13,14 +12,14 @@ import android.os.CancellationSignal
13
12
  import android.os.Handler
14
13
  import android.os.Looper
15
14
  import android.os.SystemClock
16
- import androidx.core.app.ActivityCompat
17
15
  import androidx.core.content.ContextCompat
18
16
  import com.facebook.proguard.annotations.DoNotStrip
19
17
  import com.facebook.react.bridge.ReactApplicationContext
18
+ import com.facebook.react.modules.core.PermissionAwareActivity
19
+ import com.facebook.react.modules.core.PermissionListener
20
20
  import com.google.android.gms.common.ConnectionResult
21
21
  import com.google.android.gms.common.GoogleApiAvailability
22
22
  import com.google.android.gms.location.LocationCallback
23
- import com.google.android.gms.location.LocationRequest as GmsLocationRequest
24
23
  import com.google.android.gms.location.LocationResult
25
24
  import com.google.android.gms.location.LocationServices
26
25
  import com.margelo.nitro.NitroModules
@@ -29,12 +28,7 @@ import java.io.IOException
29
28
  import java.util.Locale
30
29
  import java.util.UUID
31
30
  import java.util.concurrent.ConcurrentHashMap
32
- import java.util.concurrent.atomic.AtomicBoolean
33
-
34
- private const val NO_LOCATION_PROVIDER_AVAILABLE_MESSAGE = "No location provider available"
35
- private const val NO_APPROXIMATE_LOCATION_PROVIDER_AVAILABLE_MESSAGE =
36
- "No location provider is available for approximate location. " +
37
- "ACCESS_COARSE_LOCATION is granted, but no enabled coarse-compatible provider is available."
31
+ import java.util.concurrent.atomic.AtomicLong
38
32
 
39
33
  /**
40
34
  * Geolocation implementation for Android.
@@ -50,94 +44,6 @@ class NitroGeolocation(
50
44
  private val reactContext: ReactApplicationContext = NitroModules.applicationContext!!
51
45
  ) : HybridNitroGeolocationSpec() {
52
46
 
53
- // MARK: - Types
54
-
55
- private data class ParsedOptions(
56
- val timeout: Double,
57
- val maximumAge: Double,
58
- val androidAccuracy: AndroidAccuracyResolution,
59
- val interval: Double,
60
- val fastestInterval: Double,
61
- val distanceFilter: Double,
62
- val granularity: AndroidGranularity,
63
- val waitForAccurateLocation: Boolean,
64
- val maxUpdateAge: Double?,
65
- val maxUpdateDelay: Double,
66
- val maxUpdates: Int?
67
- ) {
68
- companion object {
69
- private const val DEFAULT_TIMEOUT = 10.0 * 60 * 1000 // 10 minutes in ms
70
- private const val DEFAULT_MAXIMUM_AGE = 0.0
71
- private const val DEFAULT_INTERVAL = 1000.0
72
- private const val DEFAULT_FASTEST_INTERVAL = 100.0
73
- private const val DEFAULT_DISTANCE_FILTER = 0.0
74
- private const val DEFAULT_MAX_UPDATE_DELAY = 0.0
75
-
76
- fun parse(
77
- options: LocationRequestOptions?,
78
- defaultMaximumAge: Double = DEFAULT_MAXIMUM_AGE
79
- ): ParsedOptions {
80
- val enableHighAccuracy = options?.enableHighAccuracy ?: false
81
- val maxUpdates = options?.maxUpdates?.let { value ->
82
- if (!value.isFinite()) {
83
- 0
84
- } else {
85
- value.toInt()
86
- }
87
- }
88
-
89
- return ParsedOptions(
90
- timeout = options?.timeout ?: DEFAULT_TIMEOUT,
91
- maximumAge = options?.maximumAge ?: defaultMaximumAge,
92
- androidAccuracy = resolveAndroidAccuracy(
93
- options?.accuracy,
94
- enableHighAccuracy
95
- ),
96
- interval = options?.interval ?: DEFAULT_INTERVAL,
97
- fastestInterval = options?.fastestInterval ?: DEFAULT_FASTEST_INTERVAL,
98
- distanceFilter = options?.distanceFilter ?: DEFAULT_DISTANCE_FILTER,
99
- granularity = options?.granularity ?: AndroidGranularity.PERMISSION,
100
- waitForAccurateLocation = options?.waitForAccurateLocation ?: false,
101
- maxUpdateAge = options?.maxUpdateAge,
102
- maxUpdateDelay = options?.maxUpdateDelay ?: DEFAULT_MAX_UPDATE_DELAY,
103
- maxUpdates = maxUpdates
104
- )
105
- }
106
-
107
- fun parseLastKnown(options: LocationRequestOptions?): ParsedOptions {
108
- return parse(options, defaultMaximumAge = Double.POSITIVE_INFINITY)
109
- }
110
- }
111
- }
112
-
113
- private data class WatchSubscription(
114
- val token: String,
115
- val success: (GeolocationResponse) -> Unit,
116
- val error: ((LocationError) -> Unit)?,
117
- val options: ParsedOptions,
118
- var deliveredUpdates: Int = 0
119
- )
120
-
121
- private sealed interface PositionResult {
122
- data class Success(val position: GeolocationResponse) : PositionResult
123
- data class Failure(val error: LocationError) : PositionResult
124
- }
125
-
126
- private data class PositionRequest(
127
- val id: UUID,
128
- val resolver: (PositionResult) -> Unit,
129
- val options: ParsedOptions,
130
- val handler: Handler,
131
- val providers: List<String>,
132
- val deadlineElapsedRealtime: Long,
133
- var providerIndex: Int = 0,
134
- var cancellationSignal: CancellationSignal? = null
135
- ) {
136
- fun remainingTimeoutMillis(): Long {
137
- return (deadlineElapsedRealtime - SystemClock.elapsedRealtime()).coerceAtLeast(0L)
138
- }
139
- }
140
-
141
47
  // MARK: - Properties
142
48
 
143
49
  private var configuration: GeolocationConfiguration? = null
@@ -155,6 +61,14 @@ class NitroGeolocation(
155
61
  private val fusedLocationClient by lazy {
156
62
  LocationServices.getFusedLocationProviderClient(reactContext)
157
63
  }
64
+ private val fusedLocationProvider by lazy {
65
+ AndroidFusedLocationProvider(
66
+ fusedLocationClient = fusedLocationClient,
67
+ isCachedLocationValid = ::isCachedLocationValid,
68
+ effectiveMaximumAge = ::effectiveMaximumAge,
69
+ locationToPosition = ::locationToPosition
70
+ )
71
+ }
158
72
  private val headingManager: AndroidHeadingManager by lazy {
159
73
  AndroidHeadingManager(
160
74
  context = reactContext,
@@ -181,15 +95,7 @@ class NitroGeolocation(
181
95
  // Location listener for watch subscriptions
182
96
  private var watchLocationListener: LocationListener? = null
183
97
  private var fusedWatchLocationCallback: LocationCallback? = null
184
- private var currentWatchProvider: String? = null
185
-
186
- // Error codes
187
- private val INTERNAL_ERROR = -1.0
188
- private val PERMISSION_DENIED = 1.0
189
- private val POSITION_UNAVAILABLE = 2.0
190
- private val TIMEOUT = 3.0
191
- private val PLAY_SERVICE_NOT_AVAILABLE = 4.0
192
- private val SETTINGS_NOT_SATISFIED = 5.0
98
+ private val watchLocationGeneration = AtomicLong(0L)
193
99
 
194
100
  // MARK: - Configuration
195
101
 
@@ -227,6 +133,15 @@ class NitroGeolocation(
227
133
  return
228
134
  }
229
135
 
136
+ val permissionAware = activity as? PermissionAwareActivity
137
+ if (permissionAware == null) {
138
+ error?.invoke(createLocationError(
139
+ INTERNAL_ERROR,
140
+ "Current activity cannot request permissions"
141
+ ))
142
+ return
143
+ }
144
+
230
145
  // Queue resolver
231
146
  pendingPermissionResolvers.add(success)
232
147
 
@@ -236,10 +151,10 @@ class NitroGeolocation(
236
151
  Manifest.permission.ACCESS_COARSE_LOCATION
237
152
  )
238
153
 
239
- ActivityCompat.requestPermissions(
240
- activity,
154
+ permissionAware.requestPermissions(
241
155
  permissions,
242
- PERMISSION_REQUEST_CODE
156
+ PERMISSION_REQUEST_CODE,
157
+ createPermissionListener()
243
158
  )
244
159
  }
245
160
 
@@ -272,36 +187,26 @@ class NitroGeolocation(
272
187
  return promise
273
188
  }
274
189
 
275
- if (requiresPlayServices()) {
276
- if (!isGooglePlayServicesAvailable()) {
277
- promise.resolve(createLocationAvailability(false, "playServicesUnavailable"))
278
- return promise
279
- }
280
-
190
+ if (currentProviderRoute(isGooglePlayServicesAvailable()) == AndroidProviderRoute.FUSED) {
281
191
  fusedLocationClient.locationAvailability
282
192
  .addOnSuccessListener { availability ->
283
- promise.resolve(
284
- createLocationAvailability(
285
- availability.isLocationAvailable,
286
- if (availability.isLocationAvailable) null else "fusedLocationUnavailable"
287
- )
288
- )
193
+ if (availability.isLocationAvailable) {
194
+ promise.resolve(createLocationAvailability(true, null))
195
+ return@addOnSuccessListener
196
+ }
197
+
198
+ promise.resolve(getPlatformLocationAvailability())
289
199
  }
290
- .addOnFailureListener { exception ->
291
- promise.resolve(createLocationAvailability(
292
- false,
293
- "fusedLocationUnavailable: ${exception.message ?: "unknown error"}"
294
- ))
200
+ .addOnFailureListener {
201
+ promise.resolve(getPlatformLocationAvailability())
295
202
  }
296
203
  .addOnCanceledListener {
297
- promise.resolve(createLocationAvailability(false, "fusedLocationUnavailable"))
204
+ promise.resolve(getPlatformLocationAvailability())
298
205
  }
299
206
  return promise
300
207
  }
301
208
 
302
- val providers = getValidProviders(resolveAndroidAccuracy(null, enableHighAccuracy = false))
303
- val reason = if (providers.isEmpty()) "noLocationProvider" else null
304
- promise.resolve(createLocationAvailability(providers.isNotEmpty(), reason))
209
+ promise.resolve(getPlatformLocationAvailability())
305
210
  return promise
306
211
  }
307
212
 
@@ -362,16 +267,41 @@ class NitroGeolocation(
362
267
  error?.invoke(permissionError)
363
268
  return
364
269
  }
365
- if (requiresPlayServices() && !isGooglePlayServicesAvailable()) {
366
- error?.invoke(createPlayServicesUnavailableError())
270
+ val deadlineElapsedRealtime = createRequestDeadlineElapsedRealtime(parsedOptions.timeout)
271
+ if (currentProviderRoute(isGooglePlayServicesAvailable()) == AndroidProviderRoute.FUSED) {
272
+ fusedLocationProvider.getCurrentPosition(
273
+ success,
274
+ { fusedError ->
275
+ runAndroidCurrentPositionFallbackAfterFusedFailure(
276
+ locationProvider = configuration?.locationProvider,
277
+ runPlatformFallback = {
278
+ getCurrentPositionWithPlatform(
279
+ success,
280
+ error,
281
+ parsedOptions,
282
+ deadlineElapsedRealtime
283
+ )
284
+ },
285
+ failWithoutFallback = {
286
+ error?.invoke(fusedError)
287
+ }
288
+ )
289
+ },
290
+ parsedOptions,
291
+ deadlineElapsedRealtime
292
+ )
367
293
  return
368
294
  }
369
295
 
370
- if (requiresPlayServices()) {
371
- getCurrentPositionWithFused(success, error, parsedOptions)
372
- return
373
- }
296
+ getCurrentPositionWithPlatform(success, error, parsedOptions, deadlineElapsedRealtime)
297
+ }
374
298
 
299
+ private fun getCurrentPositionWithPlatform(
300
+ success: (GeolocationResponse) -> Unit,
301
+ error: ((LocationError) -> Unit)?,
302
+ parsedOptions: ParsedOptions,
303
+ deadlineElapsedRealtime: Long = createRequestDeadlineElapsedRealtime(parsedOptions.timeout)
304
+ ) {
375
305
  val providers = getValidProviders(parsedOptions)
376
306
  if (providers.isEmpty()) {
377
307
  error?.invoke(createNoLocationProviderError(parsedOptions))
@@ -384,8 +314,13 @@ class NitroGeolocation(
384
314
  return
385
315
  }
386
316
 
317
+ if (remainingTimeoutMillis(deadlineElapsedRealtime) <= 0L) {
318
+ error?.invoke(createPositionTimeoutError(parsedOptions))
319
+ return
320
+ }
321
+
387
322
  // Request fresh location
388
- requestFreshLocation(providers, parsedOptions) { result ->
323
+ requestFreshLocation(providers, parsedOptions, deadlineElapsedRealtime) { result ->
389
324
  when (result) {
390
325
  is PositionResult.Success -> success(result.position)
391
326
  is PositionResult.Failure -> error?.invoke(result.error)
@@ -417,16 +352,33 @@ class NitroGeolocation(
417
352
  error?.invoke(permissionError)
418
353
  return
419
354
  }
420
- if (requiresPlayServices() && !isGooglePlayServicesAvailable()) {
421
- error?.invoke(createPlayServicesUnavailableError())
355
+ if (currentProviderRoute(isGooglePlayServicesAvailable()) == AndroidProviderRoute.FUSED) {
356
+ fusedLocationProvider.getLastKnownPosition(
357
+ success,
358
+ { fusedError ->
359
+ runAndroidLastKnownPositionFallbackAfterFusedFailure(
360
+ locationProvider = configuration?.locationProvider,
361
+ runPlatformFallback = {
362
+ getLastKnownPositionWithPlatform(success, error, parsedOptions)
363
+ },
364
+ failWithoutFallback = {
365
+ error?.invoke(fusedError)
366
+ }
367
+ )
368
+ },
369
+ parsedOptions
370
+ )
422
371
  return
423
372
  }
424
373
 
425
- if (requiresPlayServices()) {
426
- getLastKnownPositionWithFused(success, error, parsedOptions)
427
- return
428
- }
374
+ getLastKnownPositionWithPlatform(success, error, parsedOptions)
375
+ }
429
376
 
377
+ private fun getLastKnownPositionWithPlatform(
378
+ success: (GeolocationResponse) -> Unit,
379
+ error: ((LocationError) -> Unit)?,
380
+ parsedOptions: ParsedOptions
381
+ ) {
430
382
  val providers = getValidProviders(parsedOptions)
431
383
  if (providers.isEmpty()) {
432
384
  error?.invoke(createNoLocationProviderError(parsedOptions))
@@ -466,7 +418,7 @@ class NitroGeolocation(
466
418
  @Suppress("DEPRECATION")
467
419
  geocoder.getFromLocationName(query, GEOCODER_MAX_RESULTS)
468
420
  .orEmpty()
469
- .mapNotNull { geocodedAddressToLocation(it) }
421
+ .mapNotNull { it.toGeocodedLocation() }
470
422
  .toTypedArray()
471
423
  }
472
424
  }
@@ -491,7 +443,7 @@ class NitroGeolocation(
491
443
  GEOCODER_MAX_RESULTS
492
444
  )
493
445
  .orEmpty()
494
- .map { addressToReverseGeocodedAddress(it) }
446
+ .map { it.toReverseGeocodedAddress() }
495
447
  .toTypedArray()
496
448
  }
497
449
  }
@@ -730,8 +682,13 @@ class NitroGeolocation(
730
682
  return null
731
683
  }
732
684
 
733
- // Handle permission request result (called from Activity)
734
- fun onPermissionResult(requestCode: Int, grantResults: IntArray) {
685
+ private fun createPermissionListener() =
686
+ PermissionListener { requestCode, _, grantResults ->
687
+ onPermissionResult(requestCode, grantResults)
688
+ requestCode == PERMISSION_REQUEST_CODE
689
+ }
690
+
691
+ private fun onPermissionResult(requestCode: Int, grantResults: IntArray) {
735
692
  if (requestCode != PERMISSION_REQUEST_CODE) return
736
693
 
737
694
  val granted = grantResults.isNotEmpty() && grantResults.any { it == PackageManager.PERMISSION_GRANTED }
@@ -746,10 +703,13 @@ class NitroGeolocation(
746
703
 
747
704
  // MARK: - Helper Functions - Provider Selection
748
705
 
749
- private fun requiresPlayServices(): Boolean {
750
- // TODO: Switch auto/default Android provider selection to prefer
751
- // Google Play Services when available.
752
- return configuration?.locationProvider == LocationProvider.PLAYSERVICES
706
+ private fun currentProviderRoute(
707
+ googlePlayServicesAvailable: Boolean
708
+ ): AndroidProviderRoute {
709
+ return selectAndroidProviderRoute(
710
+ locationProvider = configuration?.locationProvider,
711
+ googlePlayServicesAvailable = googlePlayServicesAvailable
712
+ )
753
713
  }
754
714
 
755
715
  private fun isGooglePlayServicesAvailable(): Boolean {
@@ -757,6 +717,12 @@ class NitroGeolocation(
757
717
  .isGooglePlayServicesAvailable(reactContext) == ConnectionResult.SUCCESS
758
718
  }
759
719
 
720
+ private fun getPlatformLocationAvailability(): LocationAvailability {
721
+ val providers = getValidProviders(resolveAndroidAccuracy(null, enableHighAccuracy = false))
722
+ val reason = if (providers.isEmpty()) "noLocationProvider" else null
723
+ return createLocationAvailability(providers.isNotEmpty(), reason)
724
+ }
725
+
760
726
  private fun getValidProvider(accuracy: AndroidAccuracyResolution): String? {
761
727
  return getValidProviders(accuracy).firstOrNull()
762
728
  }
@@ -868,138 +834,12 @@ class NitroGeolocation(
868
834
  return bestLocation
869
835
  }
870
836
 
871
- private fun getCurrentPositionWithFused(
872
- success: (GeolocationResponse) -> Unit,
873
- error: ((LocationError) -> Unit)?,
874
- options: ParsedOptions
875
- ) {
876
- if (effectiveMaximumAge(options) > 0.0) {
877
- getFusedCachedLocation(options) { cachedLocation ->
878
- if (cachedLocation != null) {
879
- success(locationToPosition(cachedLocation))
880
- return@getFusedCachedLocation
881
- }
882
-
883
- requestFusedFreshLocation(success, error, options)
884
- }
885
- return
886
- }
887
-
888
- requestFusedFreshLocation(success, error, options)
889
- }
890
-
891
- private fun getLastKnownPositionWithFused(
892
- success: (GeolocationResponse) -> Unit,
893
- error: ((LocationError) -> Unit)?,
894
- options: ParsedOptions
895
- ) {
896
- getFusedCachedLocation(options) { cachedLocation ->
897
- if (cachedLocation != null) {
898
- success(locationToPosition(cachedLocation))
899
- return@getFusedCachedLocation
900
- }
901
-
902
- error?.invoke(createLocationError(
903
- POSITION_UNAVAILABLE,
904
- "No cached location available"
905
- ))
906
- }
907
- }
908
-
909
- private fun getFusedCachedLocation(
910
- options: ParsedOptions,
911
- completion: (Location?) -> Unit
912
- ) {
913
- // Fused lastLocation is not requested with LocationRequest granularity,
914
- // so it cannot prove that a cached fix satisfies coarse-only callers.
915
- if (options.granularity == AndroidGranularity.COARSE) {
916
- completion(null)
917
- return
918
- }
919
-
920
- try {
921
- fusedLocationClient.lastLocation
922
- .addOnSuccessListener { location ->
923
- completion(location?.takeIf { isCachedLocationValid(it, options) })
924
- }
925
- .addOnFailureListener {
926
- completion(null)
927
- }
928
- .addOnCanceledListener {
929
- completion(null)
930
- }
931
- } catch (e: SecurityException) {
932
- completion(null)
933
- }
934
- }
935
-
936
- private fun requestFusedFreshLocation(
937
- success: (GeolocationResponse) -> Unit,
938
- error: ((LocationError) -> Unit)?,
939
- options: ParsedOptions
940
- ) {
941
- val handler = Handler(Looper.getMainLooper())
942
- val didComplete = AtomicBoolean(false)
943
- lateinit var callback: LocationCallback
944
-
945
- fun complete(result: PositionResult) {
946
- if (!didComplete.compareAndSet(false, true)) return
947
-
948
- handler.removeCallbacksAndMessages(null)
949
- try {
950
- fusedLocationClient.removeLocationUpdates(callback)
951
- } catch (_: Exception) {
952
- // Ignore cleanup races.
953
- }
954
-
955
- when (result) {
956
- is PositionResult.Success -> success(result.position)
957
- is PositionResult.Failure -> error?.invoke(result.error)
958
- }
959
- }
960
-
961
- callback = object : LocationCallback() {
962
- override fun onLocationResult(result: LocationResult) {
963
- val location = result.lastLocation
964
- if (location != null) {
965
- complete(PositionResult.Success(locationToPosition(location)))
966
- }
967
- }
968
- }
969
-
970
- val timeoutRunnable = Runnable {
971
- complete(PositionResult.Failure(createPositionTimeoutError(options)))
972
- }
973
-
974
- try {
975
- fusedLocationClient.requestLocationUpdates(
976
- buildFusedLocationRequest(
977
- options,
978
- maxUpdatesOverride = 1,
979
- includeDistanceFilter = false
980
- ),
981
- callback,
982
- Looper.getMainLooper()
983
- )
984
- handler.postDelayed(timeoutRunnable, coerceTimeoutMillis(options.timeout))
985
- } catch (e: SecurityException) {
986
- complete(PositionResult.Failure(createLocationError(
987
- PERMISSION_DENIED,
988
- "Security exception: ${e.message}"
989
- )))
990
- } catch (e: Exception) {
991
- complete(PositionResult.Failure(createLocationError(
992
- POSITION_UNAVAILABLE,
993
- "Unable to request fused location: ${e.message}"
994
- )))
995
- }
996
- }
997
-
998
837
  // MARK: - Helper Functions - Request Fresh Location
999
838
 
1000
839
  private fun requestFreshLocation(
1001
840
  providers: List<String>,
1002
841
  options: ParsedOptions,
842
+ deadlineElapsedRealtime: Long = createRequestDeadlineElapsedRealtime(options.timeout),
1003
843
  resolver: (PositionResult) -> Unit
1004
844
  ) {
1005
845
  val id = UUID.randomUUID()
@@ -1011,7 +851,7 @@ class NitroGeolocation(
1011
851
  options = options,
1012
852
  handler = handler,
1013
853
  providers = providers,
1014
- deadlineElapsedRealtime = createRequestDeadlineElapsedRealtime(options.timeout)
854
+ deadlineElapsedRealtime = deadlineElapsedRealtime
1015
855
  )
1016
856
 
1017
857
  pendingPositionRequests[id] = request
@@ -1037,9 +877,9 @@ class NitroGeolocation(
1037
877
  return
1038
878
  }
1039
879
 
1040
- // Android's getCurrentLocation may resolve a recent historical fix. A maximumAge of 0
1041
- // means callers explicitly asked us to wait for a fresh provider update.
1042
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && request.options.maximumAge > 0.0) {
880
+ // Android's getCurrentLocation may resolve a recent historical fix. An effective
881
+ // maximum age of 0 means callers explicitly asked us to wait for a fresh update.
882
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && effectiveMaximumAge(request.options) > 0.0) {
1043
883
  requestCurrentLocationModern(provider, requestId, request.handler, remainingTimeoutMillis)
1044
884
  } else {
1045
885
  requestCurrentLocationLegacy(provider, requestId, request.handler, remainingTimeoutMillis)
@@ -1070,14 +910,21 @@ class NitroGeolocation(
1070
910
 
1071
911
  val request = pendingPositionRequests[requestId]
1072
912
  if (request != null) {
1073
- if (location != null) {
913
+ if (location != null && isCachedLocationValid(location, request.options)) {
1074
914
  pendingPositionRequests.remove(requestId)
1075
915
  val position = locationToPosition(location)
1076
916
  request.resolver(PositionResult.Success(position))
917
+ } else if (location != null) {
918
+ retryCurrentLocationLegacyAfterStaleModern(
919
+ provider,
920
+ requestId,
921
+ handler,
922
+ request
923
+ )
1077
924
  } else {
1078
925
  handleProviderFailure(requestId, createLocationError(
1079
926
  POSITION_UNAVAILABLE,
1080
- "Unable to get location"
927
+ "Unable to get fresh location"
1081
928
  ))
1082
929
  }
1083
930
  }
@@ -1096,6 +943,24 @@ class NitroGeolocation(
1096
943
  }
1097
944
  }
1098
945
 
946
+ private fun retryCurrentLocationLegacyAfterStaleModern(
947
+ provider: String,
948
+ requestId: UUID,
949
+ handler: Handler,
950
+ request: PositionRequest
951
+ ) {
952
+ request.cancellationSignal?.cancel()
953
+ request.cancellationSignal = null
954
+
955
+ val remainingTimeoutMillis = request.remainingTimeoutMillis()
956
+ if (remainingTimeoutMillis <= 0L) {
957
+ handlePositionTimeout(requestId)
958
+ return
959
+ }
960
+
961
+ requestCurrentLocationLegacy(provider, requestId, handler, remainingTimeoutMillis)
962
+ }
963
+
1099
964
  private fun requestCurrentLocationLegacy(
1100
965
  provider: String,
1101
966
  requestId: UUID,
@@ -1234,31 +1099,41 @@ class NitroGeolocation(
1234
1099
  // MARK: - Helper Functions - Watch Position
1235
1100
 
1236
1101
  private fun startWatchingLocation() {
1237
- if (requiresPlayServices() && !isGooglePlayServicesAvailable()) {
1238
- notifyWatchPlayServicesUnavailable()
1239
- return
1240
- }
1102
+ val generation = watchLocationGeneration.get()
1241
1103
 
1242
- if (requiresPlayServices()) {
1243
- startWatchingFusedLocation()
1104
+ if (currentProviderRoute(isGooglePlayServicesAvailable()) == AndroidProviderRoute.FUSED) {
1105
+ startWatchingFusedLocation(generation)
1244
1106
  return
1245
1107
  }
1246
1108
 
1109
+ startWatchingPlatformLocation(generation)
1110
+ }
1111
+
1112
+ private fun isActiveWatchGeneration(generation: Long): Boolean {
1113
+ return watchSubscriptions.isNotEmpty() && watchLocationGeneration.get() == generation
1114
+ }
1115
+
1116
+ private fun startWatchingPlatformLocation(generation: Long) {
1117
+ if (!isActiveWatchGeneration(generation)) return
1118
+
1247
1119
  val mergedOptions = mergeWatchOptions()
1248
1120
  val provider = getValidProvider(mergedOptions)
1249
1121
  if (provider == null) {
1250
1122
  notifyWatchProviderUnavailable()
1251
1123
  return
1252
1124
  }
1253
- currentWatchProvider = provider
1254
1125
 
1255
1126
  val listener = object : LocationListener {
1256
1127
  override fun onLocationChanged(location: Location) {
1128
+ if (!isActiveWatchGeneration(generation)) return
1129
+
1257
1130
  val position = locationToPosition(location)
1258
1131
  deliverWatchPosition(position)
1259
1132
  }
1260
1133
 
1261
1134
  override fun onProviderDisabled(provider: String) {
1135
+ if (!isActiveWatchGeneration(generation)) return
1136
+
1262
1137
  val error = LocationError(
1263
1138
  code = SETTINGS_NOT_SATISFIED,
1264
1139
  message = "Provider disabled: $provider"
@@ -1274,6 +1149,7 @@ class NitroGeolocation(
1274
1149
  override fun onStatusChanged(provider: String?, status: Int, extras: android.os.Bundle?) {}
1275
1150
  }
1276
1151
 
1152
+ removePlatformWatchLocationListener()
1277
1153
  watchLocationListener = listener
1278
1154
 
1279
1155
  try {
@@ -1296,42 +1172,53 @@ class NitroGeolocation(
1296
1172
  }
1297
1173
  }
1298
1174
 
1299
- private fun startWatchingFusedLocation() {
1175
+ private fun startWatchingFusedLocation(generation: Long) {
1176
+ if (!isActiveWatchGeneration(generation)) return
1177
+
1300
1178
  val mergedOptions = mergeWatchOptions()
1301
1179
  val callback = object : LocationCallback() {
1302
1180
  override fun onLocationResult(result: LocationResult) {
1181
+ if (!isActiveWatchGeneration(generation)) return
1182
+
1303
1183
  val location = result.lastLocation ?: return
1304
- deliverWatchPosition(locationToPosition(location))
1184
+ deliverWatchPosition(locationToPosition(location, LocationProviderUsed.FUSED))
1305
1185
  }
1306
1186
  }
1307
1187
 
1188
+ removeFusedWatchLocationCallback()
1308
1189
  fusedWatchLocationCallback = callback
1309
1190
 
1310
- try {
1311
- fusedLocationClient.requestLocationUpdates(
1312
- buildFusedLocationRequest(mergedOptions),
1313
- callback,
1314
- Looper.getMainLooper()
1315
- )
1316
- } catch (e: SecurityException) {
1317
- val error = LocationError(
1318
- code = PERMISSION_DENIED,
1319
- message = "Permission denied: ${e.message}"
1320
- )
1191
+ fun handleFusedRequestFailure(fusedError: LocationError? = null) {
1192
+ if (!isActiveWatchGeneration(generation)) return
1321
1193
 
1322
- for ((_, subscription) in watchSubscriptions) {
1323
- subscription.error?.invoke(error)
1324
- }
1325
- } catch (e: Exception) {
1326
- val error = LocationError(
1327
- code = POSITION_UNAVAILABLE,
1328
- message = "Unable to request fused location updates: ${e.message}"
1194
+ removeFusedWatchLocationCallback()
1195
+ runAndroidWatchPositionFallbackAfterFusedFailure(
1196
+ locationProvider = configuration?.locationProvider,
1197
+ runPlatformFallback = { startWatchingPlatformLocation(generation) },
1198
+ failWithoutFallback = {
1199
+ if (fusedError != null) {
1200
+ notifyWatchError(fusedError)
1201
+ } else {
1202
+ notifyWatchProviderUnavailable()
1203
+ }
1204
+ }
1329
1205
  )
1330
-
1331
- for ((_, subscription) in watchSubscriptions) {
1332
- subscription.error?.invoke(error)
1333
- }
1334
1206
  }
1207
+
1208
+ fusedLocationProvider.requestWatchUpdates(
1209
+ options = mergedOptions,
1210
+ callback = callback,
1211
+ onInactiveStart = {
1212
+ if (!isActiveWatchGeneration(generation)) {
1213
+ try {
1214
+ fusedLocationClient.removeLocationUpdates(callback)
1215
+ } catch (e: Exception) {
1216
+ // Ignore
1217
+ }
1218
+ }
1219
+ },
1220
+ onFailure = { fusedError -> handleFusedRequestFailure(fusedError) }
1221
+ )
1335
1222
  }
1336
1223
 
1337
1224
  private fun mergeWatchOptions(): ParsedOptions {
@@ -1418,9 +1305,9 @@ class NitroGeolocation(
1418
1305
  }
1419
1306
  }
1420
1307
 
1421
- private fun notifyWatchPlayServicesUnavailable() {
1308
+ private fun notifyWatchError(error: LocationError) {
1422
1309
  for ((_, subscription) in watchSubscriptions) {
1423
- subscription.error?.invoke(createPlayServicesUnavailableError())
1310
+ subscription.error?.invoke(error)
1424
1311
  }
1425
1312
  }
1426
1313
 
@@ -1448,6 +1335,12 @@ class NitroGeolocation(
1448
1335
  }
1449
1336
 
1450
1337
  private fun stopWatchingLocation() {
1338
+ watchLocationGeneration.incrementAndGet()
1339
+ removePlatformWatchLocationListener()
1340
+ removeFusedWatchLocationCallback()
1341
+ }
1342
+
1343
+ private fun removePlatformWatchLocationListener() {
1451
1344
  watchLocationListener?.let { listener ->
1452
1345
  try {
1453
1346
  locationManager.removeUpdates(listener)
@@ -1455,6 +1348,10 @@ class NitroGeolocation(
1455
1348
  // Ignore
1456
1349
  }
1457
1350
  }
1351
+ watchLocationListener = null
1352
+ }
1353
+
1354
+ private fun removeFusedWatchLocationCallback() {
1458
1355
  fusedWatchLocationCallback?.let { callback ->
1459
1356
  try {
1460
1357
  fusedLocationClient.removeLocationUpdates(callback)
@@ -1462,9 +1359,7 @@ class NitroGeolocation(
1462
1359
  // Ignore
1463
1360
  }
1464
1361
  }
1465
- watchLocationListener = null
1466
1362
  fusedWatchLocationCallback = null
1467
- currentWatchProvider = null
1468
1363
  }
1469
1364
 
1470
1365
  private fun restartWatchingLocation() {
@@ -1474,67 +1369,13 @@ class NitroGeolocation(
1474
1369
 
1475
1370
  // MARK: - Helper Functions - Conversion
1476
1371
 
1477
- private fun locationToPosition(location: Location): GeolocationResponse {
1372
+ private fun locationToPosition(
1373
+ location: Location,
1374
+ providerOverride: LocationProviderUsed? = null
1375
+ ): GeolocationResponse {
1478
1376
  lastLocation = location
1479
1377
 
1480
- val coords = GeolocationCoordinates(
1481
- latitude = location.latitude,
1482
- longitude = location.longitude,
1483
- altitude = location.altitudeValue(),
1484
- accuracy = location.accuracy.toDouble(),
1485
- altitudeAccuracy = location.altitudeAccuracyValue(),
1486
- heading = location.headingValue(),
1487
- speed = location.speedValue()
1488
- )
1489
-
1490
- return GeolocationResponse(
1491
- coords = coords,
1492
- timestamp = location.time.toDouble(),
1493
- mocked = location.isMocked(),
1494
- provider = location.providerUsed()
1495
- )
1496
- }
1497
-
1498
- private fun geocodedAddressToLocation(address: Address): GeocodedLocation? {
1499
- if (!address.hasLatitude() || !address.hasLongitude()) {
1500
- return null
1501
- }
1502
-
1503
- return GeocodedLocation(
1504
- latitude = address.latitude,
1505
- longitude = address.longitude,
1506
- accuracy = null
1507
- )
1508
- }
1509
-
1510
- private fun addressToReverseGeocodedAddress(address: Address): ReverseGeocodedAddress {
1511
- return ReverseGeocodedAddress(
1512
- country = address.countryName.nonBlankOrNull(),
1513
- region = address.adminArea.nonBlankOrNull(),
1514
- city = (address.locality ?: address.subAdminArea).nonBlankOrNull(),
1515
- district = address.subLocality.nonBlankOrNull(),
1516
- street = formatStreet(address),
1517
- postalCode = address.postalCode.nonBlankOrNull(),
1518
- formattedAddress = formatAddressLines(address)
1519
- )
1520
- }
1521
-
1522
- private fun formatStreet(address: Address): String? {
1523
- return listOf(address.subThoroughfare, address.thoroughfare)
1524
- .mapNotNull { it.nonBlankOrNull() }
1525
- .joinToString(" ")
1526
- .nonBlankOrNull()
1527
- }
1528
-
1529
- private fun formatAddressLines(address: Address): String? {
1530
- if (address.maxAddressLineIndex < 0) {
1531
- return null
1532
- }
1533
-
1534
- return (0..address.maxAddressLineIndex)
1535
- .mapNotNull { index -> address.getAddressLine(index).nonBlankOrNull() }
1536
- .joinToString(", ")
1537
- .nonBlankOrNull()
1378
+ return location.toGeolocationResponse(providerOverride)
1538
1379
  }
1539
1380
 
1540
1381
  private fun validateGeocodingCoordinates(coords: GeocodingCoordinates): LocationError? {
@@ -1593,107 +1434,9 @@ class NitroGeolocation(
1593
1434
  }.start()
1594
1435
  }
1595
1436
 
1596
- private fun createLocationAvailability(
1597
- available: Boolean,
1598
- reason: String?
1599
- ): LocationAvailability {
1600
- return LocationAvailability(
1601
- available = available,
1602
- reason = reason
1603
- )
1604
- }
1605
-
1606
- private fun createLocationError(code: Double, message: String): LocationError {
1607
- return LocationError(
1608
- code = code,
1609
- message = message
1610
- )
1611
- }
1612
-
1613
- private fun createPlayServicesUnavailableError(): LocationError {
1614
- return createLocationError(
1615
- PLAY_SERVICE_NOT_AVAILABLE,
1616
- "Google Play Services location provider is not available."
1617
- )
1618
- }
1619
-
1620
- private fun createPositionTimeoutError(options: ParsedOptions): LocationError {
1621
- val timeoutSeconds = options.timeout / 1000.0
1622
- val message = String.format("Unable to fetch location within %.1fs.", timeoutSeconds)
1623
- return createLocationError(TIMEOUT, message)
1624
- }
1625
-
1626
- private fun createRequestDeadlineElapsedRealtime(timeout: Double): Long {
1627
- val now = SystemClock.elapsedRealtime()
1628
- val timeoutMillis = coerceTimeoutMillis(timeout)
1629
- val maxTimeoutMillis = Long.MAX_VALUE - now
1630
-
1631
- return if (timeoutMillis >= maxTimeoutMillis) {
1632
- Long.MAX_VALUE
1633
- } else {
1634
- now + timeoutMillis
1635
- }
1636
- }
1637
-
1638
- private fun coerceTimeoutMillis(timeout: Double): Long {
1639
- return when {
1640
- timeout.isNaN() || timeout <= 0.0 -> 0L
1641
- timeout.isInfinite() || timeout >= Long.MAX_VALUE.toDouble() -> Long.MAX_VALUE
1642
- else -> timeout.toLong()
1643
- }
1644
- }
1645
-
1646
- private fun buildFusedLocationRequest(
1647
- options: ParsedOptions,
1648
- maxUpdatesOverride: Int? = null,
1649
- includeDistanceFilter: Boolean = true
1650
- ): GmsLocationRequest {
1651
- val builder = GmsLocationRequest
1652
- .Builder(options.androidAccuracy.gmsPriority(), coercePositiveMillis(options.interval))
1653
- .setMinUpdateIntervalMillis(coercePositiveMillis(options.fastestInterval))
1654
- .setGranularity(options.granularity.gmsGranularity())
1655
- .setWaitForAccurateLocation(options.waitForAccurateLocation)
1656
- .setMaxUpdateDelayMillis(coerceNonNegativeMillis(options.maxUpdateDelay))
1657
-
1658
- if (includeDistanceFilter) {
1659
- builder.setMinUpdateDistanceMeters(options.distanceFilter.toFloat())
1660
- }
1661
-
1662
- options.maxUpdateAge?.let { value ->
1663
- builder.setMaxUpdateAgeMillis(coerceNonNegativeMillis(value))
1664
- }
1665
-
1666
- val maxUpdates = maxUpdatesOverride ?: options.maxUpdates
1667
- if (maxUpdates != null) {
1668
- builder.setMaxUpdates(maxUpdates)
1669
- }
1670
-
1671
- return builder.build()
1672
- }
1673
-
1674
- private fun coercePositiveMillis(value: Double): Long {
1675
- return when {
1676
- value.isNaN() || value <= 0.0 -> 1L
1677
- value.isInfinite() || value >= Long.MAX_VALUE.toDouble() -> Long.MAX_VALUE
1678
- else -> value.toLong()
1679
- }
1680
- }
1681
-
1682
- private fun coerceNonNegativeMillis(value: Double): Long {
1683
- return when {
1684
- value.isNaN() || value <= 0.0 -> 0L
1685
- value.isInfinite() || value >= Long.MAX_VALUE.toDouble() -> Long.MAX_VALUE
1686
- else -> value.toLong()
1687
- }
1688
- }
1689
-
1690
1437
  companion object {
1691
1438
  private const val PERMISSION_REQUEST_CODE = 8947
1692
1439
  private const val GEOCODER_MAX_RESULTS = 5
1693
1440
  private const val TWO_MINUTES_MS = 2 * 60 * 1000L
1694
1441
  }
1695
1442
  }
1696
-
1697
- private fun String?.nonBlankOrNull(): String? {
1698
- return this?.trim()?.takeIf { it.isNotEmpty() }
1699
- }