@josuelmm/cordova-background-geolocation 4.2.3 → 4.5.2

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 (103) hide show
  1. package/.npmignore +11 -0
  2. package/CHANGELOG.md +261 -0
  3. package/README.md +306 -115
  4. package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +34 -0
  5. package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +61 -1
  6. package/android/common/src/main/AndroidManifest.xml +1 -1
  7. package/android/common/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +20 -3
  8. package/android/common/src/main/java/com/marianhello/bgloc/Config.java +87 -1
  9. package/android/common/src/main/java/com/marianhello/bgloc/data/BackgroundLocation.java +94 -0
  10. package/android/common/src/main/java/com/marianhello/bgloc/data/ConfigJsonMapper.java +211 -0
  11. package/android/common/src/main/java/com/marianhello/bgloc/data/LocationTemplateFactory.java +6 -0
  12. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationContract.java +5 -1
  13. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationDAO.java +32 -1
  14. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationContract.java +12 -2
  15. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationDAO.java +33 -2
  16. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +15 -1
  17. package/android/common/src/main/java/com/marianhello/bgloc/provider/AbstractLocationProvider.java +48 -1
  18. package/android/common/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +105 -6
  19. package/android/common/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +336 -250
  20. package/android/common/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +69 -19
  21. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +246 -21
  22. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +5 -2
  23. package/android/common/src/main/java/com/marianhello/bgloc/sync/BatchManager.java +46 -13
  24. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +23 -1
  25. package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.m +208 -70
  26. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +132 -5
  27. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +20 -0
  28. package/ios/common/BackgroundGeolocation/MAURConfig.h +7 -0
  29. package/ios/common/BackgroundGeolocation/MAURConfig.m +37 -2
  30. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +3 -0
  31. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +3 -1
  32. package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +10 -1
  33. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +15 -1
  34. package/ios/common/BackgroundGeolocation/MAURLocation.h +12 -0
  35. package/ios/common/BackgroundGeolocation/MAURLocation.m +33 -4
  36. package/ios/common/BackgroundGeolocation/MAURLocationContract.h +4 -0
  37. package/ios/common/BackgroundGeolocation/MAURLocationContract.m +5 -1
  38. package/ios/common/BackgroundGeolocation/MAURLocationManager.m +19 -1
  39. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +9 -0
  40. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +59 -1
  41. package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.m +10 -1
  42. package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.m +54 -4
  43. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.h +12 -0
  44. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.m +125 -5
  45. package/package.json +31 -1
  46. package/plugin.xml +3 -10
  47. package/www/BackgroundGeolocation.d.ts +143 -3
  48. package/www/BackgroundGeolocation.js +11 -4
  49. package/CLAUDE.md +0 -56
  50. package/HISTORY.md +0 -871
  51. package/android/CDVBackgroundGeolocation/src/test/java/com/marianhello/ConfigMapperTest.java +0 -220
  52. package/android/common/src/androidTest/java/com/marianhello/bgloc/BackgroundGeolocationFacadeTest.java +0 -45
  53. package/android/common/src/androidTest/java/com/marianhello/bgloc/BatchManagerTest.java +0 -570
  54. package/android/common/src/androidTest/java/com/marianhello/bgloc/ConfigTest.java +0 -76
  55. package/android/common/src/androidTest/java/com/marianhello/bgloc/ContentProviderLocationDAOTest.java +0 -437
  56. package/android/common/src/androidTest/java/com/marianhello/bgloc/DBLogReaderTest.java +0 -95
  57. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationContentProviderTest.java +0 -159
  58. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationServiceProxyTest.java +0 -161
  59. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationServiceTest.java +0 -247
  60. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteConfigurationDAOTest.java +0 -200
  61. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteLocationDAOTest.java +0 -457
  62. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteLocationDAOThreadTest.java +0 -96
  63. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteOpenHelperTest.java +0 -225
  64. package/android/common/src/androidTest/java/com/marianhello/bgloc/TestPluginDelegate.java +0 -46
  65. package/android/common/src/androidTest/java/com/marianhello/bgloc/TestResourceResolver.java +0 -14
  66. package/android/common/src/androidTest/java/com/marianhello/bgloc/provider/MockLocationProvider.java +0 -50
  67. package/android/common/src/androidTest/java/com/marianhello/bgloc/provider/TestLocationProviderFactory.java +0 -17
  68. package/android/common/src/androidTest/java/com/marianhello/bgloc/sqlite/SQLiteOpenHelper10.java +0 -92
  69. package/android/common/src/androidTest/java/com/marianhello/bgloc/test/LocationProviderTestCase.java +0 -107
  70. package/android/common/src/androidTest/java/com/marianhello/bgloc/test/TestConstants.java +0 -5
  71. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/ArrayListLocationTemplateTest.java +0 -82
  72. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/BackgroundLocationTest.java +0 -128
  73. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/ConfigTest.java +0 -191
  74. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/DBLogReaderTest.java +0 -37
  75. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/HashMapLocationTemplateTest.java +0 -216
  76. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/HttpPostServiceTest.java +0 -223
  77. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/LocationTemplateFactoryTest.java +0 -50
  78. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/PostLocationTaskTest.java +0 -180
  79. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/TestHelper.java +0 -16
  80. package/ios/common/BackgroundGeolocation/SOMotionDetector/CHANGELOG.md +0 -2
  81. package/ios/common/BackgroundGeolocation/SOMotionDetector/LICENSE +0 -21
  82. package/ios/common/BackgroundGeolocation/SOMotionDetector/README.md +0 -135
  83. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOLocationManager.h +0 -80
  84. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOLocationManager.m +0 -147
  85. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionActivity.h +0 -30
  86. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionActivity.m +0 -42
  87. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionDetector.h +0 -99
  88. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionDetector.m +0 -327
  89. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOStepDetector.h +0 -44
  90. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOStepDetector.m +0 -94
  91. package/ios/common/BackgroundGeolocationTests/Info.plist +0 -24
  92. package/ios/common/BackgroundGeolocationTests/MAURBackgroundLocationTest.m +0 -185
  93. package/ios/common/BackgroundGeolocationTests/MAURConfigTest.m +0 -161
  94. package/ios/common/BackgroundGeolocationTests/MAURGeolocationOpenHelperTest.m +0 -102
  95. package/ios/common/BackgroundGeolocationTests/MAURLocationTest.m +0 -216
  96. package/ios/common/BackgroundGeolocationTests/MAURLocationUploaderTest.m +0 -55
  97. package/ios/common/BackgroundGeolocationTests/MAURLogReaderTest.m +0 -43
  98. package/ios/common/BackgroundGeolocationTests/MAURSQLiteConfigurationDAOTest.m +0 -102
  99. package/ios/common/BackgroundGeolocationTests/MAURSQLiteHelperTest.m +0 -41
  100. package/ios/common/BackgroundGeolocationTests/MAURSQLiteLocationDAOTests.m +0 -240
  101. package/ios/common/BackgroundGeolocationTests/MAURSQLiteLocationDAOThreadTest.m +0 -84
  102. package/ios/common/BackgroundGeolocationTests/MAURSQLiteOpenHelperTest.m +0 -144
  103. package/ios/common/scripts/xcode-refactor.js +0 -184
