react-native-bg-geolocation 0.2.0

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 (142) hide show
  1. package/BgGeolocation.podspec +39 -0
  2. package/LICENSE +20 -0
  3. package/README.md +366 -0
  4. package/android/build.gradle +69 -0
  5. package/android/src/main/AndroidManifest.xml +53 -0
  6. package/android/src/main/java/com/bggeolocation/BgGeolocationActivityRecognitionReceiver.kt +116 -0
  7. package/android/src/main/java/com/bggeolocation/BgGeolocationBootReceiver.kt +44 -0
  8. package/android/src/main/java/com/bggeolocation/BgGeolocationForegroundService.kt +373 -0
  9. package/android/src/main/java/com/bggeolocation/BgGeolocationGeofenceReceiver.kt +55 -0
  10. package/android/src/main/java/com/bggeolocation/BgGeolocationHeadlessTask.kt +138 -0
  11. package/android/src/main/java/com/bggeolocation/BgGeolocationModule.kt +1030 -0
  12. package/android/src/main/java/com/bggeolocation/BgGeolocationMotionStateMachine.kt +159 -0
  13. package/android/src/main/java/com/bggeolocation/BgGeolocationPackage.kt +31 -0
  14. package/android/src/main/res/drawable/bg_geo_notification.xml +9 -0
  15. package/ios/BgGeolocation.h +14 -0
  16. package/ios/BgGeolocation.mm +709 -0
  17. package/ios/engine/AtomicBoolean.swift +48 -0
  18. package/ios/engine/BGActivityChangeEvent.swift +20 -0
  19. package/ios/engine/BGActivityConfig.swift +71 -0
  20. package/ios/engine/BGAppConfig.swift +92 -0
  21. package/ios/engine/BGAppState.swift +147 -0
  22. package/ios/engine/BGAuthorization.swift +85 -0
  23. package/ios/engine/BGAuthorizationAlertPresenter.swift +39 -0
  24. package/ios/engine/BGAuthorizationConfig.swift +50 -0
  25. package/ios/engine/BGAuthorizationEvent.swift +40 -0
  26. package/ios/engine/BGBackgroundTaskManager.swift +143 -0
  27. package/ios/engine/BGCLRouter.swift +101 -0
  28. package/ios/engine/BGCallback.swift +19 -0
  29. package/ios/engine/BGConfig.swift +440 -0
  30. package/ios/engine/BGConfigModuleBase.swift +180 -0
  31. package/ios/engine/BGConfigOLD.swift +582 -0
  32. package/ios/engine/BGConnectivityChangeEvent.swift +15 -0
  33. package/ios/engine/BGCrashDetector.swift +122 -0
  34. package/ios/engine/BGCurrentPositionRequest.swift +87 -0
  35. package/ios/engine/BGDataStore.swift +75 -0
  36. package/ios/engine/BGDatabase.swift +677 -0
  37. package/ios/engine/BGDatabasePool.swift +220 -0
  38. package/ios/engine/BGDatabaseQueue.swift +215 -0
  39. package/ios/engine/BGDateUtils.swift +26 -0
  40. package/ios/engine/BGDeviceInfo.swift +54 -0
  41. package/ios/engine/BGDeviceManager.swift +65 -0
  42. package/ios/engine/BGEnabledChangeEvent.swift +11 -0
  43. package/ios/engine/BGEnv.swift +17 -0
  44. package/ios/engine/BGEventBus.swift +83 -0
  45. package/ios/engine/BGEventManager.swift +169 -0
  46. package/ios/engine/BGEventNames.swift +51 -0
  47. package/ios/engine/BGGeofence.swift +233 -0
  48. package/ios/engine/BGGeofenceDAO.swift +152 -0
  49. package/ios/engine/BGGeofenceEvent.swift +42 -0
  50. package/ios/engine/BGGeofenceLocationRequest.swift +94 -0
  51. package/ios/engine/BGGeofenceManager.swift +315 -0
  52. package/ios/engine/BGGeofenceTransition.swift +97 -0
  53. package/ios/engine/BGGeofencesChangeEvent.swift +26 -0
  54. package/ios/engine/BGGeolocationConfig.swift +136 -0
  55. package/ios/engine/BGHeartbeatEvent.swift +31 -0
  56. package/ios/engine/BGHeartbeatService.swift +51 -0
  57. package/ios/engine/BGHttpConfig.swift +105 -0
  58. package/ios/engine/BGHttpErrorCodes.swift +63 -0
  59. package/ios/engine/BGHttpEvent.swift +34 -0
  60. package/ios/engine/BGHttpRequest.swift +126 -0
  61. package/ios/engine/BGHttpResponse.swift +93 -0
  62. package/ios/engine/BGHttpService.swift +428 -0
  63. package/ios/engine/BGKalmanFilter.swift +105 -0
  64. package/ios/engine/BGLMActionNames.swift +55 -0
  65. package/ios/engine/BGLicenseManager.swift +26 -0
  66. package/ios/engine/BGLiveActivityManager.swift +327 -0
  67. package/ios/engine/BGLocation.swift +311 -0
  68. package/ios/engine/BGLocationAuthorization.swift +427 -0
  69. package/ios/engine/BGLocationDAO.swift +252 -0
  70. package/ios/engine/BGLocationErrors.swift +28 -0
  71. package/ios/engine/BGLocationEvent.swift +43 -0
  72. package/ios/engine/BGLocationFilter.swift +82 -0
  73. package/ios/engine/BGLocationFilterConfig.swift +57 -0
  74. package/ios/engine/BGLocationHelper.swift +54 -0
  75. package/ios/engine/BGLocationManager.swift +662 -0
  76. package/ios/engine/BGLocationMetricsEngine.swift +116 -0
  77. package/ios/engine/BGLocationRequestService.swift +459 -0
  78. package/ios/engine/BGLocationSatisfier.swift +14 -0
  79. package/ios/engine/BGLocationStreamEvent.swift +27 -0
  80. package/ios/engine/BGLog.swift +337 -0
  81. package/ios/engine/BGLogLevel.swift +26 -0
  82. package/ios/engine/BGLoggerConfig.swift +60 -0
  83. package/ios/engine/BGMotionActivity.swift +31 -0
  84. package/ios/engine/BGMotionActivityClassifier.swift +108 -0
  85. package/ios/engine/BGMotionActivityManagerAdapter.swift +40 -0
  86. package/ios/engine/BGMotionActivitySource.swift +46 -0
  87. package/ios/engine/BGMotionDetector.swift +377 -0
  88. package/ios/engine/BGMotionPermissionManager.swift +50 -0
  89. package/ios/engine/BGNativeLogger.swift +48 -0
  90. package/ios/engine/BGNotificaitons.swift +37 -0
  91. package/ios/engine/BGOdometer.swift +66 -0
  92. package/ios/engine/BGPersistenceConfig.swift +29 -0
  93. package/ios/engine/BGPolygonStreamRequest.swift +48 -0
  94. package/ios/engine/BGPowerSaveChangeEvent.swift +12 -0
  95. package/ios/engine/BGPropertySpec.swift +29 -0
  96. package/ios/engine/BGProviderChangeEvent.swift +31 -0
  97. package/ios/engine/BGQueue.swift +50 -0
  98. package/ios/engine/BGRPC.swift +194 -0
  99. package/ios/engine/BGReachability.swift +58 -0
  100. package/ios/engine/BGResultSet.swift +157 -0
  101. package/ios/engine/BGSchedule.swift +228 -0
  102. package/ios/engine/BGScheduleEvent.swift +13 -0
  103. package/ios/engine/BGScheduler.swift +116 -0
  104. package/ios/engine/BGSingleLocationRequest.swift +49 -0
  105. package/ios/engine/BGStreamLocationRequest.swift +42 -0
  106. package/ios/engine/BGTemplate.swift +54 -0
  107. package/ios/engine/BGTimerService.swift +46 -0
  108. package/ios/engine/BGTrackingAudioManager.swift +286 -0
  109. package/ios/engine/BGTrackingService.swift +879 -0
  110. package/ios/engine/BGWatchPositionRequest.swift +63 -0
  111. package/ios/engine/DatabaseQueue.swift +47 -0
  112. package/ios/engine/LogQuery.swift +10 -0
  113. package/ios/engine/SQLQuery.swift +65 -0
  114. package/ios/engine/TransistorAuthorizationToken.swift +182 -0
  115. package/ios/liveactivity/BGLiveTrackingAttributes.swift +52 -0
  116. package/ios/locationpush/BGLocationPushDeliverer.swift +260 -0
  117. package/ios/locationpush/BGLocationPushService.swift +161 -0
  118. package/ios/locationpush/BGLocationPushShared.swift +98 -0
  119. package/ios/locationpush/BGLocationPushSocketClient.swift +198 -0
  120. package/lib/module/NativeBgGeolocation.js +5 -0
  121. package/lib/module/NativeBgGeolocation.js.map +1 -0
  122. package/lib/module/events.js +20 -0
  123. package/lib/module/events.js.map +1 -0
  124. package/lib/module/index.js +706 -0
  125. package/lib/module/index.js.map +1 -0
  126. package/lib/module/package.json +1 -0
  127. package/lib/module/types.js +2 -0
  128. package/lib/module/types.js.map +1 -0
  129. package/lib/typescript/package.json +1 -0
  130. package/lib/typescript/src/NativeBgGeolocation.d.ts +57 -0
  131. package/lib/typescript/src/NativeBgGeolocation.d.ts.map +1 -0
  132. package/lib/typescript/src/events.d.ts +18 -0
  133. package/lib/typescript/src/events.d.ts.map +1 -0
  134. package/lib/typescript/src/index.d.ts +238 -0
  135. package/lib/typescript/src/index.d.ts.map +1 -0
  136. package/lib/typescript/src/types.d.ts +229 -0
  137. package/lib/typescript/src/types.d.ts.map +1 -0
  138. package/package.json +141 -0
  139. package/src/NativeBgGeolocation.ts +236 -0
  140. package/src/events.ts +17 -0
  141. package/src/index.tsx +935 -0
  142. package/src/types.ts +254 -0
