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.
- package/BgGeolocation.podspec +39 -0
- package/LICENSE +20 -0
- package/README.md +366 -0
- package/android/build.gradle +69 -0
- package/android/src/main/AndroidManifest.xml +53 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationActivityRecognitionReceiver.kt +116 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationBootReceiver.kt +44 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationForegroundService.kt +373 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationGeofenceReceiver.kt +55 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationHeadlessTask.kt +138 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationModule.kt +1030 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationMotionStateMachine.kt +159 -0
- package/android/src/main/java/com/bggeolocation/BgGeolocationPackage.kt +31 -0
- package/android/src/main/res/drawable/bg_geo_notification.xml +9 -0
- package/ios/BgGeolocation.h +14 -0
- package/ios/BgGeolocation.mm +709 -0
- package/ios/engine/AtomicBoolean.swift +48 -0
- package/ios/engine/BGActivityChangeEvent.swift +20 -0
- package/ios/engine/BGActivityConfig.swift +71 -0
- package/ios/engine/BGAppConfig.swift +92 -0
- package/ios/engine/BGAppState.swift +147 -0
- package/ios/engine/BGAuthorization.swift +85 -0
- package/ios/engine/BGAuthorizationAlertPresenter.swift +39 -0
- package/ios/engine/BGAuthorizationConfig.swift +50 -0
- package/ios/engine/BGAuthorizationEvent.swift +40 -0
- package/ios/engine/BGBackgroundTaskManager.swift +143 -0
- package/ios/engine/BGCLRouter.swift +101 -0
- package/ios/engine/BGCallback.swift +19 -0
- package/ios/engine/BGConfig.swift +440 -0
- package/ios/engine/BGConfigModuleBase.swift +180 -0
- package/ios/engine/BGConfigOLD.swift +582 -0
- package/ios/engine/BGConnectivityChangeEvent.swift +15 -0
- package/ios/engine/BGCrashDetector.swift +122 -0
- package/ios/engine/BGCurrentPositionRequest.swift +87 -0
- package/ios/engine/BGDataStore.swift +75 -0
- package/ios/engine/BGDatabase.swift +677 -0
- package/ios/engine/BGDatabasePool.swift +220 -0
- package/ios/engine/BGDatabaseQueue.swift +215 -0
- package/ios/engine/BGDateUtils.swift +26 -0
- package/ios/engine/BGDeviceInfo.swift +54 -0
- package/ios/engine/BGDeviceManager.swift +65 -0
- package/ios/engine/BGEnabledChangeEvent.swift +11 -0
- package/ios/engine/BGEnv.swift +17 -0
- package/ios/engine/BGEventBus.swift +83 -0
- package/ios/engine/BGEventManager.swift +169 -0
- package/ios/engine/BGEventNames.swift +51 -0
- package/ios/engine/BGGeofence.swift +233 -0
- package/ios/engine/BGGeofenceDAO.swift +152 -0
- package/ios/engine/BGGeofenceEvent.swift +42 -0
- package/ios/engine/BGGeofenceLocationRequest.swift +94 -0
- package/ios/engine/BGGeofenceManager.swift +315 -0
- package/ios/engine/BGGeofenceTransition.swift +97 -0
- package/ios/engine/BGGeofencesChangeEvent.swift +26 -0
- package/ios/engine/BGGeolocationConfig.swift +136 -0
- package/ios/engine/BGHeartbeatEvent.swift +31 -0
- package/ios/engine/BGHeartbeatService.swift +51 -0
- package/ios/engine/BGHttpConfig.swift +105 -0
- package/ios/engine/BGHttpErrorCodes.swift +63 -0
- package/ios/engine/BGHttpEvent.swift +34 -0
- package/ios/engine/BGHttpRequest.swift +126 -0
- package/ios/engine/BGHttpResponse.swift +93 -0
- package/ios/engine/BGHttpService.swift +428 -0
- package/ios/engine/BGKalmanFilter.swift +105 -0
- package/ios/engine/BGLMActionNames.swift +55 -0
- package/ios/engine/BGLicenseManager.swift +26 -0
- package/ios/engine/BGLiveActivityManager.swift +327 -0
- package/ios/engine/BGLocation.swift +311 -0
- package/ios/engine/BGLocationAuthorization.swift +427 -0
- package/ios/engine/BGLocationDAO.swift +252 -0
- package/ios/engine/BGLocationErrors.swift +28 -0
- package/ios/engine/BGLocationEvent.swift +43 -0
- package/ios/engine/BGLocationFilter.swift +82 -0
- package/ios/engine/BGLocationFilterConfig.swift +57 -0
- package/ios/engine/BGLocationHelper.swift +54 -0
- package/ios/engine/BGLocationManager.swift +662 -0
- package/ios/engine/BGLocationMetricsEngine.swift +116 -0
- package/ios/engine/BGLocationRequestService.swift +459 -0
- package/ios/engine/BGLocationSatisfier.swift +14 -0
- package/ios/engine/BGLocationStreamEvent.swift +27 -0
- package/ios/engine/BGLog.swift +337 -0
- package/ios/engine/BGLogLevel.swift +26 -0
- package/ios/engine/BGLoggerConfig.swift +60 -0
- package/ios/engine/BGMotionActivity.swift +31 -0
- package/ios/engine/BGMotionActivityClassifier.swift +108 -0
- package/ios/engine/BGMotionActivityManagerAdapter.swift +40 -0
- package/ios/engine/BGMotionActivitySource.swift +46 -0
- package/ios/engine/BGMotionDetector.swift +377 -0
- package/ios/engine/BGMotionPermissionManager.swift +50 -0
- package/ios/engine/BGNativeLogger.swift +48 -0
- package/ios/engine/BGNotificaitons.swift +37 -0
- package/ios/engine/BGOdometer.swift +66 -0
- package/ios/engine/BGPersistenceConfig.swift +29 -0
- package/ios/engine/BGPolygonStreamRequest.swift +48 -0
- package/ios/engine/BGPowerSaveChangeEvent.swift +12 -0
- package/ios/engine/BGPropertySpec.swift +29 -0
- package/ios/engine/BGProviderChangeEvent.swift +31 -0
- package/ios/engine/BGQueue.swift +50 -0
- package/ios/engine/BGRPC.swift +194 -0
- package/ios/engine/BGReachability.swift +58 -0
- package/ios/engine/BGResultSet.swift +157 -0
- package/ios/engine/BGSchedule.swift +228 -0
- package/ios/engine/BGScheduleEvent.swift +13 -0
- package/ios/engine/BGScheduler.swift +116 -0
- package/ios/engine/BGSingleLocationRequest.swift +49 -0
- package/ios/engine/BGStreamLocationRequest.swift +42 -0
- package/ios/engine/BGTemplate.swift +54 -0
- package/ios/engine/BGTimerService.swift +46 -0
- package/ios/engine/BGTrackingAudioManager.swift +286 -0
- package/ios/engine/BGTrackingService.swift +879 -0
- package/ios/engine/BGWatchPositionRequest.swift +63 -0
- package/ios/engine/DatabaseQueue.swift +47 -0
- package/ios/engine/LogQuery.swift +10 -0
- package/ios/engine/SQLQuery.swift +65 -0
- package/ios/engine/TransistorAuthorizationToken.swift +182 -0
- package/ios/liveactivity/BGLiveTrackingAttributes.swift +52 -0
- package/ios/locationpush/BGLocationPushDeliverer.swift +260 -0
- package/ios/locationpush/BGLocationPushService.swift +161 -0
- package/ios/locationpush/BGLocationPushShared.swift +98 -0
- package/ios/locationpush/BGLocationPushSocketClient.swift +198 -0
- package/lib/module/NativeBgGeolocation.js +5 -0
- package/lib/module/NativeBgGeolocation.js.map +1 -0
- package/lib/module/events.js +20 -0
- package/lib/module/events.js.map +1 -0
- package/lib/module/index.js +706 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeBgGeolocation.d.ts +57 -0
- package/lib/typescript/src/NativeBgGeolocation.d.ts.map +1 -0
- package/lib/typescript/src/events.d.ts +18 -0
- package/lib/typescript/src/events.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +238 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +229 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +141 -0
- package/src/NativeBgGeolocation.ts +236 -0
- package/src/events.ts +17 -0
- package/src/index.tsx +935 -0
- package/src/types.ts +254 -0
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreLocation
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
@objc public class BGTrackingService: NSObject, CLLocationManagerDelegate {
|
|
6
|
+
|
|
7
|
+
private static var _sharedInstance: BGTrackingService?
|
|
8
|
+
private static let instanceLock = NSLock()
|
|
9
|
+
|
|
10
|
+
@objc public class func sharedInstance() -> BGTrackingService {
|
|
11
|
+
instanceLock.lock()
|
|
12
|
+
defer { instanceLock.unlock() }
|
|
13
|
+
if _sharedInstance == nil { _sharedInstance = BGTrackingService() }
|
|
14
|
+
return _sharedInstance!
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// MARK: - State
|
|
18
|
+
|
|
19
|
+
@objc public var locationManager: CLLocationManager?
|
|
20
|
+
@objc public var locationFilter: BGLocationFilter?
|
|
21
|
+
@objc public var locationMetrics: BGLocationMetricsEngine?
|
|
22
|
+
@objc public var beforeInsertBlock: ((BGLocation) -> BGLocation?)?
|
|
23
|
+
|
|
24
|
+
@objc public var isEnabled: Bool = false
|
|
25
|
+
@objc public var isMoving: Bool = false
|
|
26
|
+
@objc public var isUpdatingLocation: Bool = false
|
|
27
|
+
@objc public var isMonitoringSignificantLocationChanges: Bool = false
|
|
28
|
+
@objc public var isMonitoringBackgroundFetch: Bool = false
|
|
29
|
+
@objc public var didReceiveFirstLocation: Bool = false
|
|
30
|
+
@objc public var isAwaitingAuthorizationForChangePace: Bool = false
|
|
31
|
+
|
|
32
|
+
@objc public var lastLocation: CLLocation?
|
|
33
|
+
@objc public var lastGoodLocation: CLLocation?
|
|
34
|
+
@objc public var lastOdometerLocation: CLLocation?
|
|
35
|
+
@objc public var bestLocation: CLLocation?
|
|
36
|
+
@objc public var stationaryLocation: CLLocation?
|
|
37
|
+
@objc public var prevStationaryLocation: CLLocation?
|
|
38
|
+
@objc public var stationaryRegion: CLCircularRegion?
|
|
39
|
+
@objc public var motionChangeRequest: BGCurrentPositionRequest?
|
|
40
|
+
@objc public var motionStateRequest: BGCurrentPositionRequest?
|
|
41
|
+
@objc public var currentMotionActivity: BGMotionActivity?
|
|
42
|
+
@objc public var locationError: Error?
|
|
43
|
+
@objc public var pendingIsMovingForAuthorization: NSNumber?
|
|
44
|
+
@objc public var preventSuspendTask: Any?
|
|
45
|
+
|
|
46
|
+
@objc public var distanceFilter: CLLocationDistance = 10
|
|
47
|
+
@objc public var medianLocationAccuracy: CLLocationAccuracy = 0
|
|
48
|
+
@objc public var lastLocationTimeInterval: TimeInterval = 0
|
|
49
|
+
@objc public var startedAcquiringLocationAt: Date?
|
|
50
|
+
@objc public var stopUpdatingLocationAt: Date?
|
|
51
|
+
@objc public var stoppedAt: Date?
|
|
52
|
+
@objc public var stopOnNextStationary: Bool = false
|
|
53
|
+
|
|
54
|
+
private var configChangeBufferTimer: Timer?
|
|
55
|
+
private var startDetectionTimer: Timer?
|
|
56
|
+
private var stopDetectionDelayTimer: Timer?
|
|
57
|
+
private var stopTimeoutTimer: Timer?
|
|
58
|
+
private var motionTriggerTimer: Timer?
|
|
59
|
+
private var motionObserver: NSObjectProtocol?
|
|
60
|
+
private var locationAccuracyQueue: [CLLocationAccuracy] = []
|
|
61
|
+
private let stateQueue = DispatchQueue(label: "BGTrackingService.state", attributes: .concurrent)
|
|
62
|
+
private let clMutationQueue = DispatchQueue(label: "BGTrackingService.cl")
|
|
63
|
+
|
|
64
|
+
@objc public override init() {
|
|
65
|
+
super.init()
|
|
66
|
+
locationFilter = BGLocationFilter()
|
|
67
|
+
locationMetrics = BGLocationMetricsEngine()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// MARK: - Start / Stop
|
|
71
|
+
|
|
72
|
+
@objc public func start(_ isMoving: Bool) {
|
|
73
|
+
guard !isEnabled else { return }
|
|
74
|
+
isEnabled = true
|
|
75
|
+
self.isMoving = isMoving
|
|
76
|
+
didReceiveFirstLocation = false
|
|
77
|
+
loadStationaryLocation()
|
|
78
|
+
|
|
79
|
+
registerConfigChangeHandlers()
|
|
80
|
+
registerEventBusHandlers()
|
|
81
|
+
|
|
82
|
+
_mutateCL { mgr in
|
|
83
|
+
// NOTE: the delegate is owned solely by BGCLRouter (see
|
|
84
|
+
// BGLocationManager.setupCoreLocation). Do NOT assign it here.
|
|
85
|
+
self.configureLocationManager(mgr)
|
|
86
|
+
self.startMonitoringSignificantLocationChanges()
|
|
87
|
+
if let stationary = self.stationaryLocation, !isMoving {
|
|
88
|
+
self.startMonitoringStationaryRegion(stationary, radius: BGConfig.sharedInstance().geolocation.stationaryRadius)
|
|
89
|
+
}
|
|
90
|
+
if BGConfig.sharedInstance().geolocation.useSignificantChangesOnly {
|
|
91
|
+
self.stopUpdatingLocation()
|
|
92
|
+
} else {
|
|
93
|
+
self.startUpdatingLocation()
|
|
94
|
+
}
|
|
95
|
+
if isMoving {
|
|
96
|
+
self.beginStartDetection(isMoving)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
startMotionMonitoring()
|
|
100
|
+
evaluateHeartbeatTimer()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@objc public func stop() {
|
|
104
|
+
guard isEnabled else { return }
|
|
105
|
+
isEnabled = false
|
|
106
|
+
stopDetection()
|
|
107
|
+
stopMotionMonitoring()
|
|
108
|
+
_mutateCL { mgr in
|
|
109
|
+
self.stopUpdatingLocation()
|
|
110
|
+
self.stopMonitoringSignificantLocationChanges()
|
|
111
|
+
self.stopMonitoringStationaryRegion()
|
|
112
|
+
}
|
|
113
|
+
stopHeartbeat()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// MARK: - Motion activity monitoring
|
|
117
|
+
//
|
|
118
|
+
// Drives autonomous stationary<->moving transitions from CMMotionActivity
|
|
119
|
+
// (the motion coprocessor). Previously BGMotionDetector was never started
|
|
120
|
+
// and its updates were observed by nobody, so the only way to detect motion
|
|
121
|
+
// onset was a coarse stationary-region exit. Now the detector runs while
|
|
122
|
+
// tracking is enabled and feeds onMotionActivityChange -> changePace.
|
|
123
|
+
|
|
124
|
+
@objc public func startMotionMonitoring() {
|
|
125
|
+
let detector = BGMotionDetector.sharedInstance()
|
|
126
|
+
detector.start()
|
|
127
|
+
if motionObserver == nil {
|
|
128
|
+
motionObserver = NotificationCenter.default.addObserver(
|
|
129
|
+
forName: NSNotification.Name("BGMotionDetectorDidUpdateActivity"),
|
|
130
|
+
object: nil, queue: .main
|
|
131
|
+
) { [weak self] note in
|
|
132
|
+
if let activity = note.object as? BGMotionActivity {
|
|
133
|
+
self?.onMotionActivityChange(activity)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@objc public func stopMotionMonitoring() {
|
|
140
|
+
BGMotionDetector.sharedInstance().stop()
|
|
141
|
+
if let obs = motionObserver {
|
|
142
|
+
NotificationCenter.default.removeObserver(obs)
|
|
143
|
+
motionObserver = nil
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// MARK: - CL mutation
|
|
148
|
+
|
|
149
|
+
@objc public func _mutateCL(_ block: @escaping (CLLocationManager) -> Void) {
|
|
150
|
+
clMutationQueue.async {
|
|
151
|
+
guard let mgr = self.locationManager else { return }
|
|
152
|
+
DispatchQueue.main.async { block(mgr) }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@objc public func configureLocationManager(_ manager: CLLocationManager) {
|
|
157
|
+
let config = BGConfig.sharedInstance().geolocation
|
|
158
|
+
manager.desiredAccuracy = config.desiredAccuracy
|
|
159
|
+
manager.distanceFilter = config.distanceFilter
|
|
160
|
+
manager.activityType = config.activityType
|
|
161
|
+
manager.pausesLocationUpdatesAutomatically = config.pausesLocationUpdatesAutomatically
|
|
162
|
+
if #available(iOS 9.0, *) {
|
|
163
|
+
manager.allowsBackgroundLocationUpdates = BGAppState.sharedInstance().hasBackgroundLocationMode()
|
|
164
|
+
}
|
|
165
|
+
if #available(iOS 11.0, *) {
|
|
166
|
+
manager.showsBackgroundLocationIndicator = config.showsBackgroundLocationIndicator
|
|
167
|
+
}
|
|
168
|
+
distanceFilter = config.distanceFilter
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// MARK: - Location updating
|
|
172
|
+
|
|
173
|
+
@objc public func startUpdatingLocation() {
|
|
174
|
+
guard !isUpdatingLocation else { return }
|
|
175
|
+
_mutateCL { mgr in
|
|
176
|
+
mgr.startUpdatingLocation()
|
|
177
|
+
self.isUpdatingLocation = true
|
|
178
|
+
self.startedAcquiringLocationAt = Date()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@objc public func stopUpdatingLocation() {
|
|
183
|
+
guard isUpdatingLocation else { return }
|
|
184
|
+
_mutateCL { mgr in
|
|
185
|
+
mgr.stopUpdatingLocation()
|
|
186
|
+
self.isUpdatingLocation = false
|
|
187
|
+
self.stopUpdatingLocationAt = Date()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Re-assert tracking's intended continuous-updates state on the shared
|
|
192
|
+
/// CLLocationManager. Called by BGLocationRequestService after a
|
|
193
|
+
/// getCurrentPosition fix completes, so a one-off fix powering up GPS does
|
|
194
|
+
/// not leave it running (or off) against tracking's wishes.
|
|
195
|
+
@objc public func restoreUpdatingState() {
|
|
196
|
+
guard isEnabled else { return }
|
|
197
|
+
let geo = BGConfig.sharedInstance().geolocation
|
|
198
|
+
_mutateCL { mgr in
|
|
199
|
+
if geo.disableStopDetection || (self.isMoving && !geo.useSignificantChangesOnly) {
|
|
200
|
+
mgr.startUpdatingLocation()
|
|
201
|
+
self.isUpdatingLocation = true
|
|
202
|
+
} else {
|
|
203
|
+
mgr.stopUpdatingLocation()
|
|
204
|
+
self.isUpdatingLocation = false
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@objc public func startMonitoringSignificantLocationChanges() {
|
|
210
|
+
guard !isMonitoringSignificantLocationChanges else { return }
|
|
211
|
+
_mutateCL { mgr in
|
|
212
|
+
mgr.startMonitoringSignificantLocationChanges()
|
|
213
|
+
self.isMonitoringSignificantLocationChanges = true
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@objc public func stopMonitoringSignificantLocationChanges() {
|
|
218
|
+
guard isMonitoringSignificantLocationChanges else { return }
|
|
219
|
+
_mutateCL { mgr in
|
|
220
|
+
mgr.stopMonitoringSignificantLocationChanges()
|
|
221
|
+
self.isMonitoringSignificantLocationChanges = false
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@objc public func startMonitoringBackgroundFetch() {
|
|
226
|
+
isMonitoringBackgroundFetch = true
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@objc public func stopMonitoringBackgroundFetch() {
|
|
230
|
+
isMonitoringBackgroundFetch = false
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// MARK: - Stationary region
|
|
234
|
+
|
|
235
|
+
@objc public func startMonitoringStationaryRegion(_ location: CLLocation, radius: CLLocationDistance) {
|
|
236
|
+
// iOS CLCircularRegion monitoring is cell/wifi-coarse and unreliable
|
|
237
|
+
// below ~100-150m: a small radius either never fires the real exit or
|
|
238
|
+
// fires spurious exits from GPS jitter. The stationary region is the
|
|
239
|
+
// primary mechanism that wakes a suspended or system-terminated app when the user
|
|
240
|
+
// departs, so clamp it to a dependable minimum (decoupled from the
|
|
241
|
+
// motion stop/start `stationaryRadius` distance logic).
|
|
242
|
+
let minReliableRadius: CLLocationDistance = 200.0
|
|
243
|
+
let effectiveRadius = max(radius, minReliableRadius)
|
|
244
|
+
let region = CLCircularRegion(center: location.coordinate, radius: effectiveRadius, identifier: "BGStationary")
|
|
245
|
+
region.notifyOnExit = true
|
|
246
|
+
stationaryRegion = region
|
|
247
|
+
_mutateCL { mgr in
|
|
248
|
+
mgr.startMonitoring(for: region)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func stopMonitoringStationaryRegion() {
|
|
253
|
+
if let region = stationaryRegion {
|
|
254
|
+
_mutateCL { mgr in
|
|
255
|
+
mgr.stopMonitoring(for: region)
|
|
256
|
+
}
|
|
257
|
+
stationaryRegion = nil
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@objc public func stationaryRegionContains(location: CLLocation) -> Bool {
|
|
262
|
+
guard let region = stationaryRegion else { return false }
|
|
263
|
+
return region.contains(location.coordinate)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@objc public func locationIsBeyondStationaryRegion(_ location: CLLocation) -> Bool {
|
|
267
|
+
guard let region = stationaryRegion else { return true }
|
|
268
|
+
return !region.contains(location.coordinate)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// MARK: - Pace change
|
|
272
|
+
|
|
273
|
+
@objc public func changePace(_ moving: Bool) {
|
|
274
|
+
if isEnabled {
|
|
275
|
+
setMoving(moving)
|
|
276
|
+
} else {
|
|
277
|
+
pendingIsMovingForAuthorization = NSNumber(value: moving)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private func setMoving(_ moving: Bool) {
|
|
282
|
+
// Continuous keep-alive ("ride app") mode: never relinquish the moving
|
|
283
|
+
// state, so GPS stays on and the app stays alive in the background with
|
|
284
|
+
// the location indicator. Ignore stationary transitions entirely.
|
|
285
|
+
if !moving && BGConfig.sharedInstance().geolocation.disableStopDetection {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
let wasMoving = isMoving
|
|
289
|
+
isMoving = moving
|
|
290
|
+
|
|
291
|
+
if moving && !wasMoving {
|
|
292
|
+
beginStartDetection(moving)
|
|
293
|
+
} else if !moving && wasMoving {
|
|
294
|
+
beginStopDetection()
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// MARK: - Start detection
|
|
299
|
+
|
|
300
|
+
@objc public func beginStartDetection() {
|
|
301
|
+
beginStartDetection(true)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@objc public func beginStartDetection(_ moving: Bool) {
|
|
305
|
+
endStopDetection()
|
|
306
|
+
stopMonitoringStationaryRegion()
|
|
307
|
+
startUpdatingLocation()
|
|
308
|
+
startMotionTriggerTimer()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@objc public func endStartDetection() {
|
|
312
|
+
startDetectionTimer?.invalidate()
|
|
313
|
+
startDetectionTimer = nil
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@objc public func detectStartMotion(_ location: CLLocation) {
|
|
317
|
+
guard !isMoving else { return }
|
|
318
|
+
isMoving = true
|
|
319
|
+
onMotionChangeSuccess(true, location: location, didPersist: false)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// MARK: - Stop detection
|
|
323
|
+
|
|
324
|
+
@objc public func beginStopDetection() {
|
|
325
|
+
stopDetectionDelayTimer?.invalidate()
|
|
326
|
+
let config = BGConfig.sharedInstance()
|
|
327
|
+
let stopTimeout = config.geolocation.stopTimeout * 60
|
|
328
|
+
|
|
329
|
+
stopDetectionDelayTimer = Timer.scheduledTimer(withTimeInterval: stopTimeout, repeats: false) { [weak self] _ in
|
|
330
|
+
self?.detectStopMotion(nil)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@objc public func endStopDetection() {
|
|
335
|
+
stopDetectionDelayTimer?.invalidate()
|
|
336
|
+
stopDetectionDelayTimer = nil
|
|
337
|
+
stopTimeoutTimer?.invalidate()
|
|
338
|
+
stopTimeoutTimer = nil
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
func stopDetection() {
|
|
342
|
+
endStartDetection()
|
|
343
|
+
endStopDetection()
|
|
344
|
+
stopMotionTriggerTimer()
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
@objc public func beginStopDetectionDelayTimer() {
|
|
348
|
+
beginStopDetection()
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@objc public func detectStopMotion(_ location: CLLocation?) {
|
|
352
|
+
guard isMoving else { return }
|
|
353
|
+
isMoving = false
|
|
354
|
+
let loc = location ?? lastGoodLocation
|
|
355
|
+
onMotionChangeSuccess(false, location: loc, didPersist: false)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
@objc public func onStopTimeout() {
|
|
359
|
+
detectStopMotion(lastGoodLocation)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
@objc public func resetStopDetectionDelayTimer() {
|
|
363
|
+
stopDetectionDelayTimer?.invalidate()
|
|
364
|
+
beginStopDetection()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
@objc public func resetStopTimeoutTimer() {
|
|
368
|
+
stopTimeoutTimer?.invalidate()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// MARK: - Motion trigger timer
|
|
372
|
+
|
|
373
|
+
@objc public func startMotionTriggerTimer() {
|
|
374
|
+
motionTriggerTimer?.invalidate()
|
|
375
|
+
let interval: TimeInterval = 30.0
|
|
376
|
+
DispatchQueue.main.async {
|
|
377
|
+
self.motionTriggerTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
|
|
378
|
+
self?.onMotionTrigger()
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@objc public func stopMotionTriggerTimer() {
|
|
384
|
+
motionTriggerTimer?.invalidate()
|
|
385
|
+
motionTriggerTimer = nil
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@objc public func resetMotionTriggerTimer() {
|
|
389
|
+
stopMotionTriggerTimer()
|
|
390
|
+
startMotionTriggerTimer()
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@objc public func onMotionTrigger() {
|
|
394
|
+
guard isEnabled else { return }
|
|
395
|
+
if let best = bestLocation {
|
|
396
|
+
detectStartMotion(best)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// MARK: - Heartbeat
|
|
401
|
+
|
|
402
|
+
@objc public func beginHeartbeat() {
|
|
403
|
+
let config = BGConfig.sharedInstance()
|
|
404
|
+
let interval = config.app.heartbeatInterval
|
|
405
|
+
BGHeartbeatService.sharedInstance().startWithInterval(interval) { [weak self] _ in
|
|
406
|
+
self?.onHeartbeat()
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@objc public func stopHeartbeat() {
|
|
411
|
+
BGHeartbeatService.sharedInstance().stop()
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@objc public func onHeartbeat() {
|
|
415
|
+
// Renew the keep-alive background task so the next interval can fire
|
|
416
|
+
// while briefly suspended (best-effort; true kill-state longevity comes
|
|
417
|
+
// from SLC/region wake, not from background tasks).
|
|
418
|
+
if BGConfig.sharedInstance().app.preventSuspend {
|
|
419
|
+
BGBackgroundTaskManager.sharedInstance().renewPreventSuspend()
|
|
420
|
+
}
|
|
421
|
+
// Emit the typed event the bridge/listener expects (a BGHeartbeatEvent,
|
|
422
|
+
// not a raw dictionary — the previous payload type never matched and so
|
|
423
|
+
// never reached JS onHeartbeat).
|
|
424
|
+
let event = BGHeartbeatEvent(location: lastLocation ?? lastGoodLocation)
|
|
425
|
+
BGEventBus.sharedInstance().emit(BGEventNames.heartbeat, payload: event)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
@objc public func evaluateHeartbeatTimer() {
|
|
429
|
+
let config = BGConfig.sharedInstance()
|
|
430
|
+
if config.app.heartbeatInterval > 0 && isEnabled {
|
|
431
|
+
beginHeartbeat()
|
|
432
|
+
} else {
|
|
433
|
+
stopHeartbeat()
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// MARK: - Location processing
|
|
438
|
+
|
|
439
|
+
@objc public func locationIsGoodEnough(_ location: CLLocation) -> Bool {
|
|
440
|
+
let config = BGConfig.sharedInstance()
|
|
441
|
+
let desiredAccuracy = config.geolocation.desiredAccuracy
|
|
442
|
+
return location.horizontalAccuracy <= desiredAccuracy || desiredAccuracy == 0
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
@objc public func calculateDistanceFilter(_ location: CLLocation) -> CLLocationDistance {
|
|
446
|
+
let config = BGConfig.sharedInstance()
|
|
447
|
+
let base = config.geolocation.distanceFilter
|
|
448
|
+
if config.geolocation.disableElasticity { return base }
|
|
449
|
+
let speed = max(0, location.speed)
|
|
450
|
+
let multiplier = config.geolocation.elasticityMultiplier
|
|
451
|
+
let elastic = base + speed * multiplier
|
|
452
|
+
return elastic
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
@objc public func calculateMedianLocationAccuracy(_ accuracies: [CLLocationAccuracy]) -> CLLocationAccuracy {
|
|
456
|
+
guard !accuracies.isEmpty else { return 0 }
|
|
457
|
+
let sorted = accuracies.sorted()
|
|
458
|
+
let mid = sorted.count / 2
|
|
459
|
+
return sorted.count % 2 == 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid]
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
@objc public func applyDistanceFilter(_ filter: CLLocationDistance) {
|
|
463
|
+
distanceFilter = filter
|
|
464
|
+
_mutateCL { mgr in mgr.distanceFilter = filter }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@objc public func persistLocation(_ location: BGLocation) {
|
|
468
|
+
var didPersist = false
|
|
469
|
+
if let block = beforeInsertBlock {
|
|
470
|
+
guard let modified = block(location) else { return }
|
|
471
|
+
didPersist = BGLocationDAO.sharedInstance().create(modified, error: nil)
|
|
472
|
+
} else {
|
|
473
|
+
didPersist = BGLocationDAO.sharedInstance().create(location, error: nil)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
let httpConfig = BGConfig.sharedInstance().http
|
|
477
|
+
if didPersist && httpConfig.autoSync && httpConfig.hasValidUrl {
|
|
478
|
+
let http = BGHttpService.sharedInstance()
|
|
479
|
+
if isInBackground() || BGAppState.sharedInstance().didLaunchInBackground {
|
|
480
|
+
http.flushForBackgroundWake()
|
|
481
|
+
} else {
|
|
482
|
+
http.flush()
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@objc public func persistStationaryLocation(_ location: CLLocation) {
|
|
488
|
+
let tsLocation = BGLocation(location: location, type: "stationary", extras: nil)
|
|
489
|
+
_ = BGLocationDAO.sharedInstance().create(tsLocation, error: nil)
|
|
490
|
+
stationaryLocation = location
|
|
491
|
+
UserDefaults.standard.set(try? JSONSerialization.data(withJSONObject: tsLocation.toDictionary()), forKey: "BGLocationManager_stationary")
|
|
492
|
+
if BGConfig.sharedInstance().http.autoSync && BGConfig.sharedInstance().http.hasValidUrl {
|
|
493
|
+
let http = BGHttpService.sharedInstance()
|
|
494
|
+
if isInBackground() || BGAppState.sharedInstance().didLaunchInBackground {
|
|
495
|
+
http.flushForBackgroundWake()
|
|
496
|
+
} else {
|
|
497
|
+
http.flush()
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
@objc public func loadStationaryLocation() {
|
|
503
|
+
if let data = UserDefaults.standard.data(forKey: "BGLocationManager_stationary"),
|
|
504
|
+
let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
505
|
+
stationaryLocation = BGLocationDAO.sharedInstance().inflate(dict).location
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
@objc public func destroyStationaryLocation() {
|
|
510
|
+
UserDefaults.standard.removeObject(forKey: "BGLocationManager_stationary")
|
|
511
|
+
stationaryLocation = nil
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// MARK: - Motion change
|
|
515
|
+
|
|
516
|
+
@objc public func onMotionChangeSuccess(_ moving: Bool, location: CLLocation?, didPersist: Bool) {
|
|
517
|
+
let loc = location ?? lastGoodLocation
|
|
518
|
+
let tsLocation = loc.map { BGLocation(location: $0, type: "motionchange", extras: nil) }
|
|
519
|
+
tsLocation?.isMoving = moving
|
|
520
|
+
|
|
521
|
+
if let tsLoc = tsLocation, !didPersist {
|
|
522
|
+
persistLocation(tsLoc)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Deliver BGLocation object to RN module listeners (onMotionChange: registered via BGEventManager)
|
|
526
|
+
if let tsLoc = tsLocation {
|
|
527
|
+
BGEventManager.sharedInstance().trigger(BGEventNames.motionChangeComplete, payload: tsLoc)
|
|
528
|
+
}
|
|
529
|
+
// Keep BGEventBus emission for internal engine consumers
|
|
530
|
+
BGEventBus.sharedInstance().emit(BGEventNames.motionChangeComplete, payload: [
|
|
531
|
+
"isMoving": moving,
|
|
532
|
+
"location": tsLocation?.toDictionary() ?? [:]
|
|
533
|
+
])
|
|
534
|
+
|
|
535
|
+
if moving {
|
|
536
|
+
stopMonitoringStationaryRegion()
|
|
537
|
+
startMonitoringSignificantLocationChanges()
|
|
538
|
+
startUpdatingLocation()
|
|
539
|
+
} else {
|
|
540
|
+
// Going stationary: persist the stop, arm the wake mechanisms (SLC +
|
|
541
|
+
// stationary region), and CRITICALLY tear down the high-power
|
|
542
|
+
// continuous GPS stream — otherwise full-accuracy GPS runs forever
|
|
543
|
+
// while parked (the biggest iOS battery drain, and the exact thing
|
|
544
|
+
// the stationary state exists to prevent).
|
|
545
|
+
if let loc = loc {
|
|
546
|
+
let config = BGConfig.sharedInstance()
|
|
547
|
+
persistStationaryLocation(loc)
|
|
548
|
+
startMonitoringStationaryRegion(loc, radius: config.geolocation.stationaryRadius)
|
|
549
|
+
}
|
|
550
|
+
startMonitoringSignificantLocationChanges()
|
|
551
|
+
// Keep GPS running in continuous keep-alive mode; otherwise tear it
|
|
552
|
+
// down to save battery and rely on SLC/region wakeups.
|
|
553
|
+
if !BGConfig.sharedInstance().geolocation.disableStopDetection {
|
|
554
|
+
stopUpdatingLocation()
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// Heartbeat lifecycle is owned by evaluateHeartbeatTimer() for the whole
|
|
558
|
+
// enabled session, so it keeps firing across pace changes (including
|
|
559
|
+
// while stationary, which is when it matters most).
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
@objc public func onMotionChangeError(_ error: Error) {
|
|
563
|
+
locationError = error
|
|
564
|
+
BGEventBus.sharedInstance().emit(BGEventNames.motionChangeError, payload: ["error": error.localizedDescription])
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// MARK: - Current position
|
|
568
|
+
|
|
569
|
+
@objc public func getCurrentPosition(_ request: BGCurrentPositionRequest) {
|
|
570
|
+
BGLocationRequestService.sharedInstance().requestLocation(request)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// MARK: - Location error event
|
|
574
|
+
|
|
575
|
+
@objc public func fireLocationErrorEvent(_ error: Error) {
|
|
576
|
+
BGEventBus.sharedInstance().emit(BGEventNames.locationError, payload: ["error": error.localizedDescription])
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// MARK: - Odometer
|
|
580
|
+
|
|
581
|
+
@objc public func setOdometer(_ value: Double, request: BGCurrentPositionRequest?) {
|
|
582
|
+
BGOdometer.sharedInstance().setOdometer(value, location: lastGoodLocation)
|
|
583
|
+
lastOdometerLocation = lastGoodLocation
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// MARK: - Schedule
|
|
587
|
+
|
|
588
|
+
@objc public func startSchedule() {
|
|
589
|
+
_ = BGScheduler.sharedInstance().start(withSchedule: BGConfig.sharedInstance().app.schedule)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
@objc public func startGeofences() {
|
|
593
|
+
BGGeofenceManager.sharedInstance().start()
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// MARK: - Significant location monitoring
|
|
597
|
+
|
|
598
|
+
@objc public func onStopMonitoringSLC() {
|
|
599
|
+
stopMonitoringSignificantLocationChanges()
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// MARK: - Config change handlers
|
|
603
|
+
|
|
604
|
+
@objc public func registerConfigChangeHandlers() {
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
@objc public func registerEventBusHandlers() {
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
@objc public func onChangeDesiredAccuracy(_ accuracy: CLLocationAccuracy) {
|
|
611
|
+
_mutateCL { mgr in mgr.desiredAccuracy = accuracy }
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
@objc public func onChangeDistanceFilter(_ filter: CLLocationDistance) {
|
|
615
|
+
applyDistanceFilter(filter)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
@objc public func onChangeActivityType(_ type: String) {
|
|
619
|
+
let activityType = BGGeolocationConfig.activityType(fromString: type)
|
|
620
|
+
_mutateCL { mgr in mgr.activityType = activityType }
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
@objc public func onChangePausesLocationUpdatesAutomatically(_ pauses: Bool) {
|
|
624
|
+
_mutateCL { mgr in mgr.pausesLocationUpdatesAutomatically = pauses }
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
@objc public func onChangeShowsBackgroundLocationIndicator(_ shows: Bool) {
|
|
628
|
+
if #available(iOS 11.0, *) {
|
|
629
|
+
_mutateCL { mgr in mgr.showsBackgroundLocationIndicator = shows }
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
@objc public func onChangeUseSignificantChangesOnly(_ enabled: Bool) {
|
|
634
|
+
if enabled {
|
|
635
|
+
stopUpdatingLocation()
|
|
636
|
+
startMonitoringSignificantLocationChanges()
|
|
637
|
+
} else {
|
|
638
|
+
stopMonitoringSignificantLocationChanges()
|
|
639
|
+
startUpdatingLocation()
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
@objc public func onChangeLocationAuthorizationRequest(_ request: String) {
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
@objc public func onChangeHeartbeatInterval(_ interval: TimeInterval) {
|
|
647
|
+
evaluateHeartbeatTimer()
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
@objc public func onChangeActivityRecognitionInterval(_ interval: TimeInterval) {
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
@objc public func onChangeDisableMotionActivityUpdates(_ disabled: Bool) {
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
@objc public func onChangeElasticity(_ enabled: Bool) {
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
@objc public func onChangePreventSuspend(_ enabled: Bool) {
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
@objc public func onChangeScheduleEvent(_ event: BGScheduleEvent) {
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// MARK: - Event handlers
|
|
666
|
+
|
|
667
|
+
@objc public func onScheduleEvent(_ event: BGScheduleEvent) {
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
@objc public func onMotionActivityChange(_ activity: BGMotionActivity) {
|
|
671
|
+
currentMotionActivity = activity
|
|
672
|
+
// Surface the activitychange event to JS (nothing emitted this before,
|
|
673
|
+
// so BackgroundGeolocation.onActivityChange never fired).
|
|
674
|
+
BGEventBus.sharedInstance().emit(BGEventNames.activityChange, payload: activity)
|
|
675
|
+
// Drive pace from the detected activity. "still"/"unknown" => stationary;
|
|
676
|
+
// anything else (walking/running/in_vehicle/on_bicycle) => moving. Route
|
|
677
|
+
// through changePace so the pending-authorization path is honored.
|
|
678
|
+
let moving = activity.type != "still" && activity.type != "unknown"
|
|
679
|
+
BGLiveActivityManager.shared.update(
|
|
680
|
+
location: lastGoodLocation,
|
|
681
|
+
isMoving: moving,
|
|
682
|
+
activity: activity.type,
|
|
683
|
+
force: true
|
|
684
|
+
)
|
|
685
|
+
if moving != isMoving {
|
|
686
|
+
changePace(moving)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
@objc public func onLocationError(_ error: Error) {
|
|
691
|
+
locationError = error
|
|
692
|
+
fireLocationErrorEvent(error)
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
@objc public func onResume() {
|
|
696
|
+
if isEnabled {
|
|
697
|
+
startMonitoringSignificantLocationChanges()
|
|
698
|
+
if !BGConfig.sharedInstance().geolocation.useSignificantChangesOnly {
|
|
699
|
+
startUpdatingLocation()
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
@objc public func onSuspend() {
|
|
705
|
+
guard isEnabled else { return }
|
|
706
|
+
let geo = BGConfig.sharedInstance().geolocation
|
|
707
|
+
startMonitoringSignificantLocationChanges()
|
|
708
|
+
// Continuous keep-alive mode: keep GPS running while backgrounded so the
|
|
709
|
+
// app is never suspended (this is what shows the status-bar indicator and
|
|
710
|
+
// is how ride apps stay alive). Otherwise downgrade to SLC + stationary
|
|
711
|
+
// region to save battery.
|
|
712
|
+
if geo.disableStopDetection || (isMoving && !geo.useSignificantChangesOnly) {
|
|
713
|
+
startUpdatingLocation()
|
|
714
|
+
} else {
|
|
715
|
+
startMonitoringSignificantLocationChanges()
|
|
716
|
+
if let loc = stationaryLocation ?? lastGoodLocation {
|
|
717
|
+
startMonitoringStationaryRegion(loc, radius: geo.stationaryRadius)
|
|
718
|
+
}
|
|
719
|
+
stopUpdatingLocation()
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
@objc public func onAppTerminate() {
|
|
724
|
+
let config = BGConfig.sharedInstance()
|
|
725
|
+
guard config.app.stopOnTerminate else {
|
|
726
|
+
config.enabled = true
|
|
727
|
+
config.isMoving = isMoving
|
|
728
|
+
config.forcePersistNow()
|
|
729
|
+
// UIApplication.willTerminate handlers MUST work synchronously — iOS
|
|
730
|
+
// kills the process shortly after this returns, so the async
|
|
731
|
+
// _mutateCL (clMutationQueue -> main) hop used elsewhere would be
|
|
732
|
+
// dropped. Re-arm the wake mechanisms directly on the manager here.
|
|
733
|
+
// (We are already on the main thread for this notification.)
|
|
734
|
+
if let mgr = locationManager {
|
|
735
|
+
configureLocationManager(mgr)
|
|
736
|
+
mgr.startMonitoringSignificantLocationChanges()
|
|
737
|
+
isMonitoringSignificantLocationChanges = true
|
|
738
|
+
if let loc = stationaryLocation ?? lastGoodLocation {
|
|
739
|
+
let radius = max(config.geolocation.stationaryRadius, 200.0)
|
|
740
|
+
let region = CLCircularRegion(center: loc.coordinate, radius: radius, identifier: "BGStationary")
|
|
741
|
+
region.notifyOnExit = true
|
|
742
|
+
stationaryRegion = region
|
|
743
|
+
mgr.startMonitoring(for: region)
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return
|
|
747
|
+
}
|
|
748
|
+
stop()
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
@objc public func updateCurrentState() {
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
@objc public func onUpdateState(_ state: [String: Any]) {
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
@objc public func shouldStopAfterElapsedMinutes() -> Bool {
|
|
758
|
+
return false
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
@objc public func stopAfterElapsedMinutes() -> TimeInterval {
|
|
762
|
+
return 0
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
@objc public func isInBackground() -> Bool {
|
|
766
|
+
return BGAppState.sharedInstance().isInBackground
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// MARK: - CLLocationManagerDelegate
|
|
770
|
+
|
|
771
|
+
@objc public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
772
|
+
guard let location = locations.last else { return }
|
|
773
|
+
|
|
774
|
+
NSLog("[BGGEO] didUpdateLocations: lat=\(location.coordinate.latitude) lng=\(location.coordinate.longitude) acc=\(location.horizontalAccuracy) background=\(isInBackground()) moving=\(isMoving)")
|
|
775
|
+
|
|
776
|
+
let config = BGConfig.sharedInstance()
|
|
777
|
+
|
|
778
|
+
locationAccuracyQueue.append(location.horizontalAccuracy)
|
|
779
|
+
if locationAccuracyQueue.count > 10 { locationAccuracyQueue.removeFirst() }
|
|
780
|
+
medianLocationAccuracy = calculateMedianLocationAccuracy(locationAccuracyQueue)
|
|
781
|
+
|
|
782
|
+
lastLocation = location
|
|
783
|
+
|
|
784
|
+
if locationIsGoodEnough(location) {
|
|
785
|
+
lastGoodLocation = location
|
|
786
|
+
if !didReceiveFirstLocation {
|
|
787
|
+
didReceiveFirstLocation = true
|
|
788
|
+
if !isMoving {
|
|
789
|
+
persistStationaryLocation(location)
|
|
790
|
+
startMonitoringStationaryRegion(location, radius: config.geolocation.stationaryRadius)
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
bestLocation = location
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
let newDistanceFilter = calculateDistanceFilter(location)
|
|
797
|
+
if newDistanceFilter != distanceFilter {
|
|
798
|
+
applyDistanceFilter(newDistanceFilter)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
BGGeofenceManager.sharedInstance().setLocation(location, isMoving: isMoving)
|
|
802
|
+
// Give the motion classifier speed/location context so its
|
|
803
|
+
// walking/running/vehicle classification can use speed alongside the
|
|
804
|
+
// accelerometer / CMMotionActivity signal.
|
|
805
|
+
BGMotionDetector.sharedInstance().setLocation(location, isMoving: isMoving)
|
|
806
|
+
|
|
807
|
+
// Speed-based motion onset. CMMotionActivity is unreliable for vehicles
|
|
808
|
+
// and bikes — when the phone is held steady the classifier reports
|
|
809
|
+
// `unknown`/`stationary` even at speed, which kept isMoving=false on real
|
|
810
|
+
// rides. GPS speed is decisive here, so flip to moving when we see real
|
|
811
|
+
// ground speed regardless of the activity classifier. (Going back to
|
|
812
|
+
// stationary is still handled by stop-detection / activity, except in
|
|
813
|
+
// keep-alive mode where we intentionally never stop.)
|
|
814
|
+
let speedMovingThreshold: CLLocationDistance = 1.5 // m/s ≈ 5.4 km/h
|
|
815
|
+
if location.speed >= 0, location.speed > speedMovingThreshold, !isMoving {
|
|
816
|
+
NSLog("[BGGEO] speed-based motion onset: speed=\(location.speed) m/s -> moving")
|
|
817
|
+
detectStartMotion(location)
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
let tsLocation = BGLocation(location: location, type: isMoving ? "tracking" : "stationary", extras: nil)
|
|
821
|
+
tsLocation.isMoving = isMoving
|
|
822
|
+
tsLocation.odometer = BGOdometer.sharedInstance().getOdometer()
|
|
823
|
+
|
|
824
|
+
if config.shouldPersist(tsLocation) {
|
|
825
|
+
persistLocation(tsLocation)
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
BGLiveActivityManager.shared.update(
|
|
829
|
+
location: location,
|
|
830
|
+
isMoving: isMoving,
|
|
831
|
+
activity: currentMotionActivity?.type ?? (isMoving ? "moving" : "still"),
|
|
832
|
+
force: false
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
// Deliver to RN module listeners (BgGeolocation.mm registered via onLocation:)
|
|
836
|
+
BGEventManager.sharedInstance().triggerLocationSuccess(tsLocation)
|
|
837
|
+
// Keep BGEventBus emission for any internal engine consumers
|
|
838
|
+
BGEventBus.sharedInstance().emit(BGEventNames.locationComplete, payload: tsLocation.toDictionary())
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
@objc public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
842
|
+
BGEventManager.sharedInstance().triggerLocationError(error)
|
|
843
|
+
onLocationError(error)
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
@objc public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
|
|
847
|
+
BGLocationAuthorization.sharedInstance().onAuthorizationStatusChanged(status)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
@objc public func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
|
|
851
|
+
guard region.identifier == "BGStationary" else { return }
|
|
852
|
+
if !isMoving {
|
|
853
|
+
startUpdatingLocation()
|
|
854
|
+
let fallback: CLLocation
|
|
855
|
+
if let circular = region as? CLCircularRegion {
|
|
856
|
+
fallback = CLLocation(latitude: circular.center.latitude, longitude: circular.center.longitude)
|
|
857
|
+
} else {
|
|
858
|
+
fallback = CLLocation()
|
|
859
|
+
}
|
|
860
|
+
detectStartMotion(lastLocation ?? lastGoodLocation ?? fallback)
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
@objc public func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
@objc public func locationManagerDidPauseLocationUpdates(_ manager: CLLocationManager) {
|
|
868
|
+
isUpdatingLocation = false
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
@objc public func locationManagerDidResumeLocationUpdates(_ manager: CLLocationManager) {
|
|
872
|
+
isUpdatingLocation = true
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// MARK: - KVO
|
|
876
|
+
|
|
877
|
+
@objc public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
|
878
|
+
}
|
|
879
|
+
}
|