@@ -60,6 +60,12 @@ public class LocationTemplateFactory {
60
60
  attrs.put("altitude", "@altitude");
61
61
  attrs.put("bearing", "@bearing");
62
62
  attrs.put("radius", "@radius");
63
+ // v4.5.1 — README/CHANGELOG promete que el payload default incluye events/battery/isCharging.
64
+ // Como Config.getTemplate() siempre cae a este default si no hay postTemplate custom, hay
65
+ // que añadirlos aquí para que PostLocationTask + BatchManager los serialicen al backend.
66
+ attrs.put("events", "@events");
67
+ attrs.put("battery", "@battery");
68
+ attrs.put("isCharging", "@isCharging");
63
69
  return new HashMapLocationTemplate(attrs);
64
70
  }
65
71
  }
@@ -47,6 +47,9 @@ public final class SQLiteConfigurationContract {
47
47
  public static final String COLUMN_NAME_TEMPLATE = "template";
48
48
  public static final String COLUMN_NAME_SHOW_TIME = "show_time";
49
49
  public static final String COLUMN_NAME_SHOW_DISTANCE = "show_distance";
50
+ // v4.4.1 — single JSON blob holding the full config (replaces per-field columns
51
+ // for new keys). Old columns are kept for backward compat with v20 databases.
52
+ public static final String COLUMN_NAME_CONFIG_JSON = "config_json";
50
53
 
51
54
  public static final String SQL_CREATE_CONFIG_TABLE =
52
55
  "CREATE TABLE " + ConfigurationEntry.TABLE_NAME + " (" +
@@ -81,7 +84,8 @@ public final class SQLiteConfigurationContract {
81
84
  ConfigurationEntry.COLUMN_NAME_MAX_LOCATIONS + INTEGER_TYPE + COMMA_SEP +
82
85
  ConfigurationEntry.COLUMN_NAME_TEMPLATE + TEXT_TYPE + COMMA_SEP +
83
86
  ConfigurationEntry.COLUMN_NAME_SHOW_TIME + INTEGER_TYPE + COMMA_SEP +
84
- ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE + INTEGER_TYPE +
87
+ ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE + INTEGER_TYPE + COMMA_SEP +
88
+ ConfigurationEntry.COLUMN_NAME_CONFIG_JSON + TEXT_TYPE +
85
89
  " )";
86
90
 
87
91
  public static final String SQL_DROP_CONFIG_TABLE =
@@ -10,6 +10,7 @@ import org.json.JSONObject;
10
10
  import org.json.JSONException;
11
11
 
12
12
  import com.marianhello.bgloc.Config;
13
+ import com.marianhello.bgloc.data.ConfigJsonMapper;
13
14
  import com.marianhello.bgloc.data.ConfigurationDAO;
14
15
  import com.marianhello.bgloc.data.LocationTemplateFactory;
15
16
  import com.marianhello.bgloc.data.sqlite.SQLiteConfigurationContract.ConfigurationEntry;
@@ -63,7 +64,8 @@ public class SQLiteConfigurationDAO implements ConfigurationDAO {
63
64
  ConfigurationEntry.COLUMN_NAME_MAX_LOCATIONS,
64
65
  ConfigurationEntry.COLUMN_NAME_TEMPLATE,
65
66
  ConfigurationEntry.COLUMN_NAME_SHOW_TIME,
66
- ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE
67
+ ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE,
68
+ ConfigurationEntry.COLUMN_NAME_CONFIG_JSON
67
69
  };
68
70
 
69
71
  String whereClause = null;
@@ -105,6 +107,26 @@ public class SQLiteConfigurationDAO implements ConfigurationDAO {
105
107
  }
106
108
 
107
109
  private Config hydrate(Cursor c) throws JSONException {
110
+ // v4.4.1: prefer the full JSON blob if present (covers all keys, including post-3.2 ones).
111
+ // Note: `template` (postTemplate) lives in its dedicated column — restore it after the
112
+ // JSON deserialization so it's not lost when the config arrived via config_json only.
113
+ int idxJson = c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_CONFIG_JSON);
114
+ if (idxJson >= 0 && !c.isNull(idxJson)) {
115
+ String json = c.getString(idxJson);
116
+ if (json != null && !json.isEmpty()) {
117
+ try {
118
+ Config restored = ConfigJsonMapper.fromJSONObject(new JSONObject(json));
119
+ int idxTpl = c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_TEMPLATE);
120
+ if (idxTpl >= 0 && !c.isNull(idxTpl)) {
121
+ restored.setTemplate(LocationTemplateFactory.fromJSONString(c.getString(idxTpl)));
122
+ }
123
+ return restored;
124
+ } catch (JSONException ex) {
125
+ Log.w(TAG, "config_json parse failed; falling back to legacy columns: " + ex.getMessage());
126
+ }
127
+ }
128
+ }
129
+ // Legacy hydration (DBs upgraded from v20 or earlier where config_json is still NULL).
108
130
  Config config = Config.getDefault();
109
131
  config.setStationaryRadius(c.getFloat(c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_RADIUS)));
