@tsachit/react-native-geo-service 1.0.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/README.md +250 -0
- package/android/build.gradle +43 -0
- package/android/src/main/AndroidManifest.xml +48 -0
- package/android/src/main/java/com/geoservice/BootReceiver.kt +73 -0
- package/android/src/main/java/com/geoservice/GeoServiceConfig.kt +56 -0
- package/android/src/main/java/com/geoservice/GeoServiceModule.kt +291 -0
- package/android/src/main/java/com/geoservice/GeoServicePackage.kt +14 -0
- package/android/src/main/java/com/geoservice/HeadlessLocationTask.kt +76 -0
- package/android/src/main/java/com/geoservice/LocationService.kt +265 -0
- package/ios/RNGeoService.h +19 -0
- package/ios/RNGeoService.m +311 -0
- package/lib/index.d.ts +73 -0
- package/lib/index.js +155 -0
- package/lib/types.d.ts +139 -0
- package/lib/types.js +2 -0
- package/package.json +42 -0
- package/react-native-geo-service.podspec +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# react-native-geo-service
|
|
2
|
+
|
|
3
|
+
Battery-efficient background geolocation for React Native.
|
|
4
|
+
|
|
5
|
+
- Tracks location as the user moves and fires a JS listener
|
|
6
|
+
- Keeps tracking in the background and when the app is killed (headless mode)
|
|
7
|
+
- Uses `FusedLocationProviderClient` on Android and `CLLocationManager` on iOS for maximum battery savings
|
|
8
|
+
- **Adaptive accuracy**: GPS turns off automatically when the device is idle and wakes up the moment movement is detected
|
|
9
|
+
- Fully configurable from JavaScript
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react-native-geo-service
|
|
17
|
+
# or
|
|
18
|
+
yarn add react-native-geo-service
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### iOS
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd ios && pod install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Add the following to your `Info.plist`:
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- Required — explain why you need location -->
|
|
31
|
+
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
|
32
|
+
<string>We use your location to track your route in the background.</string>
|
|
33
|
+
|
|
34
|
+
<key>NSLocationWhenInUseUsageDescription</key>
|
|
35
|
+
<string>We use your location to show your position on the map.</string>
|
|
36
|
+
|
|
37
|
+
<!-- Required for background location updates -->
|
|
38
|
+
<key>UIBackgroundModes</key>
|
|
39
|
+
<array>
|
|
40
|
+
<string>location</string>
|
|
41
|
+
</array>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### iOS Headless Mode (app terminated)
|
|
45
|
+
|
|
46
|
+
When the app is terminated, iOS can still wake it up for location events if you use
|
|
47
|
+
`coarseTracking: true` (fires when the device moves ~500 m) or standard background
|
|
48
|
+
location updates (requires the Always permission).
|
|
49
|
+
|
|
50
|
+
Add this to your **AppDelegate** so tracking resumes after a background relaunch:
|
|
51
|
+
|
|
52
|
+
```objc
|
|
53
|
+
// AppDelegate.m
|
|
54
|
+
- (BOOL)application:(UIApplication *)application
|
|
55
|
+
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
56
|
+
|
|
57
|
+
// If the app was relaunched in the background due to a location event,
|
|
58
|
+
// the location manager delegate will resume tracking automatically.
|
|
59
|
+
if (launchOptions[UIApplicationLaunchOptionsLocationKey]) {
|
|
60
|
+
// Optionally restore your RNGeoService.configure() call here.
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ... rest of your setup
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### Android
|
|
70
|
+
|
|
71
|
+
#### 1. Register the package
|
|
72
|
+
|
|
73
|
+
**`android/app/src/main/java/.../MainApplication.kt`** (or `.java`):
|
|
74
|
+
|
|
75
|
+
```kotlin
|
|
76
|
+
import com.geoservice.GeoServicePackage
|
|
77
|
+
|
|
78
|
+
override fun getPackages(): List<ReactPackage> =
|
|
79
|
+
PackageList(this).packages.apply {
|
|
80
|
+
add(GeoServicePackage())
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### 2. Register the HeadlessJS task
|
|
85
|
+
|
|
86
|
+
In your app's **`index.js`** (at the top level, outside any component):
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
import { AppRegistry } from 'react-native';
|
|
90
|
+
import App from './App';
|
|
91
|
+
|
|
92
|
+
AppRegistry.registerComponent('YourApp', () => App);
|
|
93
|
+
|
|
94
|
+
// Register the background task for when the app is not in the foreground.
|
|
95
|
+
// This runs even when the app is killed (as long as the foreground service is active).
|
|
96
|
+
AppRegistry.registerHeadlessTask('GeoServiceHeadlessTask', () => async (location) => {
|
|
97
|
+
console.log('[Background] Location:', location);
|
|
98
|
+
// Send to your server using a pre-stored auth token (e.g. from SecureStore/Keychain).
|
|
99
|
+
// Avoid relying on in-memory app state — the JS context here is headless and isolated.
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### 3. Add permissions to `AndroidManifest.xml`
|
|
104
|
+
|
|
105
|
+
The library's manifest already declares the permissions, but your **app's** manifest must
|
|
106
|
+
also include them (merging happens at build time):
|
|
107
|
+
|
|
108
|
+
```xml
|
|
109
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
110
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
111
|
+
<!-- Android 10+ — required for background access: -->
|
|
112
|
+
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### 4. Request permissions at runtime
|
|
116
|
+
|
|
117
|
+
On Android 10+ you must request `ACCESS_BACKGROUND_LOCATION` **separately**, after the
|
|
118
|
+
user has already granted foreground location. Use
|
|
119
|
+
[`react-native-permissions`](https://github.com/zoontek/react-native-permissions) or the
|
|
120
|
+
built-in `PermissionsAndroid` API.
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import { PermissionsAndroid, Platform } from 'react-native';
|
|
124
|
+
|
|
125
|
+
async function requestLocationPermissions() {
|
|
126
|
+
if (Platform.OS !== 'android') return;
|
|
127
|
+
|
|
128
|
+
await PermissionsAndroid.request(
|
|
129
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Android 10+ requires a second, separate request for background
|
|
133
|
+
if (Platform.Version >= 29) {
|
|
134
|
+
await PermissionsAndroid.request(
|
|
135
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Usage
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import RNGeoService from 'react-native-geo-service';
|
|
147
|
+
|
|
148
|
+
// 1. Configure (call once, before start)
|
|
149
|
+
await RNGeoService.configure({
|
|
150
|
+
minDistanceMeters: 10, // fire update every 10 metres of movement
|
|
151
|
+
accuracy: 'balanced', // balanced accuracy = good battery
|
|
152
|
+
stopOnAppClose: false, // keep tracking even when app is killed
|
|
153
|
+
restartOnBoot: true, // restart on device reboot (Android)
|
|
154
|
+
serviceTitle: 'Tracking active',
|
|
155
|
+
serviceBody: 'Your route is being recorded.',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// 2. Start tracking
|
|
159
|
+
await RNGeoService.start();
|
|
160
|
+
|
|
161
|
+
// 3. Listen for location updates
|
|
162
|
+
const subscription = RNGeoService.onLocation((location) => {
|
|
163
|
+
console.log(location.latitude, location.longitude);
|
|
164
|
+
console.log('GPS idle:', location.isStationary);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 4. Stop tracking
|
|
168
|
+
await RNGeoService.stop();
|
|
169
|
+
|
|
170
|
+
// 5. Remove listener
|
|
171
|
+
subscription.remove();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### One-time location
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const location = await RNGeoService.getCurrentLocation();
|
|
178
|
+
console.log(location.latitude, location.longitude);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Check tracking state
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const tracking = await RNGeoService.isTracking();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Configuration reference
|
|
190
|
+
|
|
191
|
+
| Option | Type | Default | Description |
|
|
192
|
+
|--------|------|---------|-------------|
|
|
193
|
+
| `minDistanceMeters` | `number` | `10` | Minimum metres of movement before a location update fires |
|
|
194
|
+
| `accuracy` | `'navigation' \| 'high' \| 'balanced' \| 'low'` | `'balanced'` | Location accuracy (affects battery) |
|
|
195
|
+
| `stopOnAppClose` | `boolean` | `false` | Stop tracking when the app is killed |
|
|
196
|
+
| `restartOnBoot` | `boolean` | `false` | Resume tracking after device reboot *(Android only)* |
|
|
197
|
+
| `updateIntervalMs` | `number` | `5000` | Target ms between updates *(Android only)* |
|
|
198
|
+
| `minUpdateIntervalMs` | `number` | `2000` | Minimum ms between updates *(Android only)* |
|
|
199
|
+
| `serviceTitle` | `string` | `'Location Tracking'` | Foreground service notification title *(Android only)* |
|
|
200
|
+
| `serviceBody` | `string` | `'Your location is being tracked...'` | Foreground service notification body *(Android only)* |
|
|
201
|
+
| `backgroundTaskName` | `string` | `'GeoServiceHeadlessTask'` | HeadlessJS task name *(Android only)* |
|
|
202
|
+
| `motionActivity` | `'other' \| 'automotiveNavigation' \| 'fitness' \| 'otherNavigation' \| 'airborne'` | `'other'` | Hints the OS about usage for power optimisation *(iOS only)* |
|
|
203
|
+
| `autoPauseUpdates` | `boolean` | `false` | Let iOS pause updates when no movement *(iOS only)* |
|
|
204
|
+
| `showBackgroundIndicator` | `boolean` | `false` | Show blue bar in status bar while tracking in background *(iOS only)* |
|
|
205
|
+
| `coarseTracking` | `boolean` | `false` | Use significant-change monitoring — very battery-efficient, wakes app when terminated *(iOS only)* |
|
|
206
|
+
| `adaptiveAccuracy` | `boolean` | `true` | Auto-drop to low-power when idle, restore on movement |
|
|
207
|
+
| `idleSpeedThreshold` | `number` | `0.5` | Speed in m/s below which a reading counts as idle |
|
|
208
|
+
| `idleSampleCount` | `number` | `3` | Consecutive idle readings before entering low-power mode |
|
|
209
|
+
| `debug` | `boolean` | `false` | Enable verbose native logging |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Battery saving tips
|
|
214
|
+
|
|
215
|
+
- Set `accuracy: 'balanced'` unless you need GPS precision.
|
|
216
|
+
- Set `minDistanceMeters` to the minimum distance useful for your use-case (higher = fewer wakes).
|
|
217
|
+
- On iOS, enable `coarseTracking: true` if your app only needs to know when the user
|
|
218
|
+
has moved to a new area (~500 m). This is the most battery-efficient mode.
|
|
219
|
+
- On Android, a higher `updateIntervalMs` (e.g. `10000`) with a reasonable `minUpdateIntervalMs`
|
|
220
|
+
gives FusedLocationProvider more room to batch updates and use passive fixes from other apps.
|
|
221
|
+
- Set `motionActivity: 'automotiveNavigation'` or `'fitness'` so iOS can apply activity-specific
|
|
222
|
+
power optimisations.
|
|
223
|
+
- Leave `adaptiveAccuracy: true` (the default) — this is the single biggest battery saving.
|
|
224
|
+
GPS turns off completely when parked and wakes up as soon as the device moves.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Headless mode explained
|
|
229
|
+
|
|
230
|
+
### Android
|
|
231
|
+
When the app is removed from recents (but not force-stopped), the foreground service keeps
|
|
232
|
+
running. When a location update arrives and the React JS context is not active, the library
|
|
233
|
+
calls `AppRegistry.startHeadlessTask` to spin up a lightweight JS runtime and invoke your
|
|
234
|
+
registered `backgroundTaskName` handler.
|
|
235
|
+
|
|
236
|
+
### iOS
|
|
237
|
+
When the app is terminated, iOS can relaunch it silently in the background if:
|
|
238
|
+
1. You have the `location` background mode in `UIBackgroundModes`.
|
|
239
|
+
2. You use `startMonitoringSignificantLocationChanges` (`coarseTracking: true`), **or**
|
|
240
|
+
3. You have the _Always_ location permission and standard updates are running.
|
|
241
|
+
|
|
242
|
+
Upon relaunch, `didFinishLaunchingWithOptions` is called with
|
|
243
|
+
`UIApplicationLaunchOptionsLocationKey`, and the `CLLocationManager` delegate resumes delivering
|
|
244
|
+
updates. The JS bridge boots and your `onLocation` listener fires normally.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
MIT
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
dependencies {
|
|
7
|
+
classpath 'com.android.tools.build:gradle:7.4.2'
|
|
8
|
+
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22'
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply plugin: 'com.android.library'
|
|
13
|
+
apply plugin: 'kotlin-android'
|
|
14
|
+
|
|
15
|
+
android {
|
|
16
|
+
compileSdkVersion 34
|
|
17
|
+
namespace 'com.geoservice'
|
|
18
|
+
|
|
19
|
+
defaultConfig {
|
|
20
|
+
minSdkVersion 21
|
|
21
|
+
targetSdkVersion 34
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
compileOptions {
|
|
25
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
26
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
kotlinOptions {
|
|
30
|
+
jvmTarget = '1.8'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
repositories {
|
|
35
|
+
google()
|
|
36
|
+
mavenCentral()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dependencies {
|
|
40
|
+
implementation 'com.facebook.react:react-android:+'
|
|
41
|
+
implementation 'com.google.android.gms:play-services-location:21.0.1'
|
|
42
|
+
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
|
|
43
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
|
|
4
|
+
<!-- Standard location permission (coarse, for balanced/low accuracy) -->
|
|
5
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
6
|
+
|
|
7
|
+
<!-- Fine location permission (for high/navigation accuracy) -->
|
|
8
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
9
|
+
|
|
10
|
+
<!-- Required for Android 10+ background location access.
|
|
11
|
+
The app must ALSO hold ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION. -->
|
|
12
|
+
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
|
13
|
+
|
|
14
|
+
<!-- Keeps the CPU running while the foreground service is active -->
|
|
15
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
16
|
+
|
|
17
|
+
<!-- Required to start a foreground service -->
|
|
18
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
19
|
+
|
|
20
|
+
<!-- Android 12+: declares the foreground service type -->
|
|
21
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
|
22
|
+
|
|
23
|
+
<!-- Required for restartOnBoot feature -->
|
|
24
|
+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
25
|
+
|
|
26
|
+
<application>
|
|
27
|
+
<service
|
|
28
|
+
android:name=".LocationService"
|
|
29
|
+
android:enabled="true"
|
|
30
|
+
android:exported="false"
|
|
31
|
+
android:foregroundServiceType="location" />
|
|
32
|
+
|
|
33
|
+
<service
|
|
34
|
+
android:name=".HeadlessLocationTask"
|
|
35
|
+
android:permission="android.permission.BIND_JOB_SERVICE" />
|
|
36
|
+
|
|
37
|
+
<receiver
|
|
38
|
+
android:name=".BootReceiver"
|
|
39
|
+
android:enabled="true"
|
|
40
|
+
android:exported="true">
|
|
41
|
+
<intent-filter>
|
|
42
|
+
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
43
|
+
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
|
44
|
+
</intent-filter>
|
|
45
|
+
</receiver>
|
|
46
|
+
</application>
|
|
47
|
+
|
|
48
|
+
</manifest>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package com.geoservice
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.util.Log
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Restarts location tracking on device boot if restartOnBoot was enabled.
|
|
11
|
+
*
|
|
12
|
+
* The config is persisted to SharedPreferences by GeoServiceModule when start() is called.
|
|
13
|
+
* This receiver reads it on boot and restarts the service if required.
|
|
14
|
+
*/
|
|
15
|
+
class BootReceiver : BroadcastReceiver() {
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
const val TAG = "GeoService:BootReceiver"
|
|
19
|
+
const val PREFS_NAME = "GeoServicePrefs"
|
|
20
|
+
const val KEY_RESTART_ON_BOOT = "restartOnBoot"
|
|
21
|
+
const val KEY_IS_TRACKING = "isTracking"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
25
|
+
val action = intent.action
|
|
26
|
+
if (action != Intent.ACTION_BOOT_COMPLETED &&
|
|
27
|
+
action != "android.intent.action.QUICKBOOT_POWERON") {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
32
|
+
val restartOnBoot = prefs.getBoolean(KEY_RESTART_ON_BOOT, false)
|
|
33
|
+
val wasTracking = prefs.getBoolean(KEY_IS_TRACKING, false)
|
|
34
|
+
|
|
35
|
+
if (!restartOnBoot || !wasTracking) {
|
|
36
|
+
Log.d(TAG, "Skipping boot start: restartOnBoot=$restartOnBoot, wasTracking=$wasTracking")
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Log.d(TAG, "Device booted — restarting location service")
|
|
41
|
+
|
|
42
|
+
val serviceIntent = Intent(context, LocationService::class.java)
|
|
43
|
+
val configJson = prefs.getString("configBundle", null)
|
|
44
|
+
if (configJson != null) {
|
|
45
|
+
try {
|
|
46
|
+
val json = org.json.JSONObject(configJson)
|
|
47
|
+
val bundle = android.os.Bundle()
|
|
48
|
+
bundle.putFloat("minDistanceMeters", json.optDouble("minDistanceMeters", 10.0).toFloat())
|
|
49
|
+
bundle.putString("accuracy", json.optString("accuracy", "balanced"))
|
|
50
|
+
bundle.putBoolean("stopOnAppClose", json.optBoolean("stopOnAppClose", false))
|
|
51
|
+
bundle.putBoolean("restartOnBoot", json.optBoolean("restartOnBoot", false))
|
|
52
|
+
bundle.putLong("updateIntervalMs", json.optLong("updateIntervalMs", 5000L))
|
|
53
|
+
bundle.putLong("minUpdateIntervalMs", json.optLong("minUpdateIntervalMs", 2000L))
|
|
54
|
+
bundle.putString("serviceTitle", json.optString("serviceTitle", "Location Tracking"))
|
|
55
|
+
bundle.putString("serviceBody", json.optString("serviceBody", "Your location is being tracked in the background."))
|
|
56
|
+
bundle.putString("backgroundTaskName", json.optString("backgroundTaskName", "GeoServiceHeadlessTask"))
|
|
57
|
+
bundle.putBoolean("adaptiveAccuracy", json.optBoolean("adaptiveAccuracy", true))
|
|
58
|
+
bundle.putFloat("idleSpeedThreshold", json.optDouble("idleSpeedThreshold", 0.5).toFloat())
|
|
59
|
+
bundle.putInt("idleSampleCount", json.optInt("idleSampleCount", 3))
|
|
60
|
+
bundle.putBoolean("debug", json.optBoolean("debug", false))
|
|
61
|
+
serviceIntent.putExtra("config", bundle)
|
|
62
|
+
} catch (e: Exception) {
|
|
63
|
+
Log.e(TAG, "Failed to parse persisted config: ${e.message}")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
68
|
+
context.startForegroundService(serviceIntent)
|
|
69
|
+
} else {
|
|
70
|
+
context.startService(serviceIntent)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
package com.geoservice
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
|
|
5
|
+
data class GeoServiceConfig(
|
|
6
|
+
val minDistanceMeters: Float = 10f,
|
|
7
|
+
val accuracy: String = "balanced",
|
|
8
|
+
val stopOnAppClose: Boolean = false,
|
|
9
|
+
val restartOnBoot: Boolean = false,
|
|
10
|
+
val updateIntervalMs: Long = 5000L,
|
|
11
|
+
val minUpdateIntervalMs: Long = 2000L,
|
|
12
|
+
val serviceTitle: String = "Location Tracking",
|
|
13
|
+
val serviceBody: String = "Your location is being tracked in the background.",
|
|
14
|
+
val backgroundTaskName: String = "GeoServiceHeadlessTask",
|
|
15
|
+
val adaptiveAccuracy: Boolean = true,
|
|
16
|
+
val idleSpeedThreshold: Float = 0.5f,
|
|
17
|
+
val idleSampleCount: Int = 3,
|
|
18
|
+
val debug: Boolean = false
|
|
19
|
+
) {
|
|
20
|
+
fun toBundle(): Bundle = Bundle().apply {
|
|
21
|
+
putFloat("minDistanceMeters", minDistanceMeters)
|
|
22
|
+
putString("accuracy", accuracy)
|
|
23
|
+
putBoolean("stopOnAppClose", stopOnAppClose)
|
|
24
|
+
putBoolean("restartOnBoot", restartOnBoot)
|
|
25
|
+
putLong("updateIntervalMs", updateIntervalMs)
|
|
26
|
+
putLong("minUpdateIntervalMs", minUpdateIntervalMs)
|
|
27
|
+
putString("serviceTitle", serviceTitle)
|
|
28
|
+
putString("serviceBody", serviceBody)
|
|
29
|
+
putString("backgroundTaskName", backgroundTaskName)
|
|
30
|
+
putBoolean("adaptiveAccuracy", adaptiveAccuracy)
|
|
31
|
+
putFloat("idleSpeedThreshold", idleSpeedThreshold)
|
|
32
|
+
putInt("idleSampleCount", idleSampleCount)
|
|
33
|
+
putBoolean("debug", debug)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
companion object {
|
|
37
|
+
fun fromBundle(bundle: Bundle?): GeoServiceConfig {
|
|
38
|
+
if (bundle == null) return GeoServiceConfig()
|
|
39
|
+
return GeoServiceConfig(
|
|
40
|
+
minDistanceMeters = bundle.getFloat("minDistanceMeters", 10f),
|
|
41
|
+
accuracy = bundle.getString("accuracy", "balanced") ?: "balanced",
|
|
42
|
+
stopOnAppClose = bundle.getBoolean("stopOnAppClose", false),
|
|
43
|
+
restartOnBoot = bundle.getBoolean("restartOnBoot", false),
|
|
44
|
+
updateIntervalMs = bundle.getLong("updateIntervalMs", 5000L),
|
|
45
|
+
minUpdateIntervalMs = bundle.getLong("minUpdateIntervalMs", 2000L),
|
|
46
|
+
serviceTitle = bundle.getString("serviceTitle", "Location Tracking") ?: "Location Tracking",
|
|
47
|
+
serviceBody = bundle.getString("serviceBody", "Your location is being tracked in the background.") ?: "Your location is being tracked in the background.",
|
|
48
|
+
backgroundTaskName = bundle.getString("backgroundTaskName", "GeoServiceHeadlessTask") ?: "GeoServiceHeadlessTask",
|
|
49
|
+
adaptiveAccuracy = bundle.getBoolean("adaptiveAccuracy", true),
|
|
50
|
+
idleSpeedThreshold = bundle.getFloat("idleSpeedThreshold", 0.5f),
|
|
51
|
+
idleSampleCount = bundle.getInt("idleSampleCount", 3),
|
|
52
|
+
debug = bundle.getBoolean("debug", false)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|