@@ -0,0 +1,116 @@
1
+ import Foundation
2
+ import CoreLocation
3
+
4
+ @objc public class BGLocationMetrics: NSObject {
5
+
6
+ @objc public var accuracyCurrent: CLLocationAccuracy = 0
7
+ @objc public var accuracyPrev: CLLocationAccuracy = 0
8
+ @objc public var speed: CLLocationSpeed = 0
9
+ @objc public var speedStability: Double = 0
10
+ @objc public var bearing: CLLocationDirection = 0
11
+ @objc public var bearingStability: Double = 0
12
+ @objc public var course: CLLocationDirection = 0
13
+ @objc public var deltaHeading: Double = 0
14
+ @objc public var headingChangeRate: Double = 0
15
+ @objc public var headingQuality: Double = 0
16
+ @objc public var dt: Double = 0
17
+ @objc public var distanceFilter: CLLocationDistance = 10
18
+ @objc public var jerk: Double = 0
19
+ @objc public var isStationary: Bool = false
20
+ @objc public var isOscillating: Bool = false
21
+ @objc public var stationaryConfidence: Double = 0
22
+ @objc public var impliedSpeed: Double = 0
23
+ @objc public var impliedAnomaly: Bool = false
24
+ @objc public var rollingAverage: Double = 0
25
+ @objc public var raw: Double = 0
26
+ @objc public var effective: Double = 0
27
+ @objc public var accCap: Double = 0
28
+ @objc public var burstCap: Double = 0
29
+ @objc public var kinCap: Double = 0
30
+ @objc public var capCandidate: Double = 0
31
+ }
32
+
33
+ @objc public class BGLocationMetricsEngine: NSObject {
34
+
35
+ @objc public var config: BGLocationFilterConfig?
36
+ @objc public var filterDebug: Bool = false
37
+ @objc public var burstWindow: Double = 60.0
38
+ @objc public var maxBurstDistance: Double = 100.0
39
+ @objc public var maxImpliedSpeed: Double = 514.0
40
+ @objc public var rollingWindow: Int = 10
41
+ @objc public var last: CLLocation?
42
+ @objc public var lastBearingDeg: Double = 0
43
+ @objc public var lastBearingValid: Bool = false
44
+
45
+ private var rbBuffer: [Double] = []
46
+ @objc public var rbCount: Int = 0
47
+ @objc public var rbHead: Int = 0
48
+ @objc public var rbSum: Double = 0
49
+
50
+ @objc public init(withConfig config: BGLocationFilterConfig) {
51
+ self.config = config
52
+ super.init()
53
+ applyConfig(config)
54
+ }
55
+
56
+ @objc public override init() {
57
+ super.init()
58
+ }
59
+
60
+ @objc public func applyConfig(_ config: BGLocationFilterConfig) {
61
+ self.config = config
62
+ filterDebug = config.filterDebug
63
+ burstWindow = config.burstWindow
64
+ maxBurstDistance = config.maxBurstDistance
65
+ maxImpliedSpeed = config.maxImpliedSpeed
66
+ rollingWindow = config.rollingWindow
67
+ rbBuffer = Array(repeating: 0.0, count: rollingWindow)
68
+ }
69
+
70
+ @objc public func computeFor(
71
+ _ current: CLLocation,
72
+ previous: CLLocation?,
73
+ distanceFilter: CLLocationDistance
74
+ ) -> BGLocationMetrics {
75
+ let metrics = BGLocationMetrics()
76
+ metrics.accuracyCurrent = current.horizontalAccuracy
77
+ metrics.speed = current.speed
78
+ metrics.bearing = current.course
79
+ metrics.distanceFilter = distanceFilter
80
+
81
+ if let prev = previous {
82
+ metrics.dt = current.timestamp.timeIntervalSince(prev.timestamp)
83
+ metrics.raw = current.distance(from: prev)
84
+ if metrics.dt > 0 {
85
+ metrics.impliedSpeed = metrics.raw / metrics.dt
86
+ metrics.impliedAnomaly = metrics.impliedSpeed > maxImpliedSpeed
87
+ }
88
+ metrics.deltaHeading = abs(current.course - prev.course)
89
+ }
90
+
91
+ return metrics
92
+ }
93
+
94
+ @objc public func computeStationaryMetrics(_ metrics: BGLocationMetrics, current: CLLocation) {
95
+ metrics.isStationary = metrics.speed < 0.5
96
+ metrics.stationaryConfidence = metrics.isStationary ? 1.0 : 0.0
97
+ }
98
+
99
+ @objc public func reset() {
100
+ last = nil
101
+ lastBearingValid = false
102
+ lastBearingDeg = 0
103
+ rbBuffer = Array(repeating: 0.0, count: rollingWindow)
104
+ rbCount = 0
105
+ rbHead = 0
106
+ rbSum = 0
107
+ }
108
+
109
+ @objc public func onMotionChange(_ isMoving: Bool) {
110
+ reset()
111
+ }
112
+
113
+ @objc public override var description: String {
114
+ return "<BGLocationMetricsEngine rollingWindow=\(rollingWindow)>"
115
+ }
116
+ }
@@ -0,0 +1,459 @@
1
+ import Foundation
2
+ import CoreLocation
3
+
4
+ @objc public class BGSamplingSession: NSObject {
5
+
6
+ @objc public var active: Bool = false
7
+ @objc public var startedAt: Date?
8
+ @objc public var bestLocation: CLLocation?
9
+ @objc public var collected: Int = 0
10
+
11
+ @objc public override init() {
12
+ super.init()
13
+ }
14
+ }
15
+
16
+ @objc public class BGStreamState: NSObject {
17
+
18
+ @objc public var request: BGStreamLocationRequest?
19
+ @objc public var lastEmitAt: Date?
20
+ @objc public var lastEmitted: CLLocation?
21
+ @objc public var minInterval: TimeInterval = 1.0
22
+ @objc public var startedAt: Date?
23
+ @objc public var timeoutTimer: Timer?
24
+
25
+ @objc public override init() {
26
+ super.init()
27
+ }
28
+ }
29
+
30
+ @objc public class BGLocationRequestService: NSObject, CLLocationManagerDelegate {
31
+
32
+ private static var _sharedInstance: BGLocationRequestService?
33
+ private static let instanceLock = NSLock()
34
+
35
+ @objc public class func sharedInstance() -> BGLocationRequestService {
36
+ instanceLock.lock()
37
+ defer { instanceLock.unlock() }
38
+ if _sharedInstance == nil { _sharedInstance = BGLocationRequestService() }
39
+ return _sharedInstance!
40
+ }
41
+
42
+ @objc public class func configureShared(withLocationManager manager: CLLocationManager) {
43
+ sharedInstance().manager = manager
44
+ // The CLLocationManager delegate is owned by BGCLRouter, which forwards
45
+ // location callbacks here. Do NOT assign manager.delegate.
46
+ }
47
+
48
+ // MARK: - State
49
+
50
+ @objc public var manager: CLLocationManager?
51
+ @objc public var session: BGSamplingSession?
52
+ @objc public var streams: [Int: BGStreamState] = [:]
53
+ @objc public var lastLocation: CLLocation?
54
+ @objc public var lastErrorAt: Date?
55
+ @objc public var lastTransientError: Error?
56
+ @objc public var transientErrorCount: Int = 0
57
+ @objc public var transientErrorTimer: Timer?
58
+ @objc public var nextStreamId: Int = 1
59
+ @objc public var sampleCount: Int = 0
60
+
61
+ @objc public var errorGraceBase: TimeInterval = 5.0
62
+ @objc public var errorGraceMax: TimeInterval = 60.0
63
+ @objc public var errorBackoffFactor: Double = 2.0
64
+ @objc public var errorGracePeriod: TimeInterval = 0
65
+
66
+ var lockQueue: DispatchQueue?
67
+ var callbackQueue: DispatchQueue?
68
+ var eventBusTokens: [Int] = []
69
+ var timeoutTimers: [String: Timer] = [:]
70
+
71
+ private var completeListeners: [String: [Int: (CLLocation?) -> Void]] = [:]
72
+ private var errorListeners: [String: [Int: (Error) -> Void]] = [:]
73
+ private var sampleListeners: [String: [Int: (CLLocation) -> Void]] = [:]
74
+ private var listenerToken = 0
75
+
76
+ // Active getCurrentPosition requests, keyed by requestId. The request's own
77
+ // success/failure blocks are driven directly from didUpdateLocations / the
78
+ // timeout — these are the blocks the ObjC bridge passed down.
79
+ private var positionRequests: [String: BGCurrentPositionRequest] = [:]
80
+ private var positionSamples: [String: [CLLocation]] = [:]
81
+
82
+ @objc public override init() {
83
+ super.init()
84
+ lockQueue = DispatchQueue(label: "BGLocationRequestService.lock")
85
+ callbackQueue = DispatchQueue.main
86
+ }
87
+
88
+ @objc public init(locationManager: CLLocationManager) {
89
+ self.manager = locationManager
90
+ super.init()
91
+ lockQueue = DispatchQueue(label: "BGLocationRequestService.lock")
92
+ callbackQueue = DispatchQueue.main
93
+ }
94
+
95
+ @objc public func isActive() -> Bool {
96
+ return session?.active == true || !streams.isEmpty || !positionRequests.isEmpty
97
+ }
98
+
99
+ @objc public func hasActiveStreams() -> Bool {
100
+ return !streams.isEmpty
101
+ }
102
+
103
+ // MARK: - Listener registration
104
+
105
+ @objc public func addCompleteListener(forType type: String, listener: @escaping (CLLocation?) -> Void) -> Int {
106
+ listenerToken += 1
107
+ if completeListeners[type] == nil { completeListeners[type] = [:] }
108
+ completeListeners[type]![listenerToken] = listener
109
+ return listenerToken
110
+ }
111
+
112
+ @objc public func addErrorListener(forType type: String, listener: @escaping (Error) -> Void) -> Int {
113
+ listenerToken += 1
114
+ if errorListeners[type] == nil { errorListeners[type] = [:] }
115
+ errorListeners[type]![listenerToken] = listener
116
+ return listenerToken
117
+ }
118
+
119
+ @objc public func addSampleListener(forType type: String, listener: @escaping (CLLocation) -> Void) -> Int {
120
+ listenerToken += 1
121
+ if sampleListeners[type] == nil { sampleListeners[type] = [:] }
122
+ sampleListeners[type]![listenerToken] = listener
123
+ return listenerToken
124
+ }
125
+
126
+ @objc public func removeCompleteListener(forType type: String, token: Int) {
127
+ completeListeners[type]?.removeValue(forKey: token)
128
+ }
129
+
130
+ @objc public func removeErrorListener(forType type: String, token: Int) {
131
+ errorListeners[type]?.removeValue(forKey: token)
132
+ }
133
+
134
+ @objc public func removeSampleListener(forType type: String, token: Int) {
135
+ sampleListeners[type]?.removeValue(forKey: token)
136
+ }
137
+
138
+ @objc public func removeAllCompleteListeners(forType type: String) {
139
+ completeListeners.removeValue(forKey: type)
140
+ }
141
+
142
+ @objc public func removeAllErrorListeners(forType type: String) {
143
+ errorListeners.removeValue(forKey: type)
144
+ }
145
+
146
+ @objc public func removeAllSampleListeners(forType type: String) {
147
+ sampleListeners.removeValue(forKey: type)
148
+ }
149
+
150
+ // MARK: - Location requests
151
+
152
+ @objc public func requestLocation(_ request: BGCurrentPositionRequest) {
153
+ lockQueue?.async {
154
+ // Serve immediately from a fresh-enough cached fix when allowed.
155
+ if request.maximumAge > 0, let cached = self.lastLocation {
156
+ let age = -cached.timestamp.timeIntervalSinceNow
157
+ let accuracyOK = request.desiredAccuracy <= 0 || cached.horizontalAccuracy <= request.desiredAccuracy
158
+ if age <= (request.maximumAge / 1000.0) && accuracyOK {
159
+ self.deliverPositionLocked(request, location: cached)
160
+ return
161
+ }
162
+ }
163
+ self.positionRequests[request.requestId] = request
164
+ self.positionSamples[request.requestId] = []
165
+ self.startUpdatingLocked()
166
+ self.scheduleTimeoutLocked(forRequest: request)
167
+ }
168
+ }
169
+
170
+ /// Build a BGLocation, optionally persist, and fire the request's success
171
+ /// block on the callback queue. Caller must hold the lockQueue.
172
+ private func deliverPositionLocked(_ request: BGCurrentPositionRequest, location: CLLocation) {
173
+ let tsLocation = BGLocation(location: location, type: "current", extras: request.extras as? [String: Any])
174
+ if request.persist {
175
+ _ = BGLocationDAO.sharedInstance().create(tsLocation, error: nil)
176
+ }
177
+ let success = request.success
178
+ callbackQueue?.async { success?(tsLocation) }
179
+ }
180
+
181
+ /// Resolve an in-flight position request with its best sample. lockQueue.
182
+ private func finishPositionRequestLocked(_ requestId: String, location: CLLocation) {
183
+ guard let request = positionRequests[requestId] else { return }
184
+ clearTimeoutLockedForRequest(requestId)
185
+ positionRequests.removeValue(forKey: requestId)
186
+ positionSamples.removeValue(forKey: requestId)
187
+ deliverPositionLocked(request, location: location)
188
+ maybeStopUpdatesLocked()
189
+ }
190
+
191
+ /// Fail an in-flight position request with a CLError-style code. lockQueue.
192
+ private func failPositionRequestLocked(_ requestId: String, code: Int) {
193
+ guard let request = positionRequests[requestId] else { return }
194
+ clearTimeoutLockedForRequest(requestId)
195
+ positionRequests.removeValue(forKey: requestId)
196
+ positionSamples.removeValue(forKey: requestId)
197
+ let failure = request.failure
198
+ callbackQueue?.async { failure?(code) }
199
+ maybeStopUpdatesLocked()
200
+ }
201
+
202
+ @objc public func startStream(_ request: BGStreamLocationRequest) -> Int {
203
+ let streamId = nextStreamId
204
+ nextStreamId += 1
205
+ let state = BGStreamState()
206
+ state.request = request
207
+ state.startedAt = Date()
208
+ state.minInterval = TimeInterval(request.interval) / 1000.0
209
+ lockQueue?.async {
210
+ self.streams[streamId] = state
211
+ self.startUpdatingLocked()
212
+ }
213
+ return streamId
214
+ }
215
+
216
+ @objc public func stopStream(_ streamId: Int) {
217
+ lockQueue?.async {
218
+ self.streams.removeValue(forKey: streamId)
219
+ self.maybeStopUpdatesLocked()
220
+ }
221
+ }
222
+
223
+ @objc public func stopAllStreams() {
224
+ lockQueue?.async {
225
+ self.streams.removeAll()
226
+ self.maybeStopUpdatesLocked()
227
+ }
228
+ }
229
+
230
+ @objc public func cancelAllRequests() {
231
+ lockQueue?.async {
232
+ self.session = nil
233
+ self.streams.removeAll()
234
+ self.stopUpdatingLocked()
235
+ self.cancelAllTimeouts()
236
+ }
237
+ }
238
+
239
+ @objc public func cancelRequest(_ requestId: String) {
240
+ lockQueue?.async {
241
+ self.timeoutTimers[requestId]?.invalidate()
242
+ self.timeoutTimers.removeValue(forKey: requestId)
243
+ self.maybeEndSessionLocked()
244
+ }
245
+ }
246
+
247
+ @objc public func cancelLockedRequest(_ requestId: String, error: Error?) {
248
+ timeoutTimers[requestId]?.invalidate()
249
+ timeoutTimers.removeValue(forKey: requestId)
250
+ if let error = error {
251
+ emitError(error, forRequest: requestId)
252
+ }
253
+ maybeEndSessionLocked()
254
+ }
255
+
256
+ // MARK: - Update control
257
+
258
+ @objc public func startUpdatingLocked() {
259
+ DispatchQueue.main.async {
260
+ self.manager?.startUpdatingLocation()
261
+ }
262
+ }
263
+
264
+ @objc public func stopUpdatingLocked() {
265
+ DispatchQueue.main.async {
266
+ self.manager?.stopUpdatingLocation()
267
+ }
268
+ }
269
+
270
+ @objc public func maybeStopUpdatesLocked() {
271
+ guard !isActive() else { return }
272
+ // The CLLocationManager is shared with BGTrackingService. If tracking is
273
+ // running, hand control back to it rather than blindly stopping updates
274
+ // (which would silently break active background tracking).
275
+ let tracking = BGTrackingService.sharedInstance()
276
+ if tracking.isEnabled {
277
+ tracking.restoreUpdatingState()
278
+ } else {
279
+ stopUpdatingLocked()
280
+ }
281
+ }
282
+
283
+ @objc public func maybeEndSessionLocked() {
284
+ if session?.active == true && (timeoutTimers.isEmpty) {
285
+ endSessionLocked()
286
+ }
287
+ }
288
+
289
+ @objc public func endSessionLocked() {
290
+ session?.active = false
291
+ session = nil
292
+ maybeStopUpdatesLocked()
293
+ }
294
+
295
+ // MARK: - Timeout management
296
+
297
+ @objc public func scheduleTimeoutLocked(forRequest request: BGCurrentPositionRequest) {
298
+ let timeout = request.timeout
299
+ guard timeout > 0 else { return }
300
+ let id = request.requestId
301
+ DispatchQueue.main.async {
302
+ let timer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in
303
+ self?.lockQueue?.async {
304
+ guard let self = self else { return }
305
+ // On timeout, return the best sample collected so far; only
306
+ // fail (code 408) if nothing arrived at all.
307
+ if let best = (self.positionSamples[id] ?? []).min(by: { $0.horizontalAccuracy < $1.horizontalAccuracy }) {
308
+ self.finishPositionRequestLocked(id, location: best)
309
+ } else {
310
+ self.failPositionRequestLocked(id, code: 408)
311
+ }
312
+ }
313
+ }
314
+ // Keep all timeoutTimers mutations on lockQueue (clear runs there too).
315
+ self.lockQueue?.async { self.timeoutTimers[id] = timer }
316
+ }
317
+ }
318
+
319
+ @objc public func clearTimeoutLockedForRequest(_ requestId: String) {
320
+ if let timer = timeoutTimers.removeValue(forKey: requestId) {
321
+ DispatchQueue.main.async { timer.invalidate() }
322
+ }
323
+ }
324
+
325
+ func cancelAllTimeouts() {
326
+ for (_, timer) in timeoutTimers { timer.invalidate() }
327
+ timeoutTimers.removeAll()
328
+ }
329
+
330
+ // MARK: - Satisfaction
331
+
332
+ @objc public func trySatisfyRequest(_ request: BGCurrentPositionRequest, with location: CLLocation) -> Bool {
333
+ let age = -location.timestamp.timeIntervalSinceNow
334
+ if age > request.maximumAge { return false }
335
+ if location.horizontalAccuracy > request.desiredAccuracy && request.desiredAccuracy > 0 { return false }
336
+ completeRequest(request.requestId, success: true, location: location, error: nil)
337
+ return true
338
+ }
339
+
340
+ @objc public func trySatisfyRequestsLockedWith(_ location: CLLocation) {
341
+ let id = session?.active == true ? "session" : nil
342
+ if let id = id {
343
+ sampleCount += 1
344
+ emitSample(location, forRequest: id)
345
+ }
346
+ }
347
+
348
+ @objc public func completeRequest(_ requestId: String, success: Bool, location: CLLocation?, error: Error?) {
349
+ clearTimeoutLockedForRequest(requestId)
350
+ if success {
351
+ emitComplete(location, forRequest: requestId)
352
+ } else if let error = error {
353
+ emitError(error, forRequest: requestId)
354
+ }
355
+ maybeEndSessionLocked()
356
+ }
357
+
358
+ // MARK: - Emission
359
+
360
+ @objc public func emitComplete(_ location: CLLocation?, forRequest requestId: String) {
361
+ for (_, listener) in (completeListeners[requestId] ?? [:]) {
362
+ callbackQueue?.async { listener(location) }
363
+ }
364
+ }
365
+
366
+ @objc public func emitError(_ error: Error, forRequest requestId: String) {
367
+ for (_, listener) in (errorListeners[requestId] ?? [:]) {
368
+ callbackQueue?.async { listener(error) }
369
+ }
370
+ }
371
+
372
+ @objc public func emitSample(_ location: CLLocation, forRequest requestId: String) {
373
+ for (_, listener) in (sampleListeners[requestId] ?? [:]) {
374
+ callbackQueue?.async { listener(location) }
375
+ }
376
+ }
377
+
378
+ // MARK: - Transient error handling
379
+
380
+ @objc public func startTransientErrorGraceTimerLocked() {
381
+ let period = min(errorGraceBase * pow(errorBackoffFactor, Double(transientErrorCount)), errorGraceMax)
382
+ errorGracePeriod = period
383
+ DispatchQueue.main.async {
384
+ self.transientErrorTimer?.invalidate()
385
+ self.transientErrorTimer = Timer.scheduledTimer(withTimeInterval: period, repeats: false) { [weak self] _ in
386
+ self?.cancelTransientErrorGraceTimerLocked()
387
+ self?.transientErrorCount = 0
388
+ }
389
+ }
390
+ }
391
+
392
+ @objc public func cancelTransientErrorGraceTimerLocked() {
393
+ transientErrorTimer?.invalidate()
394
+ transientErrorTimer = nil
395
+ }
396
+
397
+ @objc public func currentGraceWindowLocked() -> TimeInterval {
398
+ return errorGracePeriod
399
+ }
400
+
401
+ // MARK: - CLLocationManagerDelegate
402
+
403
+ @objc public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
404
+ guard let location = locations.last else { return }
405
+ lastLocation = location
406
+ lockQueue?.async {
407
+ // getCurrentPosition: collect samples; resolve once a fix is accurate
408
+ // enough or the requested sample count is reached. Iterate a snapshot
409
+ // because finishPositionRequestLocked mutates positionRequests.
410
+ for (id, request) in Array(self.positionRequests) {
411
+ self.positionSamples[id, default: []].append(location)
412
+ let samples = self.positionSamples[id] ?? [location]
413
+ let best = samples.min(by: { $0.horizontalAccuracy < $1.horizontalAccuracy }) ?? location
414
+ let accuracyOK = request.desiredAccuracy <= 0 || best.horizontalAccuracy <= request.desiredAccuracy
415
+ if accuracyOK || samples.count >= max(1, request.samples) {
416
+ self.finishPositionRequestLocked(id, location: best)
417
+ }
418
+ }
419
+
420
+ // watchPosition streams: drive each stream's success block, throttled
421
+ // by its minInterval.
422
+ for (_, state) in self.streams {
423
+ let now = Date()
424
+ if let lastEmit = state.lastEmitAt {
425
+ guard now.timeIntervalSince(lastEmit) >= state.minInterval else { continue }
426
+ }
427
+ state.lastEmitAt = now
428
+ state.lastEmitted = location
429
+ let success = state.request?.success
430
+ self.callbackQueue?.async { success?(location) }
431
+ }
432
+ }
433
+ }
434
+
435
+ @objc public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
436
+ lastErrorAt = Date()
437
+ lastTransientError = error
438
+ lockQueue?.async {
439
+ self.transientErrorCount += 1
440
+ self.startTransientErrorGraceTimerLocked()
441
+ }
442
+ }
443
+
444
+ @available(iOS 14.0, *)
445
+ @objc public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
446
+ }
447
+
448
+ @objc public func locationManagerDidPauseLocationUpdates(_ manager: CLLocationManager) {
449
+ }
450
+
451
+ // MARK: - CL mutation
452
+
453
+ @objc public func _mutateCLAsync(_ block: @escaping (CLLocationManager) -> Void) {
454
+ DispatchQueue.main.async {
455
+ guard let mgr = self.manager else { return }
456
+ block(mgr)
457
+ }
458
+ }
459
+ }
@@ -0,0 +1,14 @@
1
+ import Foundation
2
+ import CoreLocation
3
+
4
+ @objc public class BGLocationSatisfier: NSObject {
5
+
6
+ @objc public var location: CLLocation
7
+ @objc public var satisfied: Bool = false
8
+ @objc public var cancel: (() -> Void)?
9
+
10
+ @objc public init(location: CLLocation) {
11
+ self.location = location
12
+ super.init()
13
+ }
14
+ }
@@ -0,0 +1,27 @@
1
+ import Foundation
2
+
3
+ @objc public final class BGLocationStreamEvent: NSObject {
4
+
5
+ @objc public private(set) var streamId: Int
6
+ @objc public private(set) var locationEvent: BGLocationEvent?
7
+
8
+ @objc public init(streamId: Int, locationEvent: BGLocationEvent?) {
9
+ self.streamId = streamId
10
+ self.locationEvent = locationEvent
11
+ super.init()
12
+ }
13
+
14
+ @objc public func toDictionary() -> [String: Any] {
15
+ let dict = NSMutableDictionary()
16
+ if let locationEvent = locationEvent {
17
+ dict["location"] = locationEvent.toDictionary()
18
+ }
19
+ dict["streamId"] = streamId
20
+ return dict as! [String: Any]
21
+ }
22
+
23
+ public override var description: String {
24
+ return String(format: "<BGLocationStreamEvent id=%ld event=%@>",
25
+ streamId, String(describing: locationEvent))
26
+ }
27
+ }