110
132
  config.setDistanceFilter(c.getInt(c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_DISTANCE_FILTER)));
@@ -190,6 +212,15 @@ public class SQLiteConfigurationDAO implements ConfigurationDAO {
190
212
  values.put(ConfigurationEntry.COLUMN_NAME_TEMPLATE, config.hasTemplate() ? config.getTemplate().toString() : null);
191
213
  values.put(ConfigurationEntry.COLUMN_NAME_SHOW_TIME, Boolean.TRUE.equals(config.getShowTime()) ? 1 : 0);
192
214
  values.put(ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE, Boolean.TRUE.equals(config.getShowDistance()) ? 1 : 0);
215
+ // v4.4.1: persist the full Config as JSON so post-3.2 fields (httpMethod, queryParams,
216
+ // drivingEvents, includeBattery, mockLocationPolicy, heartbeatInterval, ...) survive
217
+ // a reboot / startOnBoot. Legacy columns are kept populated above for backward compat.
218
+ try {
219
+ values.put(ConfigurationEntry.COLUMN_NAME_CONFIG_JSON,
220
+ ConfigJsonMapper.toJSONObject(config).toString());
221
+ } catch (JSONException e) {
222
+ Log.w(TAG, "config_json serialize failed: " + e.getMessage());
223
+ }
193
224
 
194
225
  return values;
195
226
  }
