@josuelmm/cordova-background-geolocation 3.1.1 → 3.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/HISTORY.md +6 -0
  3. package/README.md +15 -2
  4. package/RELEASE.MD +2 -2
  5. package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +57 -1
  6. package/android/common/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +26 -0
  7. package/android/common/src/main/java/com/marianhello/bgloc/PostLocationTask.java +13 -0
  8. package/android/common/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
  9. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +8 -1
  10. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
  11. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
  12. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +5 -1
  13. package/angular/background-geolocation.service.ts +28 -0
  14. package/angular/dist/background-geolocation.service.d.ts +4 -0
  15. package/angular/dist/esm2022/background-geolocation.service.mjs +13 -1
  16. package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs +12 -0
  17. package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs.map +1 -1
  18. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.h +4 -0
  19. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +41 -1
  20. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +4 -0
  21. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +21 -0
  22. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +12 -3
  23. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +5 -0
  24. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.h +29 -0
  25. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.m +31 -0
  26. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.h +25 -0
  27. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.m +153 -0
  28. package/package.json +1 -1
  29. package/plugin.xml +8 -1
  30. package/www/BackgroundGeolocation.d.ts +42 -0
  31. package/www/BackgroundGeolocation.js +24 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.2.0](https://github.com/josuelmm/cordova-background-geolocation/tree/3.2.0) (2026-02-28)
4
+
5
+ ### Added
6
+
7
+ - **Session API for route/recording** — Lets the app keep a full copy of all locations for the *current recording session* in the plugin, independent of the sync queue. When the user reopens the app without internet, the app can restore the entire route from the plugin (no need to rely on `localStorage` for points).
8
+ - **`startSession(success?, fail?)`** — Call when the user starts a route (e.g. "Start" button). Clears the session table and from then on every new location is also stored in the session table. Session data is **not** removed when locations are synced to the server.
9
+ - **`getSessionLocations(success?, fail?)`** — Returns all locations currently in the session table, ordered by time. Same format as `Location` (latitude, longitude, time, speed, altitude, bearing, accuracy, etc.). Use when reopening without internet to rebuild the track.
10
+ - **`clearSession(success?, fail?)`** — Call when the route is finished and sync has succeeded. Clears the session table so the next `startSession()` starts clean.
11
+ - **`getSessionLocationsCount(success?, fail?)`** — Returns the number of locations in the session (e.g. to show "X points" without loading all).
12
+ - **Android:** New table `location_session` (DB version 20), `SessionLocationDAO`, and persistence from `PostLocationTask` when session is active. Session state stored in `SharedPreferences`.
13
+ - **iOS:** New table `location_session` (DB version 5), `MAURSessionLocationDAO` (singleton), and persistence from `MAURPostLocationTask` when session is active. Session state stored in `NSUserDefaults`.
14
+ - **JS / TypeScript:** The four session methods are available on the global plugin and in the `.d.ts`. **Angular:** `BackgroundGeolocationService` exposes `startSession`, `getSessionLocations`, `clearSession`, `getSessionLocationsCount`.
15
+
16
+ ### Documentation
17
+
18
+ - **README.md** — New section *"New in 3.2.0"* describing the session API and typical flow (start route → startSession; reopen without internet → getSessionLocations; finish route → clearSession).
19
+ - **docs/api.md** — Quick reference and full sections for `startSession`, `getSessionLocations`, `clearSession`, `getSessionLocationsCount`.
20
+ - **docs/angular.md** — Session methods added to the methods table.
21
+ - **docs/index.md** — Mention of session/route restore without internet.
22
+ - **CHANGELOG.md** — This entry. **HISTORY.md** — 3.2.0 session methods. **RELEASE.MD** — Version example updated.
23
+
24
+ ### Changed
25
+
26
+ - Version bump to 3.2.0.
27
+
28
+ [Full Changelog](https://github.com/josuelmm/cordova-background-geolocation/compare/3.1.1...3.2.0)
29
+
30
+ ---
31
+
3
32
  ## [3.1.1](https://github.com/josuelmm/cordova-background-geolocation/tree/3.1.1) (2026-02-27)
4
33
 
5
34
  ### Added
package/HISTORY.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  **for cordova-plugin-background-geolocation**
4
4
 
5
+ ## [3.2.0] - 2026-02-28
6
+
7
+ ### Added
8
+
9
+ - **Session API (route/recording):** `startSession()`, `getSessionLocations()`, `clearSession()`, `getSessionLocationsCount()`. Separate session table (Android DB v20, iOS DB v5) so the app can restore the full route when reopening without internet. Session is cleared on `startSession()` and `clearSession()`; not cleared when sync succeeds.
10
+
5
11
  ## [3.1.0] - 2019-09-24
6
12
 
7
13
  ### Fixed
package/README.md CHANGED
@@ -316,11 +316,15 @@ More on sync (headers, retries, postTemplate): [HTTP posting](docs/http_posting.
316
316
  | `deleteLocation(id, success, fail)` | Delete one location by id. |
317
317
  | `deleteAllLocations(success, fail)` | Delete all stored locations. |
318
318
  | `getCurrentLocation(success, fail, options)` | One-shot location (e.g. timeout, maximumAge). |
319
- | `getPluginVersion(success, fail)` | Plugin version string (e.g. "3.1.1"). |
319
+ | `getPluginVersion(success, fail)` | Plugin version string (e.g. "3.2.0"). |
320
320
  | `checkStatus(success, fail)` | Service status (isRunning, authorization, etc.). |
321
321
  | `showAppSettings()` / `openSettings()` | Open app settings. |
322
322
  | `showLocationSettings()` | Open system location settings. |
323
323
  | `getLogEntries(limit, fromId, minLevel, success, fail)` | Debug log entries. |
324
+ | `startSession(success, fail)` | Start session: clear session table and store all new locations until `clearSession()`. |
325
+ | `getSessionLocations(success, fail)` | All locations in current session (restore route when reopening without internet). |
326
+ | `clearSession(success, fail)` | Clear session table (call when route finished and sync OK). |
327
+ | `getSessionLocationsCount(success, fail)` | Number of locations in current session. |
324
328
 
325
329
  All methods return a **Promise** if you omit the `success` / `fail` callbacks.
326
330
 
@@ -343,6 +347,15 @@ Subscribe with `BackgroundGeolocation.on(eventName, callback)`. Unsubscribe with
343
347
 
344
348
  Full event payloads and options: [Events](docs/events.md). Full API (all options, all methods): [API](docs/api.md).
345
349
 
350
+ ### New in 3.2.0
351
+
352
+ - **Session API for route/recording** — Store all locations for the *current route* in the plugin, independent of sync. When the user reopens the app without internet, you can restore the full track from the plugin (no need for `localStorage` for points).
353
+ - **`startSession()`** — Call when the user starts a route. Clears the session table; from then on every location is also saved in the session table (and not removed when synced).
354
+ - **`getSessionLocations()`** — Returns all session locations (same format as `Location`: latitude, longitude, time, speed, altitude, bearing, accuracy). Use to rebuild the track after reopening without internet.
355
+ - **`clearSession()`** — Call when the route is finished and sync succeeded. Clears the session table.
356
+ - **`getSessionLocationsCount()`** — Returns how many points are in the session (e.g. to show "X points" in the UI).
357
+ - Typical flow: **Start route** → `startSession()` then `start()`. **Reopen without internet** → `getSessionLocations()` and redraw the route. **Finish route and sync OK** → `clearSession()`.
358
+
346
359
  ### New in 3.1.1
347
360
 
348
361
  - **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).
@@ -435,7 +448,7 @@ This README is the main entry point. For more detail, edge cases and examples us
435
448
 
436
449
  | Doc | What you’ll find |
437
450
  |-----|------------------|
438
- | **[API reference](docs/api.md)** | Every `configure` option, every method (`configure`, `start`, `stop`, `getPendingSyncCount`, `forceSync`, `clearSync`, `getConfig`, `getLocations`, etc.), TypeScript types. |
451
+ | **[API reference](docs/api.md)** | Every `configure` option, every method (`configure`, `start`, `stop`, `getPendingSyncCount`, `forceSync`, `clearSync`, `startSession`, `getSessionLocations`, `clearSession`, `getSessionLocationsCount`, `getConfig`, `getLocations`, etc.), TypeScript types. |
439
452
  | **[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
453
  | **[Events](docs/events.md)** | All events (`location`, `error`, `stationary`, `activity`, `http_authorization`, etc.) and payloads. |
441
454
  | **[Angular / Ionic](docs/angular.md)** | Injectable service, module, lazy-loaded modules and token “must be defined”, `ng serve` / browser build. |
package/RELEASE.MD CHANGED
@@ -1,7 +1,7 @@
1
1
  In order to release a version the following actions are needed:
2
2
 
3
3
  1. **Version and docs**
4
- - Set the new version (e.g. `3.1.1`) in:
4
+ - Set the new version (e.g. `3.2.0`) in:
5
5
  - `package.json` (`version` field)
6
6
  - `plugin.xml` (root `<plugin>` attribute `version`)
7
7
  - Add a release entry in `CHANGELOG.md` (date, Added/Fixed/Changed/Documentation).
@@ -12,5 +12,5 @@ In order to release a version the following actions are needed:
12
12
 
13
13
  3. **Git tag and release**
14
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`).
15
+ - Create a tag with the version name (e.g. `v3.2.0`).
16
16
  - Publish the release. GitHub Actions will publish the package to npm.
@@ -73,10 +73,14 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
73
73
  public static final String ACTION_FORCE_SYNC = "forceSync";
74
74
  public static final String ACTION_CLEAR_SYNC = "clearSync";
75
75
  public static final String ACTION_GET_PENDING_SYNC_COUNT = "getPendingSyncCount";
76
+ public static final String ACTION_START_SESSION = "startSession";
77
+ public static final String ACTION_GET_SESSION_LOCATIONS = "getSessionLocations";
78
+ public static final String ACTION_CLEAR_SESSION = "clearSession";
79
+ public static final String ACTION_GET_SESSION_LOCATIONS_COUNT = "getSessionLocationsCount";
76
80
  public static final String ACTION_GET_PLUGIN_VERSION = "getPluginVersion";
77
81
 
78
82
  /** Plugin version; keep in sync with plugin.xml. */
79
- public static final String PLUGIN_VERSION = "3.1.0";
83
+ public static final String PLUGIN_VERSION = "3.2.0";
80
84
 
81
85
  private BackgroundGeolocationFacade facade;
82
86
 
@@ -399,6 +403,49 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
399
403
  }
400
404
  });
401
405
  return true;
406
+ } else if (ACTION_START_SESSION.equals(action)) {
407
+ runOnWebViewThread(new Runnable() {
408
+ @Override
409
+ public void run() {
410
+ facade.startSession();
411
+ callbackContext.success();
412
+ }
413
+ });
414
+ return true;
415
+ } else if (ACTION_GET_SESSION_LOCATIONS.equals(action)) {
416
+ runOnWebViewThread(new Runnable() {
417
+ @Override
418
+ public void run() {
419
+ try {
420
+ callbackContext.success(getSessionLocations());
421
+ } catch (JSONException e) {
422
+ callbackContext.sendPluginResult(ErrorPluginResult.from("getSessionLocations failed", e, PluginException.JSON_ERROR));
423
+ }
424
+ }
425
+ });
426
+ return true;
427
+ } else if (ACTION_CLEAR_SESSION.equals(action)) {
428
+ runOnWebViewThread(new Runnable() {
429
+ @Override
430
+ public void run() {
431
+ facade.clearSession();
432
+ callbackContext.success();
433
+ }
434
+ });
435
+ return true;
436
+ } else if (ACTION_GET_SESSION_LOCATIONS_COUNT.equals(action)) {
437
+ runOnWebViewThread(new Runnable() {
438
+ @Override
439
+ public void run() {
440
+ try {
441
+ int count = facade.getSessionLocationsCount();
442
+ callbackContext.success(count);
443
+ } catch (Exception e) {
444
+ callbackContext.sendPluginResult(ErrorPluginResult.from("getSessionLocationsCount failed", e, PluginException.SERVICE_ERROR));
445
+ }
446
+ }
447
+ });
448
+ return true;
402
449
  } else if (ACTION_GET_PLUGIN_VERSION.equals(action)) {
403
450
  callbackContext.success(PLUGIN_VERSION);
404
451
  return true;
@@ -557,6 +604,15 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
557
604
  return jsonLocationsArray;
558
605
  }
559
606
 
607
+ private JSONArray getSessionLocations() throws JSONException {
608
+ JSONArray jsonLocationsArray = new JSONArray();
609
+ Collection<BackgroundLocation> locations = facade.getSessionLocations();
610
+ for (BackgroundLocation location : locations) {
611
+ jsonLocationsArray.put(location.toJSONObjectWithId());
612
+ }
613
+ return jsonLocationsArray;
614
+ }
615
+
560
616
  private JSONArray getLogs(Integer limit, int offset, String minLevel) throws Exception {
561
617
  JSONArray jsonLogsArray = new JSONArray();
562
618
  Collection<LogEntry> logEntries = facade.getLogEntries(limit, offset, minLevel);
@@ -26,11 +26,13 @@ import com.marianhello.bgloc.data.BackgroundLocation;
26
26
  import com.marianhello.bgloc.data.ConfigurationDAO;
27
27
  import com.marianhello.bgloc.data.DAOFactory;
28
28
  import com.marianhello.bgloc.data.LocationDAO;
29
+ import com.marianhello.bgloc.data.SessionLocationDAO;
29
30
  import com.marianhello.bgloc.provider.LocationProvider;
30
31
  import com.marianhello.bgloc.service.LocationService;
31
32
  import com.marianhello.bgloc.service.LocationServiceImpl;
32
33
  import com.marianhello.bgloc.service.LocationServiceProxy;
33
34
  import com.marianhello.bgloc.data.LocationTransform;
35
+ import com.marianhello.bgloc.data.sqlite.SQLiteSessionLocationDAO;
34
36
  import com.marianhello.bgloc.sync.AccountHelper;
35
37
  import com.marianhello.bgloc.sync.NotificationHelper;
36
38
  import com.marianhello.bgloc.sync.SyncService;
@@ -294,6 +296,30 @@ public class BackgroundGeolocationFacade {
294
296
  return dao.getValidLocationsAndDelete();
295
297
  }
296
298
 
299
+ /** Clear session table and start storing all new locations in session. Call when user starts a route. */
300
+ public void startSession() {
301
+ SessionLocationDAO dao = new SQLiteSessionLocationDAO(getContext());
302
+ dao.startSession();
303
+ }
304
+
305
+ /** Return all locations stored in the current session (ordered by time). */
306
+ public Collection<BackgroundLocation> getSessionLocations() {
307
+ SessionLocationDAO dao = new SQLiteSessionLocationDAO(getContext());
308
+ return dao.getSessionLocations();
309
+ }
310
+
311
+ /** Clear session table and stop storing. Call when route is finished and sync OK. */
312
+ public void clearSession() {
313
+ SessionLocationDAO dao = new SQLiteSessionLocationDAO(getContext());
314
+ dao.clearSession();
315
+ }
316
+
317
+ /** Number of locations in the current session. */
318
+ public int getSessionLocationsCount() {
319
+ SessionLocationDAO dao = new SQLiteSessionLocationDAO(getContext());
320
+ return dao.getSessionLocationsCount();
321
+ }
322
+
297
323
  public BackgroundLocation getStationaryLocation() {
298
324
  return mStationaryLocation;
299
325
  }
@@ -2,6 +2,7 @@ package com.marianhello.bgloc;
2
2
 
3
3
  import com.marianhello.bgloc.data.BackgroundLocation;
4
4
  import com.marianhello.bgloc.data.LocationDAO;
5
+ import com.marianhello.bgloc.data.SessionLocationDAO;
5
6
  import com.marianhello.logging.LoggerManager;
6
7
 
7
8
  import org.json.JSONArray;
@@ -29,6 +30,7 @@ import java.util.concurrent.TimeUnit;
29
30
  */
30
31
  public class PostLocationTask {
31
32
  private final LocationDAO mLocationDAO;
33
+ private final SessionLocationDAO mSessionDAO;
32
34
  private final PostLocationTaskListener mTaskListener;
33
35
  private final ConnectivityListener mConnectivityListener;
34
36
 
@@ -48,10 +50,17 @@ public class PostLocationTask {
48
50
 
49
51
  public PostLocationTask(LocationDAO dao, PostLocationTaskListener taskListener,
50
52
  ConnectivityListener connectivityListener) {
53
+ this(dao, null, taskListener, connectivityListener);
54
+ }
55
+
56
+ public PostLocationTask(LocationDAO dao, SessionLocationDAO sessionDAO,
57
+ PostLocationTaskListener taskListener,
58
+ ConnectivityListener connectivityListener) {
51
59
  logger = LoggerManager.getLogger(PostLocationTask.class);
52
60
  logger.info("Creating PostLocationTask");
53
61
 
54
62
  mLocationDAO = dao;
63
+ mSessionDAO = sessionDAO;
55
64
  mTaskListener = taskListener;
56
65
  mConnectivityListener = connectivityListener;
57
66
 
@@ -84,6 +93,10 @@ public class PostLocationTask {
84
93
  long locationId = mLocationDAO.persistLocation(location);
85
94
  location.setLocationId(locationId);
86
95
 
96
+ if (mSessionDAO != null && mSessionDAO.isSessionActive()) {
97
+ mSessionDAO.persistSessionLocation(location);
98
+ }
99
+
87
100
  try {
88
101
  mExecutor.execute(new Runnable() {
89
102
  @Override
@@ -0,0 +1,18 @@
1
+ package com.marianhello.bgloc.data;
2
+
3
+ import java.util.Collection;
4
+
5
+ /**
6
+ * DAO for the current recording session locations.
7
+ * Session is independent of sync: locations are kept until startSession() or clearSession().
8
+ */
9
+ public interface SessionLocationDAO {
10
+ /** Clear session table and set session active. Call when user starts a route. */
11
+ void startSession();
12
+ /** Clear session table and set session inactive. Call when route is finished and sync OK. */
13
+ void clearSession();
14
+ boolean isSessionActive();
15
+ void persistSessionLocation(BackgroundLocation location);
16
+ Collection<BackgroundLocation> getSessionLocations();
17
+ int getSessionLocationsCount();
18
+ }
@@ -8,6 +8,7 @@ import android.util.Log;
8
8
 
9
9
  import com.marianhello.bgloc.data.sqlite.SQLiteLocationContract.LocationEntry;
10
10
  import com.marianhello.bgloc.data.sqlite.SQLiteConfigurationContract.ConfigurationEntry;
11
+ import com.marianhello.bgloc.data.sqlite.SQLiteSessionContract.SessionEntry;
11
12
 
12
13
  import java.util.ArrayList;
13
14
 
@@ -21,7 +22,7 @@ import static com.marianhello.bgloc.data.sqlite.SQLiteLocationContract.LocationE
21
22
  public class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
22
23
  private static final String TAG = SQLiteOpenHelper.class.getName();
23
24
  public static final String SQLITE_DATABASE_NAME = "cordova_bg_geolocation.db";
24
- public static final int DATABASE_VERSION = 19;
25
+ public static final int DATABASE_VERSION = 20;
25
26
 
26
27
  public static final String TEXT_TYPE = " TEXT";
27
28
  public static final String INTEGER_TYPE = " INTEGER";
@@ -66,6 +67,8 @@ public class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
66
67
  execAndLogSql(db, SQL_CREATE_CONFIG_TABLE);
67
68
  execAndLogSql(db, SQL_CREATE_LOCATION_TABLE_TIME_IDX);
68
69
  execAndLogSql(db, SQL_CREATE_LOCATION_TABLE_BATCH_ID_IDX);
70
+ execAndLogSql(db, SessionEntry.SQL_CREATE_SESSION_TABLE);
71
+ execAndLogSql(db, SessionEntry.SQL_CREATE_SESSION_TABLE_TIME_IDX);
69
72
  }
70
73
 
71
74
  @Override
@@ -137,6 +140,9 @@ public class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
137
140
  " ADD COLUMN " + ConfigurationEntry.COLUMN_NAME_SHOW_TIME + INTEGER_TYPE);
138
141
  alterSql.add("ALTER TABLE " + ConfigurationEntry.TABLE_NAME +
139
142
  " ADD COLUMN " + ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE + INTEGER_TYPE);
143
+ case 19:
144
+ alterSql.add(SessionEntry.SQL_CREATE_SESSION_TABLE);
145
+ alterSql.add(SessionEntry.SQL_CREATE_SESSION_TABLE_TIME_IDX);
140
146
 
141
147
  break; // DO NOT FORGET TO MOVE DOWN BREAK ON DB UPGRADE!!!
142
148
  default:
@@ -154,6 +160,7 @@ public class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
154
160
  // we don't support db downgrade yet, instead we drop table and start over
155
161
  execAndLogSql(db, SQL_DROP_LOCATION_TABLE);
156
162
  execAndLogSql(db, SQL_DROP_CONFIG_TABLE);
163
+ execAndLogSql(db, SessionEntry.SQL_DROP_SESSION_TABLE);
157
164
  onCreate(db);
158
165
  }
159
166
 
@@ -0,0 +1,74 @@
1
+ package com.marianhello.bgloc.data.sqlite;
2
+
3
+ import android.provider.BaseColumns;
4
+
5
+ import static com.marianhello.bgloc.data.sqlite.SQLiteOpenHelper.COMMA_SEP;
6
+ import static com.marianhello.bgloc.data.sqlite.SQLiteOpenHelper.INTEGER_TYPE;
7
+ import static com.marianhello.bgloc.data.sqlite.SQLiteOpenHelper.REAL_TYPE;
8
+ import static com.marianhello.bgloc.data.sqlite.SQLiteOpenHelper.TEXT_TYPE;
9
+
10
+ /**
11
+ * Contract for the "session" location table.
12
+ * Stores all locations for the current recording session (route).
13
+ * Cleared on startSession() and clearSession(); not cleared when sync succeeds.
14
+ */
15
+ public final class SQLiteSessionContract {
16
+
17
+ public SQLiteSessionContract() {}
18
+
19
+ public static abstract class SessionEntry implements BaseColumns {
20
+ public static final String TABLE_NAME = "location_session";
21
+ public static final String COLUMN_NAME_NULLABLE = "NULLHACK";
22
+ public static final String COLUMN_NAME_TIME = "time";
23
+ public static final String COLUMN_NAME_ACCURACY = "accuracy";
24
+ public static final String COLUMN_NAME_VERTICAL_ACCURACY = "vertical_accuracy";
25
+ public static final String COLUMN_NAME_SPEED = "speed";
26
+ public static final String COLUMN_NAME_BEARING = "bearing";
27
+ public static final String COLUMN_NAME_ALTITUDE = "altitude";
28
+ public static final String COLUMN_NAME_LATITUDE = "latitude";
29
+ public static final String COLUMN_NAME_LONGITUDE = "longitude";
30
+ public static final String COLUMN_NAME_RADIUS = "radius";
31
+ public static final String COLUMN_NAME_HAS_ACCURACY = "has_accuracy";
32
+ public static final String COLUMN_NAME_HAS_VERTICAL_ACCURACY = "has_vertical_accuracy";
33
+ public static final String COLUMN_NAME_HAS_SPEED = "has_speed";
34
+ public static final String COLUMN_NAME_HAS_BEARING = "has_bearing";
35
+ public static final String COLUMN_NAME_HAS_ALTITUDE = "has_altitude";
36
+ public static final String COLUMN_NAME_HAS_RADIUS = "has_radius";
37
+ public static final String COLUMN_NAME_PROVIDER = "provider";
38
+ public static final String COLUMN_NAME_LOCATION_PROVIDER = "service_provider";
39
+ public static final String COLUMN_NAME_STATUS = "valid";
40
+ public static final String COLUMN_NAME_BATCH_START_MILLIS = "batch_start";
41
+ public static final String COLUMN_NAME_MOCK_FLAGS = "mock_flags";
42
+
43
+ public static final String SQL_CREATE_SESSION_TABLE =
44
+ "CREATE TABLE " + SessionEntry.TABLE_NAME + " (" +
45
+ SessionEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
46
+ SessionEntry.COLUMN_NAME_TIME + INTEGER_TYPE + COMMA_SEP +
47
+ SessionEntry.COLUMN_NAME_ACCURACY + REAL_TYPE + COMMA_SEP +
48
+ SessionEntry.COLUMN_NAME_VERTICAL_ACCURACY + REAL_TYPE + COMMA_SEP +
49
+ SessionEntry.COLUMN_NAME_SPEED + REAL_TYPE + COMMA_SEP +
50
+ SessionEntry.COLUMN_NAME_BEARING + REAL_TYPE + COMMA_SEP +
51
+ SessionEntry.COLUMN_NAME_ALTITUDE + REAL_TYPE + COMMA_SEP +
52
+ SessionEntry.COLUMN_NAME_LATITUDE + REAL_TYPE + COMMA_SEP +
53
+ SessionEntry.COLUMN_NAME_LONGITUDE + REAL_TYPE + COMMA_SEP +
54
+ SessionEntry.COLUMN_NAME_RADIUS + REAL_TYPE + COMMA_SEP +
55
+ SessionEntry.COLUMN_NAME_HAS_ACCURACY + INTEGER_TYPE + COMMA_SEP +
56
+ SessionEntry.COLUMN_NAME_HAS_VERTICAL_ACCURACY + INTEGER_TYPE + COMMA_SEP +
57
+ SessionEntry.COLUMN_NAME_HAS_SPEED + INTEGER_TYPE + COMMA_SEP +
58
+ SessionEntry.COLUMN_NAME_HAS_BEARING + INTEGER_TYPE + COMMA_SEP +
59
+ SessionEntry.COLUMN_NAME_HAS_ALTITUDE + INTEGER_TYPE + COMMA_SEP +
60
+ SessionEntry.COLUMN_NAME_HAS_RADIUS + INTEGER_TYPE + COMMA_SEP +
61
+ SessionEntry.COLUMN_NAME_PROVIDER + TEXT_TYPE + COMMA_SEP +
62
+ SessionEntry.COLUMN_NAME_LOCATION_PROVIDER + INTEGER_TYPE + COMMA_SEP +
63
+ SessionEntry.COLUMN_NAME_STATUS + INTEGER_TYPE + COMMA_SEP +
64
+ SessionEntry.COLUMN_NAME_BATCH_START_MILLIS + INTEGER_TYPE + COMMA_SEP +
65
+ SessionEntry.COLUMN_NAME_MOCK_FLAGS + INTEGER_TYPE +
66
+ " )";
67
+
68
+ public static final String SQL_DROP_SESSION_TABLE =
69
+ "DROP TABLE IF EXISTS " + SessionEntry.TABLE_NAME;
70
+
71
+ public static final String SQL_CREATE_SESSION_TABLE_TIME_IDX =
72
+ "CREATE INDEX session_time_idx ON " + SessionEntry.TABLE_NAME + " (" + SessionEntry.COLUMN_NAME_TIME + ")";
73
+ }
74
+ }
@@ -0,0 +1,169 @@
1
+ package com.marianhello.bgloc.data.sqlite;
2
+
3
+ import android.content.ContentValues;
4
+ import android.content.Context;
5
+ import android.database.Cursor;
6
+ import android.database.sqlite.SQLiteDatabase;
7
+ import com.marianhello.bgloc.data.BackgroundLocation;
8
+ import com.marianhello.bgloc.data.SessionLocationDAO;
9
+
10
+ import java.util.ArrayList;
11
+ import java.util.Collection;
12
+
13
+ public class SQLiteSessionLocationDAO implements SessionLocationDAO {
14
+
15
+ private static final String PREFS_NAME = "bgloc_session";
16
+ private static final String KEY_SESSION_ACTIVE = "session_active";
17
+
18
+ private final SQLiteDatabase db;
19
+ private final Context context;
20
+
21
+ public SQLiteSessionLocationDAO(Context context) {
22
+ this.context = context.getApplicationContext();
23
+ SQLiteOpenHelper helper = SQLiteOpenHelper.getHelper(this.context);
24
+ this.db = helper.getWritableDatabase();
25
+ }
26
+
27
+ @Override
28
+ public void startSession() {
29
+ db.delete(SQLiteSessionContract.SessionEntry.TABLE_NAME, null, null);
30
+ setSessionActive(true);
31
+ }
32
+
33
+ @Override
34
+ public void clearSession() {
35
+ db.delete(SQLiteSessionContract.SessionEntry.TABLE_NAME, null, null);
36
+ setSessionActive(false);
37
+ }
38
+
39
+ @Override
40
+ public boolean isSessionActive() {
41
+ return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
42
+ .getBoolean(KEY_SESSION_ACTIVE, false);
43
+ }
44
+
45
+ private void setSessionActive(boolean active) {
46
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
47
+ .edit()
48
+ .putBoolean(KEY_SESSION_ACTIVE, active)
49
+ .apply();
50
+ }
51
+
52
+ @Override
53
+ public void persistSessionLocation(BackgroundLocation location) {
54
+ if (!isSessionActive() || location == null) return;
55
+ ContentValues values = getContentValues(location);
56
+ db.insertOrThrow(SQLiteSessionContract.SessionEntry.TABLE_NAME,
57
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_NULLABLE, values);
58
+ }
59
+
60
+ @Override
61
+ public Collection<BackgroundLocation> getSessionLocations() {
62
+ Collection<BackgroundLocation> locations = new ArrayList<>();
63
+ String orderBy = SQLiteSessionContract.SessionEntry.COLUMN_NAME_TIME + " ASC";
64
+ Cursor cursor = null;
65
+ try {
66
+ cursor = db.query(
67
+ SQLiteSessionContract.SessionEntry.TABLE_NAME,
68
+ queryColumns(),
69
+ null, null, null, null, orderBy);
70
+ while (cursor.moveToNext()) {
71
+ locations.add(hydrate(cursor));
72
+ }
73
+ } finally {
74
+ if (cursor != null) cursor.close();
75
+ }
76
+ return locations;
77
+ }
78
+
79
+ @Override
80
+ public int getSessionLocationsCount() {
81
+ Cursor cursor = null;
82
+ try {
83
+ cursor = db.rawQuery("SELECT COUNT(*) FROM " + SQLiteSessionContract.SessionEntry.TABLE_NAME, null);
84
+ return cursor.moveToFirst() ? cursor.getInt(0) : 0;
85
+ } finally {
86
+ if (cursor != null) cursor.close();
87
+ }
88
+ }
89
+
90
+ private BackgroundLocation hydrate(Cursor c) {
91
+ BackgroundLocation l = new BackgroundLocation(c.getString(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_PROVIDER)));
92
+ l.setTime(c.getLong(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_TIME)));
93
+ if (c.getInt(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_ACCURACY)) == 1) {
94
+ l.setAccuracy(c.getFloat(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_ACCURACY)));
95
+ }
96
+ if (c.getInt(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_VERTICAL_ACCURACY)) == 1) {
97
+ l.setVerticalAccuracy(c.getFloat(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_VERTICAL_ACCURACY)));
98
+ }
99
+ if (c.getInt(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_SPEED)) == 1) {
100
+ l.setSpeed(c.getFloat(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_SPEED)));
101
+ }
102
+ if (c.getInt(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_BEARING)) == 1) {
103
+ l.setBearing(c.getFloat(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_BEARING)));
104
+ }
105
+ if (c.getInt(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_ALTITUDE)) == 1) {
106
+ l.setAltitude(c.getDouble(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_ALTITUDE)));
107
+ }
108
+ if (c.getInt(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_RADIUS)) == 1) {
109
+ l.setRadius(c.getFloat(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_RADIUS)));
110
+ }
111
+ l.setLatitude(c.getDouble(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_LATITUDE)));
112
+ l.setLongitude(c.getDouble(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_LONGITUDE)));
113
+ l.setLocationProvider(c.getInt(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_LOCATION_PROVIDER)));
114
+ l.setLocationId(c.getLong(c.getColumnIndex(SQLiteSessionContract.SessionEntry._ID)));
115
+ l.setMockFlags(c.getInt(c.getColumnIndex(SQLiteSessionContract.SessionEntry.COLUMN_NAME_MOCK_FLAGS)));
116
+ return l;
117
+ }
118
+
119
+ private ContentValues getContentValues(BackgroundLocation l) {
120
+ ContentValues values = new ContentValues();
121
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_PROVIDER, l.getProvider());
122
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_TIME, l.getTime());
123
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_ACCURACY, l.getAccuracy());
124
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_VERTICAL_ACCURACY, l.getVerticalAccuracy());
125
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_SPEED, l.getSpeed());
126
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_BEARING, l.getBearing());
127
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_ALTITUDE, l.getAltitude());
128
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_RADIUS, l.getRadius());
129
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_LATITUDE, l.getLatitude());
130
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_LONGITUDE, l.getLongitude());
131
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_ACCURACY, l.hasAccuracy() ? 1 : 0);
132
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_VERTICAL_ACCURACY, l.hasVerticalAccuracy() ? 1 : 0);
133
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_SPEED, l.hasSpeed() ? 1 : 0);
134
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_BEARING, l.hasBearing() ? 1 : 0);
135
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_ALTITUDE, l.hasAltitude() ? 1 : 0);
136
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_RADIUS, l.hasRadius() ? 1 : 0);
137
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_LOCATION_PROVIDER, l.getLocationProvider());
138
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_STATUS, 0);
139
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_BATCH_START_MILLIS, 0L);
140
+ values.put(SQLiteSessionContract.SessionEntry.COLUMN_NAME_MOCK_FLAGS, l.getMockFlags());
141
+ return values;
142
+ }
143
+
144
+ private String[] queryColumns() {
145
+ return new String[]{
146
+ SQLiteSessionContract.SessionEntry._ID,
147
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_PROVIDER,
148
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_TIME,
149
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_ACCURACY,
150
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_VERTICAL_ACCURACY,
151
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_SPEED,
152
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_BEARING,
153
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_ALTITUDE,
154
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_RADIUS,
155
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_LATITUDE,
156
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_LONGITUDE,
157
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_ACCURACY,
158
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_VERTICAL_ACCURACY,
159
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_SPEED,
160
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_BEARING,
161
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_ALTITUDE,
162
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_HAS_RADIUS,
163
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_LOCATION_PROVIDER,
164
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_STATUS,
165
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_BATCH_START_MILLIS,
166
+ SQLiteSessionContract.SessionEntry.COLUMN_NAME_MOCK_FLAGS
167
+ };
168
+ }
169
+ }
@@ -49,7 +49,9 @@ import com.marianhello.bgloc.data.BackgroundLocation;
49
49
  import com.marianhello.bgloc.data.ConfigurationDAO;
50
50
  import com.marianhello.bgloc.data.DAOFactory;
51
51
  import com.marianhello.bgloc.data.LocationDAO;
52
+ import com.marianhello.bgloc.data.SessionLocationDAO;
52
53
  import com.marianhello.bgloc.data.LocationTransform;
54
+ import com.marianhello.bgloc.data.sqlite.SQLiteSessionLocationDAO;
53
55
  import com.marianhello.bgloc.headless.AbstractTaskRunner;
54
56
  import com.marianhello.bgloc.headless.ActivityTask;
55
57
  import com.marianhello.bgloc.headless.LocationTask;
@@ -125,6 +127,7 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
125
127
  private HandlerThread mHandlerThread;
126
128
  private ServiceHandler mServiceHandler;
127
129
  private LocationDAO mLocationDAO;
130
+ private SessionLocationDAO mSessionDAO;
128
131
  private PostLocationTask mPostLocationTask;
129
132
  private String mHeadlessTaskRunnerClass;
130
133
  private TaskRunner mHeadlessTaskRunner;
@@ -255,8 +258,9 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
255
258
  ContentResolver.setSyncAutomatically(mSyncAccount, authority, true);
256
259
 
257
260
  mLocationDAO = DAOFactory.createLocationDAO(this);
261
+ mSessionDAO = new SQLiteSessionLocationDAO(this);
258
262
 
259
- mPostLocationTask = new PostLocationTask(mLocationDAO,
263
+ mPostLocationTask = new PostLocationTask(mLocationDAO, mSessionDAO,
260
264
  new PostLocationTask.PostLocationTaskListener() {
261
265
  @Override
262
266
  public void onRequestedAbortUpdates() {
@@ -168,6 +168,34 @@ export class BackgroundGeolocationService {
168
168
  return this.ensurePlugin().getPendingSyncCount(success, fail);
169
169
  }
170
170
 
171
+ startSession(
172
+ success?: () => void,
173
+ fail?: (error: any) => void
174
+ ): Promise<void> {
175
+ return this.ensurePlugin().startSession(success, fail);
176
+ }
177
+
178
+ getSessionLocations(
179
+ success?: (locations: any[]) => void,
180
+ fail?: (error: any) => void
181
+ ): Promise<any[]> {
182
+ return this.ensurePlugin().getSessionLocations(success, fail);
183
+ }
184
+
185
+ clearSession(
186
+ success?: () => void,
187
+ fail?: (error: any) => void
188
+ ): Promise<void> {
189
+ return this.ensurePlugin().clearSession(success, fail);
190
+ }
191
+
192
+ getSessionLocationsCount(
193
+ success?: (count: number) => void,
194
+ fail?: (error: any) => void
195
+ ): Promise<number> {
196
+ return this.ensurePlugin().getSessionLocationsCount(success, fail);
197
+ }
198
+
171
199
  getConfig(
172
200
  success?: (config: any) => void,
173
201
  fail?: (error: any) => void