@josuelmm/cordova-background-geolocation 3.0.2 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +154 -8
  3. package/RELEASE.MD +15 -4
  4. package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +28 -0
  5. package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +32 -2
  6. package/android/CDVBackgroundGeolocation/src/test/java/com/marianhello/ConfigMapperTest.java +12 -0
  7. package/android/common/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +24 -1
  8. package/android/common/src/main/java/com/marianhello/bgloc/Config.java +131 -0
  9. package/android/common/src/main/java/com/marianhello/bgloc/HttpPostService.java +34 -1
  10. package/android/common/src/main/java/com/marianhello/bgloc/data/LocationDAO.java +5 -0
  11. package/android/common/src/main/java/com/marianhello/bgloc/data/provider/ContentProviderLocationDAO.java +11 -0
  12. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationContract.java +15 -1
  13. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationDAO.java +37 -1
  14. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationDAO.java +13 -0
  15. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +18 -1
  16. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +127 -3
  17. package/android/common/src/main/java/com/marianhello/bgloc/sync/SyncAdapter.java +14 -9
  18. package/angular/background-geolocation.service.ts +14 -0
  19. package/angular/dist/background-geolocation.service.d.ts +2 -0
  20. package/angular/dist/esm2022/background-geolocation.service.mjs +7 -1
  21. package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs +6 -0
  22. package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs.map +1 -1
  23. package/angular/dist/public-api.d.ts +1 -1
  24. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.h +2 -0
  25. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +16 -0
  26. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +2 -0
  27. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +22 -0
  28. package/ios/common/BackgroundGeolocation/MAURConfig.h +3 -0
  29. package/ios/common/BackgroundGeolocation/MAURConfig.m +20 -1
  30. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +1 -0
  31. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +1 -0
  32. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +5 -1
  33. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +4 -3
  34. package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.m +16 -9
  35. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.h +2 -0
  36. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.m +20 -0
  37. package/package.json +8 -3
  38. package/plugin.xml +1 -1
  39. package/www/BackgroundGeolocation.d.ts +83 -0
  40. package/www/BackgroundGeolocation.js +12 -0
  41. package/www/cordova-channel-stub.js +27 -0
  42. package/www/cordova-exec-stub.js +15 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.1.1](https://github.com/josuelmm/cordova-background-geolocation/tree/3.1.1) (2026-02-27)
4
+
5
+ ### Added
6
+
7
+ - **Browser / `ng serve` support** — The plugin uses `cordova/exec` and `cordova/channel`, which only exist in the Cordova runtime. To allow Angular (and other webpack-based) builds to succeed when running `ng serve` or building for browser, the package now ships:
8
+ - **Stub modules:** `www/cordova-exec-stub.js` and `www/cordova-channel-stub.js`. When running in the browser they avoid crashes; when running inside Cordova they delegate to the real `cordova.exec` and `cordova/channel`.
9
+ - **`browser` field in `package.json`** so bundlers (e.g. webpack) resolve `cordova/exec` and `cordova/channel` to these stubs. No app-side webpack config is required in normal setups.
10
+
11
+ ### Fixed
12
+
13
+ - **Angular types on Windows** — The emitted `.d.ts` in `angular/dist/` used `from '../www/BackgroundGeolocation'`, which resolves (relative to `dist/`) to `angular/www/`, a folder that does not exist in the published package. Some apps worked around this by creating a junction `angular/www` → `www`. The build now runs a post-step (`scripts/fix-angular-dts-paths.js`) that rewrites these paths to `../../www/BackgroundGeolocation` in the emitted declarations, so types resolve to the package root `www/` and the junction is no longer needed.
14
+
15
+ ### Documentation
16
+
17
+ - **docs/angular.md** — New section *"Build (ng serve / browser)"*: explains why the stubs exist, that webpack uses the `browser` field, and how to add resolve aliases in the app if a bundler does not respect it (e.g. "Can't resolve 'cordova/exec'"). Note that from 3.1.1 the junction workaround for types is unnecessary.
18
+
19
+ - **README.md** — Angular section and "New in" updated to mention 3.1.1 and browser/ng serve compatibility.
20
+
21
+ ### Changed
22
+
23
+ - Version bump to 3.1.1.
24
+
25
+ [Full Changelog](https://github.com/josuelmm/cordova-background-geolocation/compare/3.1.0...3.1.1)
26
+
27
+ ---
28
+
29
+ ## [3.1.0](https://github.com/josuelmm/cordova-background-geolocation/tree/3.1.0) (2026-02-21)
30
+
31
+ ### Added
32
+
33
+ - **`getPendingSyncCount(success?, fail?)`** — Returns the number of locations pending to be synced (not yet sent to `syncUrl`). Use with `forceSync()` to show "X locations pending" and let the user trigger sync on demand. (Android, iOS)
34
+ - **`clearSync(success?, fail?)`** — Clears the pending sync queue: discards all locations waiting to be sent to `syncUrl` (they will not be synced). Use when the user wants to discard pending locations (e.g. "Clear queue" button). (Android, iOS)
35
+ - **Config option `sync`** (default `true`) — When `false`, automatic sync and `forceSync()` do not run; locations are still stored and can be synced later by setting `sync: true`. (Android, iOS)
36
+ - **Config options for sync notification texts (Android):** `notificationSyncTitle`, `notificationSyncText`, `notificationSyncCompletedText`, `notificationSyncFailedText` — Customize or localize the notification shown while locations are syncing to the server.
37
+
38
+ ### Fixed
39
+
40
+ - **Android:** `forceSync()` now correctly calls `callbackContext.success()` so the JS Promise resolves.
41
+ - **Android SyncAdapter:** Use `currentSyncConfig` (not out-of-scope `config`) when building sync notifications.
42
+ - **Android & iOS:** When `sync` was never set in config, DB column `sync_enabled` could be NULL; the plugin now treats NULL as “not set” (sync enabled by default) instead of “sync disabled”, so `forceSync()` and automatic sync work when the user did not pass `sync: false`.
43
+ - **Android sync with `Content-Type: application/x-www-form-urlencoded`:** Batch sync was sending one POST with `locations=<url-encoded-json-array>`, which many servers reject (400). Sync now sends **one POST per location** with the same flat `key=value&key=value` format as real-time posting, so the same endpoint accepts both.
44
+
45
+ ### Changed
46
+
47
+ - Version bump to 3.1.0.
48
+
49
+ [Full Changelog](https://github.com/josuelmm/cordova-background-geolocation/compare/3.0.2...3.1.0)
50
+
51
+ ---
52
+
3
53
  ## [3.0.2](https://github.com/josuelmm/cordova-background-geolocation/tree/3.0.2) (2026-02-21)
4
54
 
5
55
  ### Fixed
package/README.md CHANGED
@@ -95,6 +95,22 @@ If your app’s merged manifest ends up with a `foregroundServiceType` other tha
95
95
  <string name="plugin_bgloc_content_authority">site.seelight.client.bgloc</string>
96
96
  ```
97
97
 
98
+ **Notification labels (showTime / showDistance):** If you use `showTime: true` or `showDistance: true`, the notification shows a line for elapsed time and one for distance. **By default the labels are in English** ("Time" and "Distance"). If you want Spanish or another language, add these optional strings in your app so the plugin uses them; if you don’t define them, English is used.
99
+
100
+ Example — English (default, optional in `res/values/strings.xml`):
101
+
102
+ ```xml
103
+ <string name="plugin_bgloc_notification_time_label">Time</string>
104
+ <string name="plugin_bgloc_notification_distance_label">Distance</string>
105
+ ```
106
+
107
+ Example — Spanish: add in `res/values-es/strings.xml` (or your locale folder):
108
+
109
+ ```xml
110
+ <string name="plugin_bgloc_notification_time_label">Tiempo</string>
111
+ <string name="plugin_bgloc_notification_distance_label">Distancia</string>
112
+ ```
113
+
98
114
  This makes your app enforce the correct foreground service type and defines the strings the plugin needs for the sync account.
99
115
 
100
116
  ---
@@ -161,7 +177,31 @@ BackgroundGeolocation.FOREGROUND_MODE
161
177
 
162
178
  ### 1. Configure
163
179
 
164
- Set your preferred provider, accuracy, intervals, and optional server URL:
180
+ Set your preferred provider, accuracy, intervals, and optional server URLs. All options are optional; you can reconfigure later with a subset (options are merged).
181
+
182
+ **Main options:**
183
+
184
+ | Option | Description |
185
+ |--------|-------------|
186
+ | `locationProvider` | `ACTIVITY_PROVIDER`, `DISTANCE_FILTER_PROVIDER`, or `RAW_PROVIDER` |
187
+ | `desiredAccuracy` | `HIGH_ACCURACY`, `MEDIUM_ACCURACY`, `LOW_ACCURACY`, `PASSIVE_ACCURACY` |
188
+ | `distanceFilter` | Minimum metres the device must move before an update (e.g. 50) |
189
+ | `stationaryRadius` | Metres from “stationary” point before aggressive tracking (e.g. 50) |
190
+ | `interval` / `fastestInterval` / `activitiesInterval` | Android timing (ms) |
191
+ | `notificationTitle` / `notificationText` | Android foreground notification text |
192
+ | `url` | Server URL where **each** location is posted immediately (if post fails, it goes to sync queue) |
193
+ | `syncUrl` | Server URL where **pending** locations are sent in batch (when count reaches `syncThreshold` or on `forceSync()`) |
194
+ | `syncThreshold` | Number of pending locations before automatic batch sync (default 100) |
195
+ | `sync` | When `true` (default), automatic sync and `forceSync()` run. When `false`, sync is disabled (locations are still stored). |
196
+ | `httpHeaders` | Headers for every POST (e.g. `{ 'Content-Type': 'application/json', 'Authorization': 'Bearer TOKEN' }`) |
197
+ | `postTemplate` | Object or array of properties to send (use `@latitude`, `@longitude`, etc.). See [Custom post template](docs/api.md#custom-post-template). |
198
+ | `maxLocations` | Max locations kept in DB (default 10000). Should be &gt; `syncThreshold`. |
199
+
200
+ **Android-only:** `notificationSyncTitle`, `notificationSyncText`, `notificationSyncCompletedText`, `notificationSyncFailedText` — texts shown in the notification while syncing (defaults in English; set for localization). `startForeground`, `notificationsEnabled`, `startOnBoot`, `stopOnTerminate`, `enableWatchdog`.
201
+
202
+ **iOS-only:** `activityType`, `pauseLocationUpdates`, `saveBatteryOnBackground`.
203
+
204
+ Example:
165
205
 
166
206
  ```js
167
207
  BackgroundGeolocation.configure({
@@ -176,15 +216,17 @@ BackgroundGeolocation.configure({
176
216
  fastestInterval: 5000,
177
217
  activitiesInterval: 10000,
178
218
  url: 'https://yourserver.com/location',
179
- // HTTP headers can be sent in two ways:
180
- // 1) Static: set httpHeaders here — same headers on every POST/sync request (e.g. API key, Content-Type).
219
+ syncUrl: 'https://yourserver.com/location',
220
+ syncThreshold: 5,
221
+ sync: true,
181
222
  httpHeaders: {
182
- 'X-FOO': 'bar',
223
+ 'Content-Type': 'application/json',
183
224
  'Authorization': 'Bearer YOUR_TOKEN'
184
225
  },
185
226
  postTemplate: {
186
227
  lat: '@latitude',
187
- lon: '@longitude'
228
+ lon: '@longitude',
229
+ timestamp: '@time'
188
230
  }
189
231
  });
190
232
  ```
@@ -224,7 +266,99 @@ BackgroundGeolocation.on('http_authorization', function () {
224
266
  BackgroundGeolocation.stop();
225
267
  ```
226
268
 
227
- More options (stationary, activity, start/stop events, headless task) are in the [documentation](https://josuelmm.github.io/cordova-background-geolocation/). For **Angular** (service, methods, events, example), see [Angular (Ionic Angular)](https://josuelmm.github.io/cordova-background-geolocation/angular).
269
+ ### 5. Sync queue (syncUrl): pending count, force sync, clear queue
270
+
271
+ When you use `syncUrl`, locations that fail to post to `url` (or that are only queued for sync) are sent in batch to `syncUrl`.
272
+
273
+ **How sync sends data (Content-Type):** It depends on the `Content-Type` you set in `httpHeaders`. Many people assume “one request per location”; that is only true for form encoding.
274
+
275
+ | Content-Type | Sync to `syncUrl` |
276
+ |--------------|-------------------|
277
+ | **`application/json`** (default) | **One POST** with a JSON **array** of all locations in the batch. |
278
+ | **`application/x-www-form-urlencoded`** | **One POST per location** (same flat `key=value&...` as real-time to `url`). Same endpoint can handle both. |
279
+
280
+ So: with **JSON** you get one request per batch (e.g. 100 locations in one body). With **form-urlencoded** you get one request per location (one record per POST). For headers, retries, `postTemplate` and full behaviour see [HTTP posting](docs/http_posting.md) and [API](docs/api.md).
281
+
282
+ You can:
283
+
284
+ - **Get pending count** — `getPendingSyncCount()` returns how many locations are waiting to be synced.
285
+ - **Force sync now** — `forceSync()` sends all pending locations immediately (ignores `syncThreshold`). No-op if `sync: false`.
286
+ - **Clear queue** — `clearSync()` discards all pending locations (they will not be sent). Use for a “Clear queue” or “Discard” button.
287
+
288
+ ```js
289
+ // Show "X locations pending" and let user sync or clear
290
+ BackgroundGeolocation.getPendingSyncCount()
291
+ .then(function (count) {
292
+ console.log('Pending to sync:', count);
293
+ // e.g. show UI: "Sync (5)" or "Clear queue"
294
+ });
295
+
296
+ // User taps "Sync now"
297
+ BackgroundGeolocation.forceSync().then(function () {
298
+ console.log('Sync completed');
299
+ });
300
+
301
+ // User taps "Clear queue"
302
+ BackgroundGeolocation.clearSync().then(function () {
303
+ console.log('Queue cleared');
304
+ });
305
+ ```
306
+
307
+ More on sync (headers, retries, postTemplate): [HTTP posting](docs/http_posting.md). Full options and methods: [API](docs/api.md).
308
+
309
+ ### 6. Other methods (summary)
310
+
311
+ | Method | Description |
312
+ |--------|-------------|
313
+ | `getConfig(success, fail)` | Get current configuration (merged options). |
314
+ | `getLocations(success, fail)` | Get all stored locations. |
315
+ | `getValidLocations(success, fail)` | Get locations not yet posted (valid only). |
316
+ | `deleteLocation(id, success, fail)` | Delete one location by id. |
317
+ | `deleteAllLocations(success, fail)` | Delete all stored locations. |
318
+ | `getCurrentLocation(success, fail, options)` | One-shot location (e.g. timeout, maximumAge). |
319
+ | `getPluginVersion(success, fail)` | Plugin version string (e.g. "3.1.1"). |
320
+ | `checkStatus(success, fail)` | Service status (isRunning, authorization, etc.). |
321
+ | `showAppSettings()` / `openSettings()` | Open app settings. |
322
+ | `showLocationSettings()` | Open system location settings. |
323
+ | `getLogEntries(limit, fromId, minLevel, success, fail)` | Debug log entries. |
324
+
325
+ All methods return a **Promise** if you omit the `success` / `fail` callbacks.
326
+
327
+ ### 7. Events (summary)
328
+
329
+ Subscribe with `BackgroundGeolocation.on(eventName, callback)`. Unsubscribe with the returned object’s `remove()` or by calling `removeAllListeners(eventName)`.
330
+
331
+ | Event | Payload | When |
332
+ |-------|---------|------|
333
+ | `location` | Location object | New location (foreground/background). |
334
+ | `stationary` | Location | Device stopped (activity provider). |
335
+ | `activity` | Activity type | Activity changed (walking, driving, still). |
336
+ | `error` | `{ code, message }` | Error (e.g. permission, timeout). |
337
+ | `start` | — | Tracking started. |
338
+ | `stop` | — | Tracking stopped. |
339
+ | `authorization` | status | Permission status changed. |
340
+ | `background` / `foreground` | — | App entered background/foreground. |
341
+ | `http_authorization` | — | Server returned 401; refresh token and reconfigure headers. |
342
+ | `abort_requested` | — | Server returned 285 (updates not required). |
343
+
344
+ Full event payloads and options: [Events](docs/events.md). Full API (all options, all methods): [API](docs/api.md).
345
+
346
+ ### New in 3.1.1
347
+
348
+ - **Browser / `ng serve` builds** — The plugin can now be bundled by webpack without "Can't resolve 'cordova/exec'" or "Can't resolve 'cordova/channel'". The package ships stub modules and a `browser` field so `ng serve` and browser builds succeed; on device/emulator the stubs delegate to the real Cordova API. See [docs/angular.md](docs/angular.md#build-ng-serve--browser).
349
+
350
+ ### New in 3.1.0
351
+
352
+ - **`getPendingSyncCount()`** — Number of locations pending to be synced. Use with `forceSync()` for “X pending” UI.
353
+ - **`forceSync()`** — Sends all pending locations to `syncUrl` immediately. Promise now resolves correctly on Android.
354
+ - **`clearSync()`** — Clears the pending sync queue (discard without sending).
355
+ - **Config `sync`** (default `true`) — Set `sync: false` to disable automatic sync and `forceSync()`; locations are still stored.
356
+ - **Config `notificationSyncTitle`, `notificationSyncText`, `notificationSyncCompletedText`, `notificationSyncFailedText`** (Android) — Customize or localize the notification shown while syncing.
357
+ - **Sync with `Content-Type: application/x-www-form-urlencoded`** — Batch sync now sends **one POST per location** (same flat format as real-time), so the same server endpoint works for both.
358
+
359
+ ---
360
+
361
+ More (stationary, activity, headless task, Angular) is in the [documentation](https://josuelmm.github.io/cordova-background-geolocation/). For **Angular** (service, methods, events), see [docs/angular.md](docs/angular.md).
228
362
 
229
363
  ---
230
364
 
@@ -269,6 +403,10 @@ export class MyService {
269
403
 
270
404
  **You must import `BackgroundGeolocationModule`** in your `AppModule` (or feature module) so the service is provided and AOT builds work. Then inject `BackgroundGeolocationService` as in the example above. See [docs/angular.md](docs/angular.md) for the full snippet.
271
405
 
406
+ **`ng serve` / browser:** From 3.1.1 the plugin includes browser stubs so `ng serve` and web builds complete without "Can't resolve 'cordova/exec'" — see [docs/angular.md](docs/angular.md#build-ng-serve--browser).
407
+
408
+ **Lazy-loaded pages:** If you see **NG0202** or *"dependency at index N is invalid"* when opening a page that injects this service, use an app-defined token and inject by that token (the plugin token can be undefined in the lazy chunk). See [docs/angular.md](docs/angular.md) (Lazy-loaded modules).
409
+
272
410
  **Migrating from @awesome-cordova-plugins/background-geolocation:** there you inject a class named `BackgroundGeolocation`. In this package, `BackgroundGeolocation` is the **global plugin object**, not an injectable class. Use `BackgroundGeolocationService` instead (same API). See [docs/angular.md](docs/angular.md) for details.
273
411
 
274
412
  ### Summary
@@ -293,8 +431,16 @@ No extra wrapper (e.g. Awesome Cordova Plugins) is required.
293
431
 
294
432
  ## Documentation and changelog
295
433
 
296
- - [Documentation](https://josuelmm.github.io/cordova-background-geolocation/) (API, options, examples)
297
- - [CHANGELOG](CHANGELOG.md) for version history
434
+ This README is the main entry point. For more detail, edge cases and examples use the docs below (and the [online documentation](https://josuelmm.github.io/cordova-background-geolocation/)).
435
+
436
+ | Doc | What you’ll find |
437
+ |-----|------------------|
438
+ | **[API reference](docs/api.md)** | Every `configure` option, every method (`configure`, `start`, `stop`, `getPendingSyncCount`, `forceSync`, `clearSync`, `getConfig`, `getLocations`, etc.), TypeScript types. |
439
+ | **[HTTP posting](docs/http_posting.md)** | `url` vs `syncUrl`, Content-Type (JSON = one POST with array; form-urlencoded = one POST per location), headers, retries, `postTemplate`, sync behaviour. |
440
+ | **[Events](docs/events.md)** | All events (`location`, `error`, `stationary`, `activity`, `http_authorization`, etc.) and payloads. |
441
+ | **[Angular / Ionic](docs/angular.md)** | Injectable service, module, lazy-loaded modules and token “must be defined”, `ng serve` / browser build. |
442
+ | **[Example](docs/example.md)** | Full example with events and sync. |
443
+ | **[CHANGELOG](CHANGELOG.md)** | Version history. |
298
444
 
299
445
  This project is based on [@mauron85/cordova-plugin-background-geolocation](https://github.com/mauron85/cordova-plugin-background-geolocation) and the original by [christocracy](https://github.com/christocracy). Maintained at [josuelmm/cordova-background-geolocation](https://github.com/josuelmm/cordova-background-geolocation). Issues and PRs welcome.
300
446
 
package/RELEASE.MD CHANGED
@@ -1,5 +1,16 @@
1
1
  In order to release a version the following actions are needed:
2
- 1. Update the version in the package.json, lock and plugin.xml files
3
- 2. Go to [releases and create a new release](https://github.com/josuelmm/cordova-background-geolocation/releases/new) in GitHub
4
- 3. Make sure to create a tag too with the version name (e.g. v3.0.2)
5
- 4. Click create. Github actions will publish the package to npm
2
+
3
+ 1. **Version and docs**
4
+ - Set the new version (e.g. `3.1.1`) in:
5
+ - `package.json` (`version` field)
6
+ - `plugin.xml` (root `<plugin>` attribute `version`)
7
+ - Add a release entry in `CHANGELOG.md` (date, Added/Fixed/Changed/Documentation).
8
+ - Update `README.md` if needed (e.g. "New in X.Y.Z", example version strings).
9
+
10
+ 2. **Lock file**
11
+ - Run `npm install` so `package-lock.json` stays in sync with `package.json`.
12
+
13
+ 3. **Git tag and release**
14
+ - Go to [Releases → Create a new release](https://github.com/josuelmm/cordova-background-geolocation/releases/new).
15
+ - Create a tag with the version name (e.g. `v3.1.1`).
16
+ - Publish the release. GitHub Actions will publish the package to npm.
@@ -42,6 +42,18 @@ public class ConfigMapper {
42
42
  if (jObject.has("notificationText")) {
43
43
  config.setNotificationText(!jObject.isNull("notificationText") ? jObject.getString("notificationText") : Config.NullString);
44
44
  }
45
+ if (jObject.has("notificationSyncTitle")) {
46
+ config.setNotificationSyncTitle(jObject.isNull("notificationSyncTitle") ? null : jObject.getString("notificationSyncTitle"));
47
+ }
48
+ if (jObject.has("notificationSyncText")) {
49
+ config.setNotificationSyncText(jObject.isNull("notificationSyncText") ? null : jObject.getString("notificationSyncText"));
50
+ }
51
+ if (jObject.has("notificationSyncCompletedText")) {
52
+ config.setNotificationSyncCompletedText(jObject.isNull("notificationSyncCompletedText") ? null : jObject.getString("notificationSyncCompletedText"));
53
+ }
54
+ if (jObject.has("notificationSyncFailedText")) {
55
+ config.setNotificationSyncFailedText(jObject.isNull("notificationSyncFailedText") ? null : jObject.getString("notificationSyncFailedText"));
56
+ }
45
57
  if (jObject.has("stopOnTerminate")) {
46
58
  config.setStopOnTerminate(jObject.getBoolean("stopOnTerminate"));
47
59
  }
@@ -84,6 +96,9 @@ public class ConfigMapper {
84
96
  if (jObject.has("syncThreshold")) {
85
97
  config.setSyncThreshold(jObject.getInt("syncThreshold"));
86
98
  }
99
+ if (jObject.has("sync")) {
100
+ config.setSyncEnabled(jObject.getBoolean("sync"));
101
+ }
87
102
  if (jObject.has("httpHeaders")) {
88
103
  config.setHttpHeaders(jObject.getJSONObject("httpHeaders"));
89
104
  }
@@ -101,6 +116,12 @@ public class ConfigMapper {
101
116
  if (jObject.has("enableWatchdog")) {
102
117
  config.setEnableWatchdog(jObject.getBoolean("enableWatchdog"));
103
118
  }
119
+ if (jObject.has("showTime")) {
120
+ config.setShowTime(jObject.getBoolean("showTime"));
121
+ }
122
+ if (jObject.has("showDistance")) {
123
+ config.setShowDistance(jObject.getBoolean("showDistance"));
124
+ }
104
125
 
105
126
  return config;
106
127
  }
@@ -114,6 +135,10 @@ public class ConfigMapper {
114
135
  json.put("notificationsEnabled", config.getNotificationsEnabled());
115
136
  json.put("notificationTitle", config.getNotificationTitle() != Config.NullString ? config.getNotificationTitle() : JSONObject.NULL);
116
137
  json.put("notificationText", config.getNotificationText() != Config.NullString ? config.getNotificationText() : JSONObject.NULL);
138
+ json.put("notificationSyncTitle", config.getNotificationSyncTitle());
139
+ json.put("notificationSyncText", config.getNotificationSyncText());
140
+ json.put("notificationSyncCompletedText", config.getNotificationSyncCompletedText());
141
+ json.put("notificationSyncFailedText", config.getNotificationSyncFailedText());
117
142
  json.put("notificationIconLarge", config.getLargeNotificationIcon() != Config.NullString ? config.getLargeNotificationIcon() : JSONObject.NULL);
118
143
  json.put("notificationIconSmall", config.getSmallNotificationIcon() != Config.NullString ? config.getSmallNotificationIcon() : JSONObject.NULL);
119
144
  json.put("notificationIconColor", config.getNotificationIconColor() != Config.NullString ? config.getNotificationIconColor() : JSONObject.NULL);
@@ -128,9 +153,12 @@ public class ConfigMapper {
128
153
  json.put("url", config.getUrl() != Config.NullString ? config.getUrl() : JSONObject.NULL);
129
154
  json.put("syncUrl", config.getSyncUrl() != Config.NullString ? config.getSyncUrl() : JSONObject.NULL);
130
155
  json.put("syncThreshold", config.getSyncThreshold());
156
+ json.put("sync", config.getSyncEnabled());
131
157
  json.put("httpHeaders", new JSONObject(config.getHttpHeaders()));
132
158
  json.put("maxLocations", config.getMaxLocations());
133
159
  json.put("enableWatchdog", Boolean.TRUE.equals(config.getEnableWatchdog()));
160
+ json.put("showTime", Boolean.TRUE.equals(config.getShowTime()));
161
+ json.put("showDistance", Boolean.TRUE.equals(config.getShowDistance()));
134
162
  LocationTemplate tpl = config.getTemplate();
135
163
  Object template = JSONObject.NULL;
136
164
  if (tpl instanceof HashMapLocationTemplate) {
@@ -71,10 +71,12 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
71
71
  public static final String ACTION_END_TASK = "endTask";
72
72
  public static final String ACTION_REGISTER_HEADLESS_TASK = "registerHeadlessTask";
73
73
  public static final String ACTION_FORCE_SYNC = "forceSync";
74
+ public static final String ACTION_CLEAR_SYNC = "clearSync";
75
+ public static final String ACTION_GET_PENDING_SYNC_COUNT = "getPendingSyncCount";
74
76
  public static final String ACTION_GET_PLUGIN_VERSION = "getPluginVersion";
75
77
 
76
78
  /** Plugin version; keep in sync with plugin.xml. */
77
- public static final String PLUGIN_VERSION = "3.0.0";
79
+ public static final String PLUGIN_VERSION = "3.1.0";
78
80
 
79
81
  private BackgroundGeolocationFacade facade;
80
82
 
@@ -367,7 +369,35 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
367
369
  return true;
368
370
  } else if (ACTION_FORCE_SYNC.equals(action)) {
369
371
  logger.debug("Forced location sync requested");
370
- facade.forceSync();
372
+ runOnWebViewThread(new Runnable() {
373
+ @Override
374
+ public void run() {
375
+ facade.forceSync();
376
+ callbackContext.success();
377
+ }
378
+ });
379
+ return true;
380
+ } else if (ACTION_CLEAR_SYNC.equals(action)) {
381
+ runOnWebViewThread(new Runnable() {
382
+ @Override
383
+ public void run() {
384
+ facade.clearSync();
385
+ callbackContext.success();
386
+ }
387
+ });
388
+ return true;
389
+ } else if (ACTION_GET_PENDING_SYNC_COUNT.equals(action)) {
390
+ runOnWebViewThread(new Runnable() {
391
+ @Override
392
+ public void run() {
393
+ try {
394
+ long count = facade.getPendingSyncCount();
395
+ callbackContext.success((int) Math.min(count, Integer.MAX_VALUE));
396
+ } catch (Exception e) {
397
+ callbackContext.sendPluginResult(ErrorPluginResult.from("getPendingSyncCount failed", e, PluginException.SERVICE_ERROR));
398
+ }
399
+ }
400
+ });
371
401
  return true;
372
402
  } else if (ACTION_GET_PLUGIN_VERSION.equals(action)) {
373
403
  callbackContext.success(PLUGIN_VERSION);
@@ -34,6 +34,10 @@ public class ConfigMapperTest {
34
34
  Assert.assertEquals(config.isDebugging().booleanValue(), jConfig.getBoolean("debug"));
35
35
  Assert.assertEquals(config.getNotificationTitle(), jConfig.getString("notificationTitle"));
36
36
  Assert.assertEquals(config.getNotificationText(), jConfig.getString("notificationText"));
37
+ Assert.assertEquals(config.getNotificationSyncTitle(), jConfig.getString("notificationSyncTitle"));
38
+ Assert.assertEquals(config.getNotificationSyncText(), jConfig.getString("notificationSyncText"));
39
+ Assert.assertEquals(config.getNotificationSyncCompletedText(), jConfig.getString("notificationSyncCompletedText"));
40
+ Assert.assertEquals(config.getNotificationSyncFailedText(), jConfig.getString("notificationSyncFailedText"));
37
41
  Assert.assertEquals(config.getStopOnTerminate().booleanValue(), jConfig.getBoolean("stopOnTerminate"));
38
42
  Assert.assertEquals(config.getStartOnBoot().booleanValue(), jConfig.getBoolean("startOnBoot"));
39
43
  Assert.assertEquals(config.getLocationProvider().intValue(), jConfig.getInt("locationProvider"));
@@ -48,6 +52,7 @@ public class ConfigMapperTest {
48
52
  Assert.assertEquals(config.getUrl(), jConfig.getString("url"));
49
53
  Assert.assertEquals(config.getSyncUrl(), jConfig.getString("syncUrl"));
50
54
  Assert.assertEquals(config.getSyncThreshold().intValue(), jConfig.getInt("syncThreshold"));
55
+ Assert.assertEquals(Boolean.TRUE.equals(config.getSyncEnabled()), jConfig.getBoolean("sync"));
51
56
  Assert.assertEquals(new JSONObject(config.getHttpHeaders()).toString(), jConfig.getJSONObject("httpHeaders").toString());
52
57
  Assert.assertEquals(config.getMaxLocations().intValue(), jConfig.getInt("maxLocations"));
53
58
  Assert.assertEquals(LocationTemplateFactory.getDefault().toString(), jConfig.get("postTemplate").toString());
@@ -90,6 +95,13 @@ public class ConfigMapperTest {
90
95
  Assert.assertFalse(config.hasSmallNotificationIcon());
91
96
  }
92
97
 
98
+ /** When "sync" is not in JSON, getSyncEnabled() must return true (default). */
99
+ @Test
100
+ public void testSyncDefaultWhenNotInJson() throws JSONException {
101
+ Config config = ConfigMapper.fromJSONObject(new JSONObject());
102
+ Assert.assertTrue("sync not in JSON should default to true", config.getSyncEnabled());
103
+ }
104
+
93
105
  @Test
94
106
  public void testNullablePropsToJSONObject() throws JSONException {
95
107
  Config config = new Config();
@@ -407,9 +407,14 @@ public class BackgroundGeolocationFacade {
407
407
  * Force location sync
408
408
  *
409
409
  * Method is ignoring syncThreshold and also user sync settings preference
410
- * and sync locations to defined syncUrl
410
+ * and sync locations to defined syncUrl. No-op if sync is disabled in config (sync: false).
411
411
  */
412
412
  public void forceSync() {
413
+ Config config = getConfig();
414
+ if (!Boolean.TRUE.equals(config.getSyncEnabled())) {
415
+ logger.debug("Sync disabled in config, skipping forceSync");
416
+ return;
417
+ }
413
418
  logger.debug("Sync locations forced");
414
419
  ResourceResolver resolver = ResourceResolver.newInstance(getContext());
415
420
  Account syncAccount = AccountHelper.CreateSyncAccount(getContext(), resolver.getAccountName(),
@@ -417,6 +422,24 @@ public class BackgroundGeolocationFacade {
417
422
  SyncService.sync(syncAccount, resolver.getAuthority(), true);
418
423
  }
419
424
 
425
+ /**
426
+ * Returns the number of locations pending to be synced (not yet sent to syncUrl).
427
+ */
428
+ public long getPendingSyncCount() {
429
+ LocationDAO dao = DAOFactory.createLocationDAO(getContext());
430
+ return dao.getLocationsForSyncCount(Long.MAX_VALUE);
431
+ }
432
+
433
+ /**
434
+ * Clear the pending sync queue: mark all locations waiting to be synced as deleted.
435
+ * They will not be sent to syncUrl. Use when the user wants to discard pending locations.
436
+ */
437
+ public void clearSync() {
438
+ LocationDAO dao = DAOFactory.createLocationDAO(getContext());
439
+ int count = dao.deletePendingSyncLocations();
440
+ logger.debug("Cleared {} pending sync locations", count);
441
+ }
442
+
420
443
  public int getAuthorizationStatus() {
421
444
  return hasPermissions() ? AUTHORIZATION_AUTHORIZED : AUTHORIZATION_DENIED;
422
445
  }