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,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
|
+
}
|