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,39 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
react_native_package = Pod::Executable.execute_command(
|
|
5
|
+
"node",
|
|
6
|
+
[
|
|
7
|
+
"-p",
|
|
8
|
+
"require.resolve('react-native/package.json', { paths: [process.argv[1]] })",
|
|
9
|
+
__dir__,
|
|
10
|
+
]
|
|
11
|
+
).strip
|
|
12
|
+
require File.join(File.dirname(react_native_package), "scripts/react_native_pods")
|
|
13
|
+
|
|
14
|
+
Pod::Spec.new do |s|
|
|
15
|
+
s.name = "BgGeolocation"
|
|
16
|
+
s.version = package["version"]
|
|
17
|
+
s.summary = package["description"]
|
|
18
|
+
s.homepage = package["homepage"]
|
|
19
|
+
s.license = package["license"]
|
|
20
|
+
s.authors = package["author"]
|
|
21
|
+
|
|
22
|
+
s.platforms = { :ios => "15.1" }
|
|
23
|
+
s.source = { :git => "https://github.com/ZohaibNaseer786/react-native-bg-geolocation.git", :tag => "#{s.version}" }
|
|
24
|
+
|
|
25
|
+
# ObjC++ TurboModule wrapper + all Swift engine files.
|
|
26
|
+
# No binary dependency — the engine is compiled from Swift source directly.
|
|
27
|
+
# The location-push extension's PRINCIPAL class (BGLocationPushService.swift)
|
|
28
|
+
# is excluded from the pod — it belongs only to the
|
|
29
|
+
# CLLocationPushServiceExtension target. The shared constants, socket client,
|
|
30
|
+
# and deliverer ARE compiled into the engine so the in-app background-push
|
|
31
|
+
# handler can deliver natively without React Native.
|
|
32
|
+
s.source_files = "ios/**/*.{h,m,mm,cpp}", "ios/engine/**/*.swift", "ios/liveactivity/**/*.swift", "ios/locationpush/BGLocationPushShared.swift", "ios/locationpush/BGLocationPushSocketClient.swift", "ios/locationpush/BGLocationPushDeliverer.swift"
|
|
33
|
+
|
|
34
|
+
s.libraries = 'sqlite3', 'z', 'stdc++'
|
|
35
|
+
s.frameworks = "CoreLocation", "CoreMotion", "AVFoundation", "AudioToolbox", "MediaPlayer", "UIKit", "CoreData",
|
|
36
|
+
"MessageUI", "UserNotifications", "BackgroundTasks", "ActivityKit"
|
|
37
|
+
|
|
38
|
+
install_modules_dependencies(s)
|
|
39
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zohaib Naseer
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# react-native-bg-geolocation
|
|
2
|
+
|
|
3
|
+
Background geolocation tracking for React Native in foreground, background, Android kill state, and iOS kill state (server-triggered via the Location Push Service Extension, plus eligible OS-managed relaunches), with motion detection (moving/stationary), geofencing, and pluggable delivery (socket / HTTP / headless).
|
|
4
|
+
|
|
5
|
+
> Educational re-implementation of the `react-native-background-geolocation` API using only public platform APIs (FusedLocationProvider + Activity Recognition on Android, CoreLocation + CoreMotion + BGTaskScheduler on iOS). No proprietary binaries.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ✅ Foreground / background location tracking with OS relaunch-capable events
|
|
10
|
+
- ✅ Moving ↔ stationary detection (motion state machine + activity recognition)
|
|
11
|
+
- ✅ OS-owned delivery (Android PendingIntent, iOS significant-change + regions)
|
|
12
|
+
- ✅ **iOS Location Push Service Extension** — server-triggered location even when the app is force-quit (APNs `location` push → extension captures + uploads natively)
|
|
13
|
+
- ✅ iOS Live Activity for Lock Screen and Dynamic Island tracking status
|
|
14
|
+
- ✅ Headless JS task (Android) for kill-state JS execution
|
|
15
|
+
- ✅ Pluggable delivery: native socket (Socket.IO) → REST fallback, or your own JS handler
|
|
16
|
+
- ✅ Geofencing (`CLCircularRegion` / `GeofencingClient`)
|
|
17
|
+
- ✅ Reboot persistence
|
|
18
|
+
- ✅ Same API shape as `react-native-background-geolocation` (`AuthorizationStatus`, `DesiredAccuracy`, nested `Config`, etc.)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install react-native-bg-geolocation
|
|
24
|
+
# or
|
|
25
|
+
yarn add react-native-bg-geolocation
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### iOS
|
|
29
|
+
|
|
30
|
+
Add to `Info.plist`:
|
|
31
|
+
|
|
32
|
+
```xml
|
|
33
|
+
<key>NSLocationWhenInUseUsageDescription</key>
|
|
34
|
+
<string>Used to track your location.</string>
|
|
35
|
+
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
|
36
|
+
<string>Used to track your location in the background.</string>
|
|
37
|
+
<key>NSMotionUsageDescription</key>
|
|
38
|
+
<string>Used to detect movement.</string>
|
|
39
|
+
<key>UIBackgroundModes</key>
|
|
40
|
+
<array>
|
|
41
|
+
<string>location</string>
|
|
42
|
+
<!-- Required only when app.trackingAudioEnabled is approved and enabled. -->
|
|
43
|
+
<string>audio</string>
|
|
44
|
+
<string>fetch</string>
|
|
45
|
+
<string>processing</string>
|
|
46
|
+
<!-- Required for the app-alive background push path (Location Push). -->
|
|
47
|
+
<string>remote-notification</string>
|
|
48
|
+
</array>
|
|
49
|
+
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
50
|
+
<array>
|
|
51
|
+
<string>com.bggeolocation.location-refresh</string>
|
|
52
|
+
</array>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For the Location Push Service Extension (kill-state), the app and extension also
|
|
56
|
+
need the `aps-environment` and `com.apple.developer.location.push` entitlements
|
|
57
|
+
and a shared App Group — see [iOS Location Push Service Extension](#ios-location-push-service-extension-kill-state-tracking).
|
|
58
|
+
|
|
59
|
+
Register the BGTask in `AppDelegate` (see the example app's `AppDelegate.swift`).
|
|
60
|
+
|
|
61
|
+
#### iOS Live Activity
|
|
62
|
+
|
|
63
|
+
Live Activities require iOS 16.2 or newer and a WidgetKit extension in the
|
|
64
|
+
host application. The example includes a complete extension in
|
|
65
|
+
`example/ios/BgGeolocationLiveActivity`.
|
|
66
|
+
|
|
67
|
+
Add these keys to the app `Info.plist`:
|
|
68
|
+
|
|
69
|
+
```xml
|
|
70
|
+
<key>NSSupportsLiveActivities</key>
|
|
71
|
+
<true/>
|
|
72
|
+
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
|
|
73
|
+
<true/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Add `ios/liveactivity/TSLiveTrackingAttributes.swift` to the WidgetKit
|
|
77
|
+
extension target and create an `ActivityConfiguration` for
|
|
78
|
+
`TSLiveTrackingAttributes`. The package starts, updates, recovers, and ends the
|
|
79
|
+
activity from native iOS location callbacks.
|
|
80
|
+
|
|
81
|
+
#### iOS Tracking Audio
|
|
82
|
+
|
|
83
|
+
For Apple-approved use cases, the package can run a real audible audio session
|
|
84
|
+
beside Core Location while tracking is active:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
app: {
|
|
88
|
+
trackingAudioEnabled: true,
|
|
89
|
+
trackingAudioVolume: 0.04,
|
|
90
|
+
trackingAudioMixWithOthers: true,
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Audio playback has no iOS runtime permission dialog. The host app must obtain
|
|
95
|
+
clear user consent in its own UI before starting it. The app's Start and Stop
|
|
96
|
+
actions control both tracking and audio. The package publishes the approved
|
|
97
|
+
playback session through Now Playing, where Pause or Stop ends both playback and
|
|
98
|
+
tracking. The tracking Live Activity remains a separate status surface. Add
|
|
99
|
+
`audio` to `UIBackgroundModes` and enable this only
|
|
100
|
+
for an audio use case and distribution model Apple has approved. Explicitly
|
|
101
|
+
swiping the app away still terminates audio and location execution.
|
|
102
|
+
Runtime status is available from
|
|
103
|
+
`(await BackgroundGeolocation.getState()).trackingAudio`.
|
|
104
|
+
|
|
105
|
+
#### iOS Location Push Service Extension (kill-state tracking)
|
|
106
|
+
|
|
107
|
+
This is the production mechanism for getting a location from a **force-quit**
|
|
108
|
+
app. Your server sends an APNs push to the device's *location-push token*; iOS
|
|
109
|
+
launches a tiny `CLLocationPushServiceExtension` (a **separate process**, no
|
|
110
|
+
React Native), which grabs one fix and uploads it — then the app is left
|
|
111
|
+
untouched. This is how rideshare / delivery apps keep tracking after the user
|
|
112
|
+
swipes the app away.
|
|
113
|
+
|
|
114
|
+
> **There is no JavaScript in the extension process.** Delivery from a push is
|
|
115
|
+
> done **natively** by the SDK (Socket.IO → REST fallback), because on a
|
|
116
|
+
> kill-state / background wake the RN bridge is not running and JS event
|
|
117
|
+
> listeners would be dropped.
|
|
118
|
+
|
|
119
|
+
**Two push types, two paths** (your server may send either/both; the device
|
|
120
|
+
uses whichever fires):
|
|
121
|
+
|
|
122
|
+
| App state | Push to send | Wakes | Who delivers |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| Alive (fg / backgrounded) | `apns-push-type: background`, `content-available: 1` (standard APNs token) | the app | SDK natively, then fires the `locationpush` JS event for awareness |
|
|
125
|
+
| **Force-quit** | `apns-push-type: location` to `<bundle-id>.location-query` (location-push token) | the **extension** | SDK natively inside the extension |
|
|
126
|
+
|
|
127
|
+
The push payload must carry a `location-query` id (any of `location-query`,
|
|
128
|
+
`locationQuery`, `location_query`, `locationQueryId`, `queryId`). Its presence
|
|
129
|
+
means "report a location now"; it is echoed back in the upload so the server can
|
|
130
|
+
correlate.
|
|
131
|
+
|
|
132
|
+
**1. Request the Apple entitlement** (one-time, ~24–48h):
|
|
133
|
+
<https://developer.apple.com/contact/request/location-push-service-extension>.
|
|
134
|
+
Once approved, add `com.apple.developer.location.push` to the app entitlements.
|
|
135
|
+
|
|
136
|
+
**2. Add an App Group** shared by the app and the extension (e.g.
|
|
137
|
+
`group.<your-bundle-id>`). The SDK writes its delivery config into this suite so
|
|
138
|
+
the separate-process extension can read where/how to upload. Add the same
|
|
139
|
+
identifier to both targets' `Info.plist` files:
|
|
140
|
+
|
|
141
|
+
```xml
|
|
142
|
+
<key>BGLocationPushAppGroupIdentifier</key>
|
|
143
|
+
<string>group.your.bundle.id</string>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**3. Add a Location Push Service Extension target** whose principal class is the
|
|
147
|
+
SDK's `BGLocationPushService`. The example app ships a ready-made target and a
|
|
148
|
+
script that wires it (`example/ios/add_location_push_target.rb`) — copy that
|
|
149
|
+
target, or run the equivalent for your project. The extension's `Info.plist`:
|
|
150
|
+
|
|
151
|
+
```xml
|
|
152
|
+
<key>NSExtension</key>
|
|
153
|
+
<dict>
|
|
154
|
+
<key>NSExtensionPointIdentifier</key>
|
|
155
|
+
<string>com.apple.location.push.service</string>
|
|
156
|
+
<key>NSExtensionPrincipalClass</key>
|
|
157
|
+
<string>BGLocationPushService</string>
|
|
158
|
+
</dict>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**4. Register the location-push token in `AppDelegate`** and ship it to your
|
|
162
|
+
server:
|
|
163
|
+
|
|
164
|
+
```swift
|
|
165
|
+
import CoreLocation
|
|
166
|
+
// keep a strong ref or the manager is deallocated before the callback
|
|
167
|
+
private var locationPushManager: CLLocationManager?
|
|
168
|
+
|
|
169
|
+
if #available(iOS 15.0, *) {
|
|
170
|
+
let mgr = CLLocationManager()
|
|
171
|
+
locationPushManager = mgr
|
|
172
|
+
mgr.startMonitoringLocationPushes { tokenData, error in
|
|
173
|
+
guard let data = tokenData else { return }
|
|
174
|
+
let token = data.map { String(format: "%02hhx", $0) }.joined()
|
|
175
|
+
UserDefaults.standard.set(token, forKey: "BGLocationManager_locationPushToken")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Then in JS, after `ready()`:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const token = await BackgroundGeolocation.getLocationPushToken(); // iOS only, else null
|
|
184
|
+
if (token) await myApi.registerLocationPushToken(token); // your endpoint
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**5. Configure delivery** (where the extension uploads). Call once after
|
|
188
|
+
`ready()`:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
await BackgroundGeolocation.setLocationPushConfig({
|
|
192
|
+
socketUrl: 'https://your.server', // Socket.IO base URL (tried first)
|
|
193
|
+
socketPath: '/socket/location',
|
|
194
|
+
socketEvent: 'location:update',
|
|
195
|
+
socketAuthToken: jwt, // sent in the socket CONNECT auth + REST Bearer
|
|
196
|
+
socketTimeout: 8, // seconds before falling back to REST
|
|
197
|
+
fallbackUrl: 'https://your.server/api/location/fallback',
|
|
198
|
+
fcmToken, // available as the {fcmToken} token
|
|
199
|
+
// Custom HTTP headers for the REST fallback.
|
|
200
|
+
headers: { 'Device-Type': 'ios' },
|
|
201
|
+
// OPTIONAL: define the exact upload shape (socket emit + REST fallback).
|
|
202
|
+
// Values may contain {tokens} the SDK substitutes with live values. Omit to
|
|
203
|
+
// use the built-in default body.
|
|
204
|
+
payloadTemplate: {
|
|
205
|
+
lat: '{latitude}', // exact single token → stays numeric
|
|
206
|
+
long: '{longitude}',
|
|
207
|
+
fcm_token: '{fcmToken}',
|
|
208
|
+
device_type: 'ios', // literal value, passed through
|
|
209
|
+
user_current_time: '{userCurrentTime}',
|
|
210
|
+
location_query_id: '{queryId}',
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**`payloadTemplate`** lets you ship any payload shape from JS — no native
|
|
216
|
+
patching. Tokens (use inside string values): `{latitude}`/`{lat}`,
|
|
217
|
+
`{longitude}`/`{long}`, `{accuracy}`, `{speed}`, `{heading}`, `{altitude}`,
|
|
218
|
+
`{timestamp}`, `{fcmToken}`, `{queryId}`, `{userCurrentTime}` (local `HH:mm`),
|
|
219
|
+
`{deviceType}`. A value that is exactly one token (`'{latitude}'`) keeps its
|
|
220
|
+
native type (numbers stay numbers); inline tokens (`'v2-{queryId}'`) interpolate
|
|
221
|
+
as strings; nested objects/arrays are supported. Without `payloadTemplate` the
|
|
222
|
+
deliverer posts the default body
|
|
223
|
+
`{ lat, long, fcm_token, device_type: "ios", user_current_time, location_query_id }`
|
|
224
|
+
(applies to both the socket emit and the REST fallback).
|
|
225
|
+
|
|
226
|
+
**6. App-alive handler (optional).** When the app is alive, the SDK still
|
|
227
|
+
delivers natively and emits a `locationpush` event so your JS can react. The
|
|
228
|
+
event carries `delivered: true` when native already sent it — **do not re-send**:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
BackgroundGeolocation.onLocationPush(async (event) => {
|
|
232
|
+
if (event.delivered) return; // native already uploaded it
|
|
233
|
+
// fallback: deliver event.location yourself, then:
|
|
234
|
+
await BackgroundGeolocation.finishLocationPush(event.requestId);
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**7. Server.** Send `apns-push-type: location` to the location-push token with
|
|
239
|
+
topic `<bundle-id>.location-query` (and/or a `background` push to the standard
|
|
240
|
+
APNs token for the app-alive path). Requires "Always" location authorization.
|
|
241
|
+
|
|
242
|
+
Set `app.locationPushEnabled: true` in your config to signal this flow is in use.
|
|
243
|
+
|
|
244
|
+
### Android
|
|
245
|
+
|
|
246
|
+
Permissions are declared by the library manifest (`ACCESS_FINE_LOCATION`, `ACCESS_BACKGROUND_LOCATION`, `FOREGROUND_SERVICE_LOCATION`, `ACTIVITY_RECOGNITION`, `WAKE_LOCK`, `RECEIVE_BOOT_COMPLETED`). Request the runtime permissions in JS (see the example).
|
|
247
|
+
|
|
248
|
+
## Usage
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import BackgroundGeolocation from 'react-native-bg-geolocation';
|
|
252
|
+
|
|
253
|
+
// 1. Configure (nested config, same shape as the original library)
|
|
254
|
+
await BackgroundGeolocation.ready({
|
|
255
|
+
geolocation: {
|
|
256
|
+
desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
|
|
257
|
+
distanceFilter: 10,
|
|
258
|
+
stopTimeout: 5,
|
|
259
|
+
locationAuthorizationRequest: 'Always',
|
|
260
|
+
},
|
|
261
|
+
app: {
|
|
262
|
+
stopOnTerminate: false,
|
|
263
|
+
startOnBoot: true,
|
|
264
|
+
enableHeadless: true,
|
|
265
|
+
heartbeatInterval: 60,
|
|
266
|
+
liveActivityEnabled: true,
|
|
267
|
+
liveActivityTitle: 'Live location',
|
|
268
|
+
liveActivitySubtitle: 'Background tracking is active',
|
|
269
|
+
// Enable only after adding Push Notifications and ActivityKit APNs.
|
|
270
|
+
liveActivityPushUpdates: false,
|
|
271
|
+
trackingAudioEnabled: true,
|
|
272
|
+
trackingAudioVolume: 0.04,
|
|
273
|
+
trackingAudioMixWithOthers: true,
|
|
274
|
+
foregroundService: true,
|
|
275
|
+
notification: { title: 'Tracking', text: 'Location is active' },
|
|
276
|
+
},
|
|
277
|
+
logger: { logLevel: BackgroundGeolocation.LogLevel.Verbose },
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// 2. Listen
|
|
281
|
+
const sub = BackgroundGeolocation.onLocation(location => {
|
|
282
|
+
console.log('[location]', location.coords.latitude, location.coords.longitude);
|
|
283
|
+
});
|
|
284
|
+
BackgroundGeolocation.onMotionChange(event => {
|
|
285
|
+
console.log('[motionchange]', event.isMoving ? 'moving' : 'stationary');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// 3. Request permission + start
|
|
289
|
+
const status = await BackgroundGeolocation.requestPermission();
|
|
290
|
+
if (status === BackgroundGeolocation.AuthorizationStatus.Always) {
|
|
291
|
+
await BackgroundGeolocation.start();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 4. Cleanup
|
|
295
|
+
sub.remove();
|
|
296
|
+
await BackgroundGeolocation.stop();
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Headless task (Android kill state)
|
|
300
|
+
|
|
301
|
+
Register at module scope (e.g. in `index.js`) so it survives app kill:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
import BackgroundGeolocation from 'react-native-bg-geolocation';
|
|
305
|
+
|
|
306
|
+
const headlessTask = async (event) => {
|
|
307
|
+
if (event.name === 'location') {
|
|
308
|
+
const { coords } = event.params;
|
|
309
|
+
// POST to your server with fetch(), connect a socket, etc.
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
BackgroundGeolocation.registerHeadlessTask(headlessTask);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## How kill-state delivery works
|
|
317
|
+
|
|
318
|
+
| Platform | Mechanism |
|
|
319
|
+
|---|---|
|
|
320
|
+
| **Android** | A FusedLocation `PendingIntent` is owned by the OS and delivered to a `BroadcastReceiver` even when the process is dead. A `START_STICKY` foreground service keeps the process priority high; `onTaskRemoved` reschedules it via `AlarmManager`. Kill-state events fire the **HeadlessJsTask**, where your JS runs. |
|
|
321
|
+
| **iOS (OS relaunch)** | Continuous background updates use Core Location. Significant-change and region events can relaunch an app the system terminated. Each native fix is persisted before upload; interrupted HTTP delivery retries on the next wake. After an explicit force-quit, iOS will **not** relaunch via Core Location until the app is reopened. |
|
|
322
|
+
| **iOS (force-quit, push-driven)** | Your server sends an APNs `location` push (topic `<bundle-id>.location-query`) to the device's location-push token. iOS launches the **Location Push Service Extension** — a separate process, even when the app is force-quit — which captures one fix and uploads it natively (Socket.IO → REST fallback). See the [Location Push section](#ios-location-push-service-extension-kill-state-tracking). Requires the Apple `location.push` entitlement, an App Group, and "Always" authorization. |
|
|
323
|
+
|
|
324
|
+
Live Activities do not collect location and do not provide unrestricted
|
|
325
|
+
background execution. Their extension cannot access the network or receive
|
|
326
|
+
location updates. Core Location performs tracking; ActivityKit presents the
|
|
327
|
+
latest state. For updates while the app process is unavailable, read
|
|
328
|
+
`(await BackgroundGeolocation.getState()).liveActivity.pushToken`, send it to
|
|
329
|
+
your server, and update the activity through APNs using the
|
|
330
|
+
`<bundle-id>.push-type.liveactivity` topic.
|
|
331
|
+
|
|
332
|
+
Devices without a Dynamic Island, including iPhone 12, show the Live Activity
|
|
333
|
+
on the Lock Screen rather than persistently in the status bar.
|
|
334
|
+
|
|
335
|
+
## API
|
|
336
|
+
|
|
337
|
+
- **Lifecycle:** `ready`, `start`, `stop`, `getState`, `setConfig`, `reset`
|
|
338
|
+
- **Location:** `getCurrentPosition`, `watchPosition`, `stopWatchPosition`, `changePace`, `getOdometer`, `setOdometer`, `resetOdometer`
|
|
339
|
+
- **Permissions:** `requestPermission`, `requestTemporaryFullAccuracy`, `getProviderState`
|
|
340
|
+
- **Persistence:** `getLocations`, `getCount`, `destroyLocations`, `insertLocation`, `sync`
|
|
341
|
+
- **Geofencing:** `addGeofence(s)`, `removeGeofence(s)`, `getGeofences`, `getGeofence`, `geofenceExists`
|
|
342
|
+
- **Events:** `onLocation`, `onMotionChange`, `onActivityChange`, `onHeartbeat`, `onProviderChange`, `onGeofence`, `onEnabledChange`, `onHttp`, `onLocationPush`, …
|
|
343
|
+
- **iOS Location Push (kill-state):** `getLocationPushToken`, `getApnsDeviceToken`, `setLocationPushConfig`, `onLocationPush`, `finishLocationPush` — all iOS-only (resolve `null` / no-op on Android)
|
|
344
|
+
- **Enums:** `AuthorizationStatus`, `DesiredAccuracy`, `LogLevel`, `NotificationPriority`, `ActivityType`, `PersistMode`, `AccuracyAuthorization`
|
|
345
|
+
|
|
346
|
+
## Example
|
|
347
|
+
|
|
348
|
+
The [`example/`](example) app demonstrates app-level tracking, socket delivery, motion detection and a live event log. See `example/src/backgroundLocationService.ts` for the recommended app-level integration pattern.
|
|
349
|
+
|
|
350
|
+
```sh
|
|
351
|
+
yarn
|
|
352
|
+
yarn example android # or: yarn example ios
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Contributing
|
|
356
|
+
|
|
357
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
358
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
359
|
+
|
|
360
|
+
## License
|
|
361
|
+
|
|
362
|
+
MIT
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.BgGeolocation = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return BgGeolocation[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.bggeolocation"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildFeatures {
|
|
46
|
+
buildConfig true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lint {
|
|
56
|
+
disable "GradleCompatible"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compileOptions {
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dependencies {
|
|
66
|
+
implementation "com.facebook.react:react-android"
|
|
67
|
+
implementation "com.google.android.gms:play-services-location:21.0.1"
|
|
68
|
+
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
|
69
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
|
|
3
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
4
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
5
|
+
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
6
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
7
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
8
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
9
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
10
|
+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
11
|
+
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
|
|
12
|
+
<!-- Required by React Native's HeadlessJsTaskService (kill-state JS execution) -->
|
|
13
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
14
|
+
|
|
15
|
+
<application>
|
|
16
|
+
|
|
17
|
+
<!-- Foreground service — the single active location source. Runs FusedLocation
|
|
18
|
+
with foregroundServiceType=location, so the OS grants continuous fresh GPS
|
|
19
|
+
in the background & after kill (this is what shows the location indicator). -->
|
|
20
|
+
<service
|
|
21
|
+
android:name=".BgGeolocationForegroundService"
|
|
22
|
+
android:foregroundServiceType="location"
|
|
23
|
+
android:exported="false"
|
|
24
|
+
android:stopWithTask="false" />
|
|
25
|
+
|
|
26
|
+
<!-- Boot receiver — restarts tracking after device reboot -->
|
|
27
|
+
<receiver
|
|
28
|
+
android:name=".BgGeolocationBootReceiver"
|
|
29
|
+
android:exported="false">
|
|
30
|
+
<intent-filter>
|
|
31
|
+
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
32
|
+
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
|
33
|
+
</intent-filter>
|
|
34
|
+
</receiver>
|
|
35
|
+
|
|
36
|
+
<!-- Activity recognition receiver — receives motion activity updates from Play Services -->
|
|
37
|
+
<receiver
|
|
38
|
+
android:name=".BgGeolocationActivityRecognitionReceiver"
|
|
39
|
+
android:exported="false" />
|
|
40
|
+
|
|
41
|
+
<!-- Geofence receiver — receives ENTER/EXIT/DWELL transitions (bg + kill state) -->
|
|
42
|
+
<receiver
|
|
43
|
+
android:name=".BgGeolocationGeofenceReceiver"
|
|
44
|
+
android:exported="false" />
|
|
45
|
+
|
|
46
|
+
<!-- HeadlessTask service — executes JS in a headless context when app is killed -->
|
|
47
|
+
<service
|
|
48
|
+
android:name=".BgGeolocationHeadlessTask"
|
|
49
|
+
android:exported="false" />
|
|
50
|
+
|
|
51
|
+
</application>
|
|
52
|
+
|
|
53
|
+
</manifest>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
package com.bggeolocation
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import com.facebook.react.bridge.Arguments
|
|
8
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
9
|
+
import com.google.android.gms.location.ActivityRecognitionResult
|
|
10
|
+
import com.google.android.gms.location.DetectedActivity
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Receives activity-recognition updates from Google Play Services.
|
|
14
|
+
*
|
|
15
|
+
* Because the PendingIntent that drives this receiver is owned by the OS, it
|
|
16
|
+
* fires in ALL app states — foreground, background AND after the app is killed.
|
|
17
|
+
*
|
|
18
|
+
* On every update it:
|
|
19
|
+
* 1. Persists the latest {type, confidence, isMoving} to SharedPreferences so
|
|
20
|
+
* the module / foreground-service can stamp it onto every location.
|
|
21
|
+
* 2. Emits a `motionchange` event when the moving<->stationary state flips:
|
|
22
|
+
* - to JS via DeviceEventEmitter when the React context is alive
|
|
23
|
+
* - via the HeadlessTask when the app is killed.
|
|
24
|
+
*
|
|
25
|
+
* Declared in AndroidManifest.xml as a non-exported receiver.
|
|
26
|
+
*/
|
|
27
|
+
class BgGeolocationActivityRecognitionReceiver : BroadcastReceiver() {
|
|
28
|
+
|
|
29
|
+
companion object {
|
|
30
|
+
private const val TAG = "BgGeoActivity"
|
|
31
|
+
private const val PREFS = "bg_geolocation_prefs"
|
|
32
|
+
const val KEY_ACTIVITY_TYPE = "activity_type"
|
|
33
|
+
const val KEY_ACTIVITY_CONF = "activity_confidence"
|
|
34
|
+
const val KEY_IS_MOVING = "activity_is_moving"
|
|
35
|
+
|
|
36
|
+
/** Map a Play-Services activity to our string + moving flag. */
|
|
37
|
+
fun describe(type: Int): Pair<String, Boolean> = when (type) {
|
|
38
|
+
DetectedActivity.IN_VEHICLE -> "in_vehicle" to true
|
|
39
|
+
DetectedActivity.ON_BICYCLE -> "on_bicycle" to true
|
|
40
|
+
DetectedActivity.ON_FOOT -> "on_foot" to true
|
|
41
|
+
DetectedActivity.RUNNING -> "running" to true
|
|
42
|
+
DetectedActivity.WALKING -> "walking" to true
|
|
43
|
+
DetectedActivity.STILL -> "still" to false
|
|
44
|
+
DetectedActivity.TILTING -> "tilting" to false
|
|
45
|
+
else -> "unknown" to false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun readActivityType(context: Context): String =
|
|
49
|
+
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
|
50
|
+
.getString(KEY_ACTIVITY_TYPE, "unknown") ?: "unknown"
|
|
51
|
+
|
|
52
|
+
fun readActivityConfidence(context: Context): Int =
|
|
53
|
+
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
|
54
|
+
.getInt(KEY_ACTIVITY_CONF, 0)
|
|
55
|
+
|
|
56
|
+
fun readIsMoving(context: Context): Boolean =
|
|
57
|
+
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
|
58
|
+
.getBoolean(KEY_IS_MOVING, false)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
62
|
+
if (!ActivityRecognitionResult.hasResult(intent)) return
|
|
63
|
+
val result = ActivityRecognitionResult.extractResult(intent) ?: return
|
|
64
|
+
|
|
65
|
+
val mostProbable = result.mostProbableActivity
|
|
66
|
+
val (typeStr, isMoving) = describe(mostProbable.type)
|
|
67
|
+
val confidence = mostProbable.confidence
|
|
68
|
+
|
|
69
|
+
val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
|
70
|
+
val wasMoving = prefs.getBoolean(KEY_IS_MOVING, false)
|
|
71
|
+
val hadPrevious = prefs.contains(KEY_IS_MOVING)
|
|
72
|
+
|
|
73
|
+
// Persist latest activity so locations can be stamped with it in any state.
|
|
74
|
+
prefs.edit()
|
|
75
|
+
.putString(KEY_ACTIVITY_TYPE, typeStr)
|
|
76
|
+
.putInt(KEY_ACTIVITY_CONF, confidence)
|
|
77
|
+
.putBoolean(KEY_IS_MOVING, isMoving)
|
|
78
|
+
.apply()
|
|
79
|
+
|
|
80
|
+
Log.d("BgGeoTest", "[ACTIVITY] $typeStr (moving=$isMoving conf=$confidence)")
|
|
81
|
+
|
|
82
|
+
// Only emit motionchange on an actual transition.
|
|
83
|
+
if (hadPrevious && wasMoving == isMoving) return
|
|
84
|
+
|
|
85
|
+
val payload = Arguments.createMap().apply {
|
|
86
|
+
putBoolean("isMoving", isMoving)
|
|
87
|
+
putMap("activity", Arguments.createMap().apply {
|
|
88
|
+
putString("type", typeStr)
|
|
89
|
+
putInt("confidence", confidence)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
val reactContext = BgGeolocationModule.getReactContext()
|
|
94
|
+
|
|
95
|
+
if (reactContext != null) {
|
|
96
|
+
// App alive — emit straight to JS.
|
|
97
|
+
try {
|
|
98
|
+
reactContext
|
|
99
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
100
|
+
?.emit("motionchange", payload)
|
|
101
|
+
Log.d("BgGeoTest", "[ACTIVITY] motionchange → JS (isMoving=$isMoving)")
|
|
102
|
+
} catch (e: Exception) {
|
|
103
|
+
Log.w(TAG, "emit motionchange failed: ${e.message}")
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// App killed — route through the headless task.
|
|
107
|
+
val headlessMap = Arguments.createMap().apply {
|
|
108
|
+
putBoolean("isMoving", isMoving)
|
|
109
|
+
putString("activityType", typeStr)
|
|
110
|
+
putInt("confidence", confidence)
|
|
111
|
+
}
|
|
112
|
+
BgGeolocationHeadlessTask.onEvent(context.applicationContext, "motionchange", headlessMap)
|
|
113
|
+
Log.d("BgGeoTest", "[ACTIVITY] motionchange → HEADLESS (isMoving=$isMoving)")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|