@@ -37,6 +37,10 @@ public final class SQLiteLocationContract {
37
37
  public static final String COLUMN_NAME_STATUS = "valid";
38
38
  public static final String COLUMN_NAME_BATCH_START_MILLIS = "batch_start";
39
39
  public static final String COLUMN_NAME_MOCK_FLAGS = "mock_flags";
40
+ // v4.5 — survive sync queue: events JSON, battery percentage and charging state.
41
+ public static final String COLUMN_NAME_EVENTS_JSON = "events_json";
42
+ public static final String COLUMN_NAME_BATTERY_LEVEL = "battery_level";
43
+ public static final String COLUMN_NAME_IS_CHARGING = "is_charging";
40
44
 
41
45
  public static final String SQL_CREATE_LOCATION_TABLE =
42
46
  "CREATE TABLE " + LocationEntry.TABLE_NAME + " (" +
@@ -60,7 +64,10 @@ public final class SQLiteLocationContract {
60
64
  LocationEntry.COLUMN_NAME_LOCATION_PROVIDER + INTEGER_TYPE + COMMA_SEP +
61
65
  LocationEntry.COLUMN_NAME_STATUS + INTEGER_TYPE + COMMA_SEP +
62
66
  LocationEntry.COLUMN_NAME_BATCH_START_MILLIS + INTEGER_TYPE + COMMA_SEP +
63
- LocationEntry.COLUMN_NAME_MOCK_FLAGS + INTEGER_TYPE +
67
+ LocationEntry.COLUMN_NAME_MOCK_FLAGS + INTEGER_TYPE + COMMA_SEP +
68
+ LocationEntry.COLUMN_NAME_EVENTS_JSON + TEXT_TYPE + COMMA_SEP +
69
+ LocationEntry.COLUMN_NAME_BATTERY_LEVEL + INTEGER_TYPE + COMMA_SEP +
70
+ LocationEntry.COLUMN_NAME_IS_CHARGING + INTEGER_TYPE +
64
71
  " )";
65
72
 
66
73
  public static final String SQL_DROP_LOCATION_TABLE =
@@ -106,7 +113,10 @@ public final class SQLiteLocationContract {
106
113
  COLUMN_NAME_LOCATION_PROVIDER,
107
114
  COLUMN_NAME_STATUS,
108
115
  COLUMN_NAME_BATCH_START_MILLIS,
109
- COLUMN_NAME_MOCK_FLAGS
116
+ COLUMN_NAME_MOCK_FLAGS,
117
+ COLUMN_NAME_EVENTS_JSON,
118
+ COLUMN_NAME_BATTERY_LEVEL,
119
+ COLUMN_NAME_IS_CHARGING
110
120
  };
111
121
  }
112
122
  }
@@ -300,7 +300,10 @@ public class SQLiteLocationDAO implements LocationDAO {
300
300
  .append(LocationEntry.COLUMN_NAME_LOCATION_PROVIDER).append("= ?,")
301
301
  .append(LocationEntry.COLUMN_NAME_BATCH_START_MILLIS).append("= ?,")
302
302
  .append(LocationEntry.COLUMN_NAME_STATUS).append("= ?,")
303
- .append(LocationEntry.COLUMN_NAME_MOCK_FLAGS).append("= ?")
303
+ .append(LocationEntry.COLUMN_NAME_MOCK_FLAGS).append("= ?,")
304
+ .append(LocationEntry.COLUMN_NAME_EVENTS_JSON).append("= ?,")
305
+ .append(LocationEntry.COLUMN_NAME_BATTERY_LEVEL).append("= ?,")
306
+ .append(LocationEntry.COLUMN_NAME_IS_CHARGING).append("= ?")
304
307
  .append(" WHERE ").append(LocationEntry._ID)
305
308
  .append("= ?")
306
309
  .toString();
@@ -325,6 +328,9 @@ public class SQLiteLocationDAO implements LocationDAO {
325
328
  location.getBatchStartMillis(),
326
329
  location.getStatus(),
327
330
  location.getMockFlags(),
331
+ location.hasDrivingEvents() ? location.getDrivingEvents().toString() : null,
332
+ location.getBatteryLevel(),
333
+ location.isCharging() != null ? (location.isCharging() ? 1 : 0) : null,
328
334
  locationId
329
335
  });
330
336
 
@@ -459,6 +465,18 @@ public class SQLiteLocationDAO implements LocationDAO {
459
465
  l.setStatus(c.getInt(c.getColumnIndex(LocationEntry.COLUMN_NAME_STATUS)));
460
466
  l.setLocationId(c.getLong(c.getColumnIndex(LocationEntry._ID)));
461
467
  l.setMockFlags(c.getInt((c.getColumnIndex(LocationEntry.COLUMN_NAME_MOCK_FLAGS))));
468
+ // v4.5: events / battery / charging
469
+ int idxEv = c.getColumnIndex(LocationEntry.COLUMN_NAME_EVENTS_JSON);
470
+ if (idxEv >= 0 && !c.isNull(idxEv)) {
471
+ String s = c.getString(idxEv);
472
+ if (s != null && !s.isEmpty()) {
473
+ try { l.setDrivingEvents(new org.json.JSONArray(s)); } catch (org.json.JSONException ignored) {}
474
+ }
475
+ }
476
+ int idxBat = c.getColumnIndex(LocationEntry.COLUMN_NAME_BATTERY_LEVEL);
477
+ if (idxBat >= 0 && !c.isNull(idxBat)) l.setBatteryLevel(c.getInt(idxBat));
478
+ int idxChg = c.getColumnIndex(LocationEntry.COLUMN_NAME_IS_CHARGING);
479
+ if (idxChg >= 0 && !c.isNull(idxChg)) l.setCharging(c.getInt(idxChg) == 1);
462
480
 
463
481
  return l;
464
482
  }
@@ -485,6 +503,16 @@ public class SQLiteLocationDAO implements LocationDAO {
485
503
  values.put(LocationEntry.COLUMN_NAME_STATUS, l.getStatus());
486
504
  values.put(LocationEntry.COLUMN_NAME_BATCH_START_MILLIS, l.getBatchStartMillis());
487
505
  values.put(LocationEntry.COLUMN_NAME_MOCK_FLAGS, l.getMockFlags());
506
+ // v4.5.1: always write — NULL when absent — to clear stale values on maxRows recycle.
507
+ if (l.hasDrivingEvents()) {
508
+ values.put(LocationEntry.COLUMN_NAME_EVENTS_JSON, l.getDrivingEvents().toString());
509
+ } else {
510
+ values.putNull(LocationEntry.COLUMN_NAME_EVENTS_JSON);
511
+ }
512
+ if (l.getBatteryLevel() != null) values.put(LocationEntry.COLUMN_NAME_BATTERY_LEVEL, l.getBatteryLevel());
513
+ else values.putNull(LocationEntry.COLUMN_NAME_BATTERY_LEVEL);
514
+ if (l.isCharging() != null) values.put(LocationEntry.COLUMN_NAME_IS_CHARGING, l.isCharging() ? 1 : 0);
515
+ else values.putNull(LocationEntry.COLUMN_NAME_IS_CHARGING);
488
516
 
489
517
  return values;
490
518
  }
@@ -511,7 +539,10 @@ public class SQLiteLocationDAO implements LocationDAO {
511
539
  LocationEntry.COLUMN_NAME_LOCATION_PROVIDER,
512
540
  LocationEntry.COLUMN_NAME_STATUS,
513
541
  LocationEntry.COLUMN_NAME_BATCH_START_MILLIS,
514
- LocationEntry.COLUMN_NAME_MOCK_FLAGS
542
+ LocationEntry.COLUMN_NAME_MOCK_FLAGS,
543
+ LocationEntry.COLUMN_NAME_EVENTS_JSON,
544
+ LocationEntry.COLUMN_NAME_BATTERY_LEVEL,
545
+ LocationEntry.COLUMN_NAME_IS_CHARGING
515
546
  };
516
547
 
517
548
  return columns;
@@ -22,7 +22,7 @@ import static com.marianhello.bgloc.data.sqlite.SQLiteLocationContract.LocationE
22
22
  public class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
23
23
  private static final String TAG = SQLiteOpenHelper.class.getName();
24
24
  public static final String SQLITE_DATABASE_NAME = "cordova_bg_geolocation.db";
25
- public static final int DATABASE_VERSION = 20;
25
+ public static final int DATABASE_VERSION = 22;
26
26
 
27
27
  public static final String TEXT_TYPE = " TEXT";
28
28
  public static final String INTEGER_TYPE = " INTEGER";
@@ -143,6 +143,20 @@ public class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
143
143
  case 19:
144
144
  alterSql.add(SessionEntry.SQL_CREATE_SESSION_TABLE);
145
145
  alterSql.add(SessionEntry.SQL_CREATE_SESSION_TABLE_TIME_IDX);
146
+ case 20:
147
+ // v4.4.1: store the full Config as a single JSON blob so future-added fields
148
+ // do not require a per-field schema bump.
149
+ alterSql.add("ALTER TABLE " + ConfigurationEntry.TABLE_NAME +
150
+ " ADD COLUMN " + ConfigurationEntry.COLUMN_NAME_CONFIG_JSON + TEXT_TYPE);
151
+ case 21:
152
+ // v4.5.0: persist driving events / battery / isCharging on each location so
153
+ // they survive the sync queue (POST failure → SQLite → background sync).
154
+ alterSql.add("ALTER TABLE " + LocationEntry.TABLE_NAME +
155
+ " ADD COLUMN " + LocationEntry.COLUMN_NAME_EVENTS_JSON + TEXT_TYPE);
156
+ alterSql.add("ALTER TABLE " + LocationEntry.TABLE_NAME +
157
+ " ADD COLUMN " + LocationEntry.COLUMN_NAME_BATTERY_LEVEL + INTEGER_TYPE);
158
+ alterSql.add("ALTER TABLE " + LocationEntry.TABLE_NAME +
159
+ " ADD COLUMN " + LocationEntry.COLUMN_NAME_IS_CHARGING + INTEGER_TYPE);
146
160
 
147
161
  break; // DO NOT FORGET TO MOVE DOWN BREAK ON DB UPGRADE!!!
148
162
  default:
@@ -88,11 +88,29 @@ public abstract class AbstractLocationProvider implements LocationProvider {
88
88
  mContext.unregisterReceiver(receiver);
89
89
  }
90
90
 
91
+ /**
92
+ * v4.5.2: drop fixes whose horizontal accuracy is worse than the configured
93
+ * maxAcceptedAccuracy threshold. Returns true when the location must be
94
+ * discarded.
95
+ */
96
+ private boolean exceedsMaxAcceptedAccuracy(Location location) {
97
+ if (location == null || mConfig == null) return false;
98
+ Float max = mConfig.getMaxAcceptedAccuracy();
99
+ if (max == null || max <= 0) return false;
100
+ if (!location.hasAccuracy()) return false;
101
+ if (location.getAccuracy() > max) {
102
+ logger.debug("Dropping fix: accuracy={} exceeds maxAcceptedAccuracy={}", location.getAccuracy(), max);
103
+ return true;
104
+ }
105
+ return false;
106
+ }
107
+
91
108
  /**
92
109
  * Handle location as recorder by provider
93
110
  * @param location
94
111
  */
95
112
  protected void handleLocation (Location location) {
113
+ if (exceedsMaxAcceptedAccuracy(location)) return;
96
114
  playDebugTone(Tone.BEEP);
97
115
  if (mDelegate != null) {
98
116
  BackgroundLocation bgLocation = new BackgroundLocation(PROVIDER_ID, location);
@@ -108,6 +126,7 @@ public abstract class AbstractLocationProvider implements LocationProvider {
108
126
  * @param radius radius of stationary region
109
127
  */
110
128
  protected void handleStationary (Location location, float radius) {
129
+ if (exceedsMaxAcceptedAccuracy(location)) return;
111
130
  playDebugTone(Tone.LONG_BEEP);
112
131
  if (mDelegate != null) {
113
132
  BackgroundLocation bgLocation = new BackgroundLocation(PROVIDER_ID, location);
@@ -123,6 +142,7 @@ public abstract class AbstractLocationProvider implements LocationProvider {
123
142
  * @param location
124
143
  */
125
144
  protected void handleStationary (Location location) {
145
+ if (exceedsMaxAcceptedAccuracy(location)) return;
126
146
  playDebugTone(Tone.LONG_BEEP);
127
147
  if (mDelegate != null) {
128
148
  BackgroundLocation bgLocation = new BackgroundLocation(PROVIDER_ID, location);
@@ -148,6 +168,26 @@ public abstract class AbstractLocationProvider implements LocationProvider {
148
168
  }
149
169
  }
150
170
 
171
+ /**
172
+ * v4.5.2: emit a permission-denied error to the delegate (used when a runtime
173
+ * permission such as ACTIVITY_RECOGNITION is missing on Android 10+).
174
+ */
175
+ protected void handlePermissionDenied(String message) {
176
+ if (mDelegate != null) {
177
+ mDelegate.onError(new PluginException(message, PluginException.PERMISSION_DENIED_ERROR));
178
+ }
179
+ }
180
+
181
+ /**
182
+ * v4.5.2: emit a service-level error to the delegate (used when Google Play
183
+ * Services is missing/outdated or the OS location service is disabled).
184
+ */
185
+ protected void handleServiceError(String message) {
186
+ if (mDelegate != null) {
187
+ mDelegate.onError(new PluginException(message, PluginException.SERVICE_ERROR));
188
+ }
189
+ }
190
+
151
191
  protected void showDebugToast (String text) {
152
192
  if (mConfig.isDebugging()) {
153
193
  Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
@@ -155,7 +195,14 @@ public abstract class AbstractLocationProvider implements LocationProvider {
155
195
  }
156
196
 
157
197
  public Boolean hasMockLocationsEnabled() {
158
- return Settings.Secure.getString(mContext.getContentResolver(), android.provider.Settings.Secure.ALLOW_MOCK_LOCATION).equals("1");
198
+ // v4.5.2: Settings.Secure.getString may return null (key absent on the
199
+ // device's settings provider). The previous code crashed with NPE because
200
+ // it called .equals("1") on the returned value. Invert the comparison so
201
+ // null safely yields false.
202
+ String value = Settings.Secure.getString(
203
+ mContext.getContentResolver(),
204
+ android.provider.Settings.Secure.ALLOW_MOCK_LOCATION);
205
+ return "1".equals(value);
159
206
  }
160
207
 
161
208
  /**
@@ -12,6 +12,8 @@ import android.os.Build;
12
12
  import android.os.Looper;
13
13
  import androidx.core.app.ActivityCompat;
14
14
 
15
+ import com.google.android.gms.common.ConnectionResult;
16
+ import com.google.android.gms.common.GoogleApiAvailability;
15
17
  import com.google.android.gms.location.ActivityRecognition;
16
18
  import com.google.android.gms.location.ActivityRecognitionClient;
17
19
  import com.google.android.gms.location.ActivityRecognitionResult;
@@ -41,8 +43,19 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
41
43
  private boolean isStarted = false;
42
44
  private boolean isTracking = false;
43
45
  private boolean isWatchingActivity = false;
46
+ private boolean playServicesAvailable = false;
47
+ private boolean activityPermissionErrorEmitted = false;
48
+ private boolean stopOnStillWarningEmitted = false;
44
49
  private DetectedActivity lastActivity = new DetectedActivity(DetectedActivity.UNKNOWN, 100);
45
50
 
51
+ // v4.5.2: snapshot of fields that require restarting tracking when they change.
52
+ private Integer prevDesiredAccuracy;
53
+ private Integer prevInterval;
54
+ private Integer prevFastestInterval;
55
+ private Integer prevDistanceFilter;
56
+ private Integer prevActivitiesInterval;
57
+ private Boolean prevStopOnStillActivity;
58
+
46
59
  private final LocationCallback locationCallback = new LocationCallback() {
47
60
  @Override
48
61
  public void onLocationResult(LocationResult result) {
@@ -63,6 +76,20 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
63
76
  @Override
64
77
  public void onCreate() {
65
78
  super.onCreate();
79
+
80
+ // v4.5.2: ACTIVITY_PROVIDER strictly depends on Google Play Services
81
+ // (FusedLocationProviderClient + ActivityRecognitionClient). If GPS is
82
+ // missing/outdated we cannot operate — surface a clear error instead of
83
+ // silently failing.
84
+ int gps = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mContext);
85
+ playServicesAvailable = (gps == ConnectionResult.SUCCESS);
86
+ if (!playServicesAvailable) {
87
+ String msg = "Google Play Services unavailable for ACTIVITY_PROVIDER (code=" + gps + "); provider will be inert.";
88
+ logger.error(msg);
89
+ handleServiceError(msg);
90
+ return;
91
+ }
92
+
66
93
  fusedLocationClient = LocationServices.getFusedLocationProviderClient(mContext);
67
94
  activityRecognitionClient = ActivityRecognition.getClient(mContext);
68
95
 
@@ -80,6 +107,15 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
80
107
  public void onStart() {
81
108
  logger.info("Start recording");
82
109
  this.isStarted = true;
110
+
111
+ // v4.5.2: ACTIVITY_PROVIDER hinges on the STILL/ACTIVE state machine.
112
+ // If the host turned that off, the provider degenerates into a tracker
113
+ // that never pauses — warn so it shows up in logcat for the integrator.
114
+ if (mConfig != null && Boolean.FALSE.equals(mConfig.getStopOnStillActivity()) && !stopOnStillWarningEmitted) {
115
+ logger.warn("ACTIVITY_PROVIDER with stopOnStillActivity=false will track continuously; consider DISTANCE_FILTER_PROVIDER or RAW_PROVIDER for that use case.");
116
+ stopOnStillWarningEmitted = true;
117
+ }
118
+
83
119
  attachRecorder();
84
120
  }
85
121
 
@@ -93,11 +129,40 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
93
129
 
94
130
  @Override
95
131
  public void onConfigure(Config config) {
96
- super.onConfigure(config);
132
+ // v4.5.2: only restart tracking if a field that actually affects the
133
+ // LocationRequest / activity-updates subscription has changed. A no-op
134
+ // reconfigure used to drop+re-add the location callback and momentarily
135
+ // leave the service without updates.
136
+ boolean restart = false;
97
137
  if (isStarted) {
138
+ restart = !sameValue(prevDesiredAccuracy, config != null ? config.getDesiredAccuracy() : null)
139
+ || !sameValue(prevInterval, config != null ? config.getInterval() : null)
140
+ || !sameValue(prevFastestInterval, config != null ? config.getFastestInterval() : null)
141
+ || !sameValue(prevDistanceFilter, config != null ? config.getDistanceFilter() : null)
142
+ || !sameValue(prevActivitiesInterval, config != null ? config.getActivitiesInterval() : null)
143
+ || !sameValue(prevStopOnStillActivity, config != null ? config.getStopOnStillActivity() : null);
144
+ }
145
+
146
+ super.onConfigure(config);
147
+
148
+ if (restart) {
149
+ logger.debug("Restarting ACTIVITY_PROVIDER tracking after relevant config change.");
98
150
  onStop();
99
151
  onStart();
100
152
  }
153
+
154
+ if (config != null) {
155
+ prevDesiredAccuracy = config.getDesiredAccuracy();
156
+ prevInterval = config.getInterval();
157
+ prevFastestInterval = config.getFastestInterval();
158
+ prevDistanceFilter = config.getDistanceFilter();
159
+ prevActivitiesInterval = config.getActivitiesInterval();
160
+ prevStopOnStillActivity = config.getStopOnStillActivity();
161
+ }
162
+ }
163
+
164
+ private static boolean sameValue(Object a, Object b) {
165
+ return a == null ? b == null : a.equals(b);
101
166
  }
102
167
 
103
168
  @Override
@@ -139,10 +204,16 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
139
204
  int priority = translateDesiredAccuracy(mConfig.getDesiredAccuracy());
140
205
  // v3.4: LocationRequest.Builder (play-services-location 21.0.0+) replaces deprecated
141
206
  // LocationRequest.create() + setPriority/setInterval/setFastestInterval.
142
- LocationRequest locationRequest = new LocationRequest.Builder(priority, mConfig.getInterval())
207
+ // v4.5.2: also honor distanceFilter (was ignored on ACTIVITY_PROVIDER), so the
208
+ // FusedLocationProvider can throttle by distance and not just by interval.
209
+ LocationRequest.Builder builder = new LocationRequest.Builder(priority, mConfig.getInterval())
143
210
  .setMinUpdateIntervalMillis(mConfig.getFastestInterval())
144
- .setWaitForAccurateLocation(false)
145
- .build();
211
+ .setWaitForAccurateLocation(false);
212
+ Integer distanceFilter = mConfig.getDistanceFilter();
213
+ if (distanceFilter != null && distanceFilter > 0) {
214
+ builder.setMinUpdateDistanceMeters(distanceFilter.floatValue());
215
+ }
216
+ LocationRequest locationRequest = builder.build();
146
217
 
147
218
  try {
148
219
  fusedLocationClient.requestLocationUpdates(
@@ -185,7 +256,20 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
185
256
 
186
257
  startTracking();
187
258
 
188
- if (!isWatchingActivity && mConfig.getStopOnStillActivity() && activityRecognitionPermitted()) {
259
+ if (!isWatchingActivity && mConfig.getStopOnStillActivity()) {
260
+ // v4.5.2: on Android 10+ ACTIVITY_RECOGNITION is a runtime permission.
261
+ // Without it, requestActivityUpdates() silently returns no broadcasts and
262
+ // STILL/ACTIVE never flips — the provider then runs as a continuous tracker
263
+ // by accident. Emit a one-shot error so the host app knows to request it.
264
+ if (!activityRecognitionPermitted()) {
265
+ if (!activityPermissionErrorEmitted) {
266
+ String msg = "ACTIVITY_RECOGNITION permission denied; ACTIVITY_PROVIDER cannot detect STILL/ACTIVE transitions.";
267
+ logger.warn(msg);
268
+ handlePermissionDenied(msg);
269
+ activityPermissionErrorEmitted = true;
270
+ }
271
+ return;
272
+ }
189
273
  activityRecognitionClient.requestActivityUpdates(
190
274
  mConfig.getActivitiesInterval(),
191
275
  detectedActivitiesPI
@@ -255,7 +339,22 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
255
339
  List<DetectedActivity> detectedActivities = result.getProbableActivities();
256
340
  if (detectedActivities == null || detectedActivities.isEmpty()) return;
257
341
 
258
- lastActivity = getProbableActivity(detectedActivities);
342
+ DetectedActivity candidate = getProbableActivity(detectedActivities);
343
+
344
+ // v4.5.2: skip transitions whose confidence is below the configured
345
+ // threshold (default 50). Prevents jittery STILL/ACTIVE flips when the
346
+ // motion classifier is unsure — which translated into spurious GPS
347
+ // start/stop bursts in earlier versions.
348
+ Integer threshold = mConfig != null ? mConfig.getActivityConfidenceThreshold() : null;
349
+ if (threshold != null && candidate.getConfidence() < threshold) {
350
+ logger.debug("Ignoring low-confidence activity={} confidence={} threshold={}",
351
+ BackgroundActivity.getActivityString(candidate.getType()),
352
+ candidate.getConfidence(),
353
+ threshold);
354
+ return;
355
+ }
356
+
357
+ lastActivity = candidate;
259
358
 
260
359
  logger.debug("Detected activity={} confidence={}", BackgroundActivity.getActivityString(lastActivity.getType()), lastActivity.getConfidence());
261
360