@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
@@ -104,6 +104,8 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
104
104
  NSTimeInterval drLastHardBrakeAt, drLastRapidAccelAt, drLastSharpTurnAt, drLastCrashAt;
105
105
  // v4.2 sensor fusion
106
106
  MAURSensorFusionDetector *sensorFusion;
107
+ // v4.3 — events buffered when no simultaneous fix is available; drained onto next location.
108
+ NSMutableArray *pendingDrivingEvents;
107
109
  }
108
110
 
109
111
 
@@ -132,12 +134,27 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
132
134
 
133
135
  postLocationTask = [[MAURPostLocationTask alloc] init];
134
136
  postLocationTask.delegate = self;
135
-
137
+
136
138
  localNotification = [[UILocalNotification alloc] init];
137
139
  localNotification.timeZone = [NSTimeZone defaultTimeZone];
138
-
140
+
139
141
  isStarted = NO;
140
-
142
+ pendingDrivingEvents = [[NSMutableArray alloc] init];
143
+
144
+ // v4.5.1 — wire pending events + battery snapshot into the post task so they run AFTER
145
+ // any locationTransform that may produce a new instance / return nil. The previous flow
146
+ // (flush BEFORE add:) lost buffered events whenever the transform returned nil.
147
+ postLocationTask.pendingDrivingEventsBuffer = pendingDrivingEvents;
148
+ __weak typeof(self) weakSelf = self;
149
+ postLocationTask.attachBatterySnapshot = ^(MAURLocation * _Nonnull loc) {
150
+ __strong typeof(self) strongSelf = weakSelf;
151
+ if (strongSelf == nil) return;
152
+ BOOL includeBat = (strongSelf->_config == nil
153
+ || strongSelf->_config.includeBattery == nil
154
+ || [strongSelf->_config.includeBattery boolValue]);
155
+ if (includeBat) [strongSelf attachBatterySnapshotTo:loc];
156
+ };
157
+
141
158
  return self;
142
159
  }
143
160
 
@@ -401,6 +418,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
401
418
  // MAURSensorFusionListener
402
419
  - (void) onSensorCrashWithImpactG:(double)impactG location:(MAURLocation *)location
403
420
  {
421
+ [self bufferPendingEvent:@"possibleCrash" extra:@{@"value": @(impactG), @"source": @"sensor"}];
404
422
  NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
405
423
  if (location != nil) userInfo[@"location"] = location;
406
424
  userInfo[@"value"] = @(impactG);
@@ -411,6 +429,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
411
429
  }
412
430
  - (void) onPhoneUsageWhileDriving:(MAURLocation *)location
413
431
  {
432
+ [self bufferPendingEvent:@"phoneUsageWhileDriving" extra:nil];
414
433
  NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
415
434
  if (location != nil) userInfo[@"location"] = location;
416
435
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURPhoneUsageWhileDrivingNotification
@@ -418,6 +437,77 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
418
437
  userInfo:userInfo];
419
438
  }
420
439
 
440
+ // v4.3 — driving event helpers
441
+ - (void) attachDrivingEvent:(NSString *)type to:(MAURLocation *)loc extra:(NSDictionary *)extra
442
+ {
443
+ if (loc == nil || type == nil) return;
444
+ NSMutableDictionary *ev = [NSMutableDictionary dictionary];
445
+ ev[@"type"] = type;
446
+ ev[@"time"] = @((long long)([[NSDate date] timeIntervalSince1970] * 1000.0));
447
+ if (extra != nil) [ev addEntriesFromDictionary:extra];
448
+ if (loc.drivingEvents == nil) loc.drivingEvents = [NSMutableArray array];
449
+ [loc.drivingEvents addObject:ev];
450
+ }
451
+
452
+ // v4.4.1 — pending driving events: cap + TTL.
453
+ static NSInteger const kPendingDrivingEventsMax = 20;
454
+ static NSTimeInterval const kPendingDrivingEventsTTLMs = 60000.0;
455
+
456
+ - (void) bufferPendingEvent:(NSString *)type extra:(NSDictionary *)extra
457
+ {
458
+ if (type == nil) return;
459
+ NSMutableDictionary *ev = [NSMutableDictionary dictionary];
460
+ ev[@"type"] = type;
461
+ ev[@"time"] = @((long long)([[NSDate date] timeIntervalSince1970] * 1000.0));
462
+ if (extra != nil) [ev addEntriesFromDictionary:extra];
463
+ @synchronized (pendingDrivingEvents) {
464
+ while ((NSInteger)[pendingDrivingEvents count] >= kPendingDrivingEventsMax) {
465
+ [pendingDrivingEvents removeObjectAtIndex:0];
466
+ }
467
+ [pendingDrivingEvents addObject:ev];
468
+ }
469
+ }
470
+
471
+ - (void) flushPendingDrivingEventsTo:(MAURLocation *)loc
472
+ {
473
+ if (loc == nil) return;
474
+ NSTimeInterval nowMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
475
+ @synchronized (pendingDrivingEvents) {
476
+ if ([pendingDrivingEvents count] == 0) return;
477
+ if (loc.drivingEvents == nil) loc.drivingEvents = [NSMutableArray array];
478
+ for (NSDictionary *ev in pendingDrivingEvents) {
479
+ NSNumber *t = ev[@"time"];
480
+ NSTimeInterval evMs = t != nil ? [t doubleValue] : nowMs;
481
+ if (nowMs - evMs <= kPendingDrivingEventsTTLMs) {
482
+ [loc.drivingEvents addObject:ev];
483
+ }
484
+ }
485
+ [pendingDrivingEvents removeAllObjects];
486
+ }
487
+ }
488
+
489
+ // v4.4 — read device battery via UIDevice. Calling main thread; safe from any thread.
490
+ - (void) attachBatterySnapshotTo:(MAURLocation *)loc
491
+ {
492
+ if (loc == nil) return;
493
+ void (^read)(void) = ^{
494
+ UIDevice *device = [UIDevice currentDevice];
495
+ if (!device.batteryMonitoringEnabled) device.batteryMonitoringEnabled = YES;
496
+ float lvl = device.batteryLevel; // 0.0 - 1.0, or -1 if unknown
497
+ if (lvl >= 0) {
498
+ loc.batteryLevel = @((int) round(lvl * 100.0));
499
+ }
500
+ UIDeviceBatteryState state = device.batteryState;
501
+ BOOL charging = (state == UIDeviceBatteryStateCharging || state == UIDeviceBatteryStateFull);
502
+ loc.isCharging = @(charging);
503
+ };
504
+ if ([NSThread isMainThread]) {
505
+ read();
506
+ } else {
507
+ dispatch_sync(dispatch_get_main_queue(), read);
508
+ }
509
+ }
510
+
421
511
  - (void) drivingDetectorFeed:(MAURLocation *)loc
422
512
  {
423
513
  if (loc == nil || _config == nil) return;
@@ -446,6 +536,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
446
536
  NSString *provider = loc.provider;
447
537
  if (provider != nil && ![provider isEqualToString:drLastProvider]) {
448
538
  drLastProvider = provider;
539
+ [self attachDrivingEvent:@"providerChange" to:loc extra:@{@"provider": provider}];
449
540
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURProviderChangeNotification
450
541
  object:self
451
542
  userInfo:@{@"provider": provider}];
@@ -465,6 +556,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
465
556
  drBelowMovingSince = 0;
466
557
  if (!drIsMoving) {
467
558
  drIsMoving = YES;
559
+ [self attachDrivingEvent:@"moving" to:loc extra:nil];
468
560
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURMovingNotification
469
561
  object:self
470
562
  userInfo:@{@"location": loc}];
@@ -476,6 +568,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
476
568
  drTripActive = YES;
477
569
  drTripStartedAt = now;
478
570
  drTripDistanceMeters = 0;
571
+ [self attachDrivingEvent:@"tripStart" to:loc extra:nil];
479
572
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURTripStartNotification
480
573
  object:self
481
574
  userInfo:@{@"location": loc}];
@@ -490,6 +583,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
490
583
  if (drBelowMovingSince == 0) drBelowMovingSince = now;
491
584
  if (drIsMoving && (now - drBelowMovingSince) >= stoppedDuration) {
492
585
  drIsMoving = NO;
586
+ [self attachDrivingEvent:@"stopped" to:loc extra:nil];
493
587
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURStoppedNotification
494
588
  object:self
495
589
  userInfo:@{@"location": loc}];
@@ -497,6 +591,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
497
591
  NSTimeInterval durMs = (now - drTripStartedAt) * 1000.0;
498
592
  double dist = drTripDistanceMeters;
499
593
  drTripActive = NO;
594
+ [self attachDrivingEvent:@"tripEnd" to:loc extra:@{@"distance": @(dist), @"durationMs": @((long long)durMs)}];
500
595
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURTripEndNotification
501
596
  object:self
502
597
  userInfo:@{
@@ -514,6 +609,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
514
609
  if (kmh > speedLimit) {
515
610
  if (!drWasSpeeding) {
516
611
  drWasSpeeding = YES;
612
+ [self attachDrivingEvent:@"speeding" to:loc extra:@{@"speedKmh": @(kmh), @"limitKmh": @(speedLimit)}];
517
613
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURSpeedingNotification
518
614
  object:self
519
615
  userInfo:@{
@@ -546,12 +642,14 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
546
642
  double accel = dv / dt;
547
643
  if (hardBrakeMps2 > 0 && accel <= -hardBrakeMps2 && (now - drLastHardBrakeAt) >= kCooldown) {
548
644
  drLastHardBrakeAt = now;
645
+ [self attachDrivingEvent:@"hardBrake" to:loc extra:@{@"value": @(accel)}];
549
646
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURHardBrakeNotification
550
647
  object:self
551
648
  userInfo:@{@"location": loc, @"value": @(accel)}];
552
649
  }
553
650
  if (rapidAccelMps2 > 0 && accel >= rapidAccelMps2 && (now - drLastRapidAccelAt) >= kCooldown) {
554
651
  drLastRapidAccelAt = now;
652
+ [self attachDrivingEvent:@"rapidAcceleration" to:loc extra:@{@"value": @(accel)}];
555
653
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURRapidAccelerationNotification
556
654
  object:self
557
655
  userInfo:@{@"location": loc, @"value": @(accel)}];
@@ -563,6 +661,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
563
661
  && drPrevSpeed * 3.6 >= crashImpactKmh
564
662
  && (now - drLastCrashAt) >= kCooldown) {
565
663
  drLastCrashAt = now;
664
+ [self attachDrivingEvent:@"possibleCrash" to:loc extra:@{@"value": @(dropKmh), @"source": @"gps"}];
566
665
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURPossibleCrashNotification
567
666
  object:self
568
667
  userInfo:@{@"location": loc, @"value": @(dropKmh), @"source": @"gps"}];
@@ -581,6 +680,7 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
581
680
  double rate = diff / dt;
582
681
  if (rate >= sharpTurnDegPerSec && (now - drLastSharpTurnAt) >= kCooldown) {
583
682
  drLastSharpTurnAt = now;
683
+ [self attachDrivingEvent:@"sharpTurn" to:loc extra:@{@"value": @(rate)}];
584
684
  [[NSNotificationCenter defaultCenter] postNotificationName:MAURSharpTurnNotification
585
685
  object:self
586
686
  userInfo:@{@"location": loc, @"value": @(rate)}];
@@ -916,8 +1016,22 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
916
1016
  - (void) onStationaryChanged:(MAURLocation *)location
917
1017
  {
918
1018
  DDLogDebug(@"%@ #onStationaryChanged", TAG);
1019
+
1020
+ // v4.5.2: drop stationary fixes whose accuracy is worse than the configured
1021
+ // maxAcceptedAccuracy threshold. Mirrors the Android filter in
1022
+ // AbstractLocationProvider.handleStationary.
1023
+ NSNumber *maxAcc = [self getConfig].maxAcceptedAccuracy;
1024
+ if (maxAcc != nil && maxAcc.doubleValue > 0 && location.accuracy != nil
1025
+ && [location.accuracy doubleValue] > maxAcc.doubleValue) {
1026
+ DDLogDebug(@"%@ dropping stationary fix accuracy=%@ exceeds maxAcceptedAccuracy=%@", TAG, location.accuracy, maxAcc);
1027
+ return;
1028
+ }
1029
+
919
1030
  stationaryLocation = location;
920
-
1031
+
1032
+ // v4.5.1 — enrichment moved into MAURPostLocationTask.add: so pending events / battery
1033
+ // land on the post-transform instance (and survive transforms that return new instances).
1034
+
921
1035
  [postLocationTask add:location];
922
1036
 
923
1037
  MAURConfig *config = [self getConfig];
@@ -943,13 +1057,26 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
943
1057
  - (void) onLocationChanged:(MAURLocation *)location
944
1058
  {
945
1059
  DDLogDebug(@"%@ #onLocationChanged %@", TAG, location);
1060
+
1061
+ // v4.5.2: drop fixes whose accuracy is worse than maxAcceptedAccuracy.
1062
+ // Mirrors AbstractLocationProvider.handleLocation on Android.
1063
+ NSNumber *maxAcc = [self getConfig].maxAcceptedAccuracy;
1064
+ if (maxAcc != nil && maxAcc.doubleValue > 0 && location.accuracy != nil
1065
+ && [location.accuracy doubleValue] > maxAcc.doubleValue) {
1066
+ DDLogDebug(@"%@ dropping fix accuracy=%@ exceeds maxAcceptedAccuracy=%@", TAG, location.accuracy, maxAcc);
1067
+ return;
1068
+ }
1069
+
946
1070
  stationaryLocation = nil;
947
1071
  lastReceivedLocation = location; // v3.5 Phase 4: cached for heartbeat payload
948
1072
 
949
- // v4.0 Phase 6: feed driver-insights state machine.
1073
+ // v4.0 Phase 6: feed driver-insights state machine. Listener may attach events to `location`.
950
1074
  [self drivingDetectorFeed:location];
951
1075
  // v4.2 Phase 8: keep sensor pipeline aware of the latest fix.
952
1076
  sensorFusion.lastLocation = location;
1077
+ // v4.5.1 — pending events drain + battery snapshot moved into MAURPostLocationTask.add:
1078
+ // so they run AFTER any user-supplied locationTransform. The previous order lost them
1079
+ // whenever the transform returned nil.
953
1080
 
954
1081
  [postLocationTask add:location];
955
1082
 
@@ -117,6 +117,10 @@ NSString * const MAURBackgroundSyncDidProgressNotification = @"MAURBackgroundSyn
117
117
 
118
118
  // Stash count so didCompleteWithError can report it as syncSuccess payload.
119
119
  objc_setAssociatedObject(task, "locationsSent", @([jsonArray count]), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
120
+ // v4.5.1: capture cutoff timestamp NOW so on success we delete only the rows that existed
121
+ // before the upload started. Locations persisted DURING the upload are preserved.
122
+ objc_setAssociatedObject(task, "uploadCutoff",
123
+ @([[NSDate date] timeIntervalSince1970]), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
120
124
 
121
125
  [task resume];
122
126
 
@@ -216,6 +220,10 @@ totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
216
220
 
217
221
  dispatch_async(dispatch_get_main_queue(), ^{
218
222
  if (error != nil) {
223
+ // v4.5.1 — restore SyncPending → PostPending so the failed locations get retried
224
+ // on the next sync window. Without this, a single network drop loses everything.
225
+ NSError *restErr = nil;
226
+ [[MAURSQLiteLocationDAO sharedInstance] restoreFailedSyncLocations:&restErr];
219
227
  NSString *msg = error.localizedDescription ?: @"";
220
228
  if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncFailed:httpStatus:message:)]) {
221
229
  [_delegate backgroundSyncFailed:self httpStatus:0 message:msg];
@@ -225,6 +233,9 @@ totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
225
233
  object:self
226
234
  userInfo:@{@"httpStatus": @0, @"message": msg}];
227
235
  } else if (!isStatusOkay) {
236
+ // v4.5.1 — server-side failure (5xx, 4xx other than 285/401): also restore the rows.
237
+ NSError *restErr = nil;
238
+ [[MAURSQLiteLocationDAO sharedInstance] restoreFailedSyncLocations:&restErr];
228
239
  NSString *msg = [NSString stringWithFormat:@"HTTP %ld", (long)statusCode];
229
240
  if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncFailed:httpStatus:message:)]) {
230
241
  [_delegate backgroundSyncFailed:self httpStatus:statusCode message:msg];
@@ -234,6 +245,15 @@ totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
234
245
  object:self
235
246
  userInfo:@{@"httpStatus": @(statusCode), @"message": msg}];
236
247
  } else {
248
+ // v4.5.1: drop SYNC_PENDING locations whose recorded_at is <= the captured upload-start
249
+ // cutoff. This preserves any new locations persisted DURING the upload (race window).
250
+ NSNumber *cutoffNum = objc_getAssociatedObject(task, "uploadCutoff");
251
+ NSTimeInterval cutoff = cutoffNum != nil ? [cutoffNum doubleValue] : [[NSDate date] timeIntervalSince1970];
252
+ NSError *delErr = nil;
253
+ BOOL deleted = [[MAURSQLiteLocationDAO sharedInstance] deleteSyncedLocationsBefore:cutoff error:&delErr];
254
+ if (!deleted) {
255
+ NSLog(@"deleteSyncedLocationsBefore after success failed: %@", delErr.localizedDescription ?: @"unknown");
256
+ }
237
257
  if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncSucceeded:locationsSent:)]) {
238
258
  [_delegate backgroundSyncSucceeded:self locationsSent:locationsSent];
239
259
  }
@@ -44,6 +44,13 @@ enum {
44
44
  @property NSString *mockLocationPolicy; // allow | flag | drop (default allow)
45
45
  // v4.0 Phase 6: driver insights — passed through as a dictionary; the facade reads keys at runtime.
46
46
  @property NSDictionary *drivingEvents;
47
+ // v4.4: stamp battery percentage + charging state onto every location (default ON).
48
+ @property NSNumber *includeBattery;
49
+ // v4.5.2: provider hardening
50
+ /** 0-100. Activity-recognition transitions below this confidence are ignored. Default 50. */
51
+ @property NSNumber *activityConfidenceThreshold;
52
+ /** Discard fixes whose accuracy (m) is worse than this. nil = no filter. */
53
+ @property NSNumber *maxAcceptedAccuracy;
47
54
  @property NSNumber *_saveBatteryOnBackground;
48
55
  @property NSNumber *maxLocations;
49
56
  @property NSNumber *_pauseLocationUpdates;
@@ -12,7 +12,7 @@
12
12
 
13
13
  @implementation MAURConfig
14
14
 
15
- @synthesize stationaryRadius, distanceFilter, desiredAccuracy, _debug, activityType, activitiesInterval, _stopOnTerminate, url, syncUrl, syncThreshold, syncEnabled, httpHeaders, httpMethod, syncHttpMethod, httpMode, syncMode, queryParams, _showsBackgroundLocationIndicator, heartbeatInterval, mockLocationPolicy, drivingEvents, _saveBatteryOnBackground, maxLocations, _pauseLocationUpdates, locationProvider, _template;
15
+ @synthesize stationaryRadius, distanceFilter, desiredAccuracy, _debug, activityType, activitiesInterval, _stopOnTerminate, url, syncUrl, syncThreshold, syncEnabled, httpHeaders, httpMethod, syncHttpMethod, httpMode, syncMode, queryParams, _showsBackgroundLocationIndicator, heartbeatInterval, mockLocationPolicy, drivingEvents, includeBattery, activityConfidenceThreshold, maxAcceptedAccuracy, _saveBatteryOnBackground, maxLocations, _pauseLocationUpdates, locationProvider, _template;
16
16
 
17
17
  -(instancetype) initWithDefaults {
18
18
  self = [super init];
@@ -40,6 +40,10 @@
40
40
  syncMode = @"batch";
41
41
  heartbeatInterval = [NSNumber numberWithInt:0];
42
42
  mockLocationPolicy = @"allow";
43
+ // v4.5.2 — match Android defaults so the JS layer sees the same behavior
44
+ // regardless of platform when the host doesn't override these.
45
+ activityConfidenceThreshold = [NSNumber numberWithInt:50];
46
+ maxAcceptedAccuracy = nil; // off by default
43
47
  // template =
44
48
 
45
49
  return self;
@@ -117,6 +121,16 @@
117
121
  if ([config[@"drivingEvents"] isKindOfClass:[NSDictionary class]]) {
118
122
  instance.drivingEvents = config[@"drivingEvents"];
119
123
  }
124
+ if (isNotNull(config[@"includeBattery"])) {
125
+ instance.includeBattery = config[@"includeBattery"];
126
+ }
127
+ // v4.5.2 provider hardening
128
+ if (isNotNull(config[@"activityConfidenceThreshold"])) {
129
+ instance.activityConfidenceThreshold = config[@"activityConfidenceThreshold"];
130
+ }
131
+ if (isNotNull(config[@"maxAcceptedAccuracy"])) {
132
+ instance.maxAcceptedAccuracy = config[@"maxAcceptedAccuracy"];
133
+ }
120
134
  if (isNotNull(config[@"saveBatteryOnBackground"])) {
121
135
  instance._saveBatteryOnBackground = config[@"saveBatteryOnBackground"];
122
136
  }
@@ -215,6 +229,15 @@
215
229
  if (newConfig.drivingEvents != nil) {
216
230
  merger.drivingEvents = newConfig.drivingEvents;
217
231
  }
232
+ if (newConfig.includeBattery != nil) {
233
+ merger.includeBattery = newConfig.includeBattery;
234
+ }
235
+ if (newConfig.activityConfidenceThreshold != nil) {
236
+ merger.activityConfidenceThreshold = newConfig.activityConfidenceThreshold;
237
+ }
238
+ if (newConfig.maxAcceptedAccuracy != nil) {
239
+ merger.maxAcceptedAccuracy = newConfig.maxAcceptedAccuracy;
240
+ }
218
241
  if ([newConfig hasSaveBatteryOnBackground]) {
219
242
  merger._saveBatteryOnBackground = newConfig._saveBatteryOnBackground;
220
243
  }
@@ -259,6 +282,9 @@
259
282
  copy.heartbeatInterval = heartbeatInterval;
260
283
  copy.mockLocationPolicy = mockLocationPolicy;
261
284
  copy.drivingEvents = drivingEvents;
285
+ copy.includeBattery = includeBattery;
286
+ copy.activityConfidenceThreshold = activityConfidenceThreshold;
287
+ copy.maxAcceptedAccuracy = maxAcceptedAccuracy;
262
288
  copy._saveBatteryOnBackground = _saveBatteryOnBackground;
263
289
  copy.maxLocations = maxLocations;
264
290
  copy._pauseLocationUpdates = _pauseLocationUpdates;
@@ -512,9 +538,15 @@
512
538
  @"altitude": @"@altitude",
513
539
  @"latitude": @"@latitude",
514
540
  @"longitude": @"@longitude",
515
- @"provider": @"provider",
541
+ @"provider": @"@provider", // v4.5.1 — was literal "provider" (bug)
516
542
  @"locationProvider": @"@locationProvider",
517
543
  @"radius": @"@radius",
544
+ // v4.5.1 — README promete events/battery/isCharging en payload default. Sin esto,
545
+ // el template default que se usa siempre que la app no configura postTemplate omitía
546
+ // estos campos al serializar via toResultFromTemplate.
547
+ @"events": @"@events",
548
+ @"battery": @"@battery",
549
+ @"isCharging": @"@isCharging",
518
550
  };
519
551
  }
520
552
 
@@ -576,6 +608,9 @@
576
608
  if (self.heartbeatInterval != nil) [dict setObject:self.heartbeatInterval forKey:@"heartbeatInterval"];
577
609
  if (self.mockLocationPolicy != nil) [dict setObject:self.mockLocationPolicy forKey:@"mockLocationPolicy"];
578
610
  if (self.drivingEvents != nil) [dict setObject:self.drivingEvents forKey:@"drivingEvents"];
611
+ if (self.includeBattery != nil) [dict setObject:self.includeBattery forKey:@"includeBattery"];
612
+ if (self.activityConfidenceThreshold != nil) [dict setObject:self.activityConfidenceThreshold forKey:@"activityConfidenceThreshold"];
613
+ if (self.maxAcceptedAccuracy != nil) [dict setObject:self.maxAcceptedAccuracy forKey:@"maxAcceptedAccuracy"];
579
614
  if ([self hasStationaryRadius]) [dict setObject:self.stationaryRadius forKey:@"stationaryRadius"];
580
615
  if ([self hasDistanceFilter]) [dict setObject:self.distanceFilter forKey:@"distanceFilter"];
581
616
  if ([self hasDesiredAccuracy]) [dict setObject:self.desiredAccuracy forKey:@"desiredAccuracy"];
@@ -40,6 +40,9 @@
40
40
  #define CC_COLUMN_NAME_PAUSE_LOCATION_UPDATES "pause_updates"
41
41
  #define CC_COLUMN_NAME_TEMPLATE "template"
42
42
  #define CC_COLUMN_NAME_LAST_UPDATED_AT "updated_at"
43
+ // v4.5: full Config JSON blob — paridad con Android. Storage of post-3.2 keys without
44
+ // new per-field columns (httpMethod, queryParams, drivingEvents, includeBattery, ...).
45
+ #define CC_COLUMN_NAME_CONFIG_JSON "config_json"
43
46
 
44
47
  @interface MAURConfigurationContract : NSObject
45
48
 
@@ -43,7 +43,9 @@
43
43
  @{ @"name": @CC_COLUMN_NAME_MAX_LOCATIONS, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
44
44
  @{ @"name": @CC_COLUMN_NAME_PAUSE_LOCATION_UPDATES, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
45
45
  @{ @"name": @CC_COLUMN_NAME_TEMPLATE, @"type": [SQLColumnType sqlColumnWithType: kText]},
46
- @{ @"name": @CC_COLUMN_NAME_LAST_UPDATED_AT, @"type": [SQLColumnType sqlColumnWithType: kInteger]}
46
+ @{ @"name": @CC_COLUMN_NAME_LAST_UPDATED_AT, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
47
+ // v4.5: full Config JSON blob
48
+ @{ @"name": @CC_COLUMN_NAME_CONFIG_JSON, @"type": [SQLColumnType sqlColumnWithType: kText]}
47
49
  ];
48
50
 
49
51
  return [MAURSQLiteHelper createTableSqlStatement:@CC_TABLE_NAME columns:columns];
@@ -531,11 +531,20 @@ enum {
531
531
  - (void) onDestroy {
532
532
  DDLogInfo(@"Destroying %@ ", TAG);
533
533
  [self onStop:nil];
534
+
535
+ // v4.5.2: release our delegate slot so a CLLocationManager retained by the
536
+ // OS (e.g. while a stationary region monitor is still alive briefly after
537
+ // stop) cannot deliver callbacks to a destroyed provider.
538
+ if (locationManager != nil && locationManager.delegate == self) {
539
+ locationManager.delegate = nil;
540
+ }
534
541
  }
535
542
 
536
543
  - (void) dealloc
537
544
  {
538
- // locationController.delegate = nil;
545
+ if (locationManager != nil && locationManager.delegate == self) {
546
+ locationManager.delegate = nil;
547
+ }
539
548
  }
540
549
 
541
550
  @end
@@ -15,7 +15,9 @@
15
15
  @implementation MAURGeolocationOpenHelper
16
16
 
17
17
  static NSString *const kDatabaseName = @"cordova_bg_geolocation.db";
18
- static NSInteger const kDatabaseVersion = 5;
18
+ // v4.5.0: bumped to 7 to add events_json + battery_level + is_charging on locations,
19
+ // and config_json on configuration (paridad con Android v22).
20
+ static NSInteger const kDatabaseVersion = 7;
19
21
 
20
22
  - (instancetype)init
21
23
  {
@@ -94,6 +96,18 @@ static NSInteger const kDatabaseVersion = 5;
94
96
  [MAURSessionLocationContract createTableSQL],
95
97
  [NSString stringWithFormat:@"CREATE INDEX session_recorded_at_idx ON %@ (%@)", @LSC_TABLE_NAME, @LSC_COLUMN_NAME_RECORDED_AT]
96
98
  ]];
99
+ case 5:
100
+ // v4.5.0: persist driving events / battery / charging on locations.
101
+ [sql addObjectsFromArray: @[
102
+ @"ALTER TABLE " @LC_TABLE_NAME @" ADD COLUMN " @LC_COLUMN_NAME_EVENTS_JSON @" TEXT",
103
+ @"ALTER TABLE " @LC_TABLE_NAME @" ADD COLUMN " @LC_COLUMN_NAME_BATTERY_LEVEL @" INTEGER",
104
+ @"ALTER TABLE " @LC_TABLE_NAME @" ADD COLUMN " @LC_COLUMN_NAME_IS_CHARGING @" INTEGER"
105
+ ]];
106
+ case 6:
107
+ // v4.5.0: full config persisted as JSON blob (paridad con Android config_json).
108
+ [sql addObjectsFromArray: @[
109
+ [NSString stringWithFormat:@"ALTER TABLE %s ADD COLUMN %s TEXT", CC_TABLE_NAME, CC_COLUMN_NAME_CONFIG_JSON]
110
+ ]];
97
111
  break; // break only for previous db version (cascade statements)
98
112
  default:
99
113
  return;
@@ -39,6 +39,18 @@ typedef NS_ENUM(NSInteger, MAURLocationStatus) {
39
39
  @property (nonatomic, retain) NSDate *recordedAt;
40
40
  /** True if location was simulated by software (e.g. Simulator). iOS 15+. */
41
41
  @property (nonatomic, retain) NSNumber *simulated;
42
+ /**
43
+ * v4.3 — Driving events anexados a este fix.
44
+ * v4.5: persiste en SQLite (events_json TEXT) — sobrevive a la cola de sync.
45
+ * Cada elemento es un NSDictionary con al menos { "type": NSString, "time": NSNumber }.
46
+ */
47
+ @property (nonatomic, retain) NSMutableArray *drivingEvents;
48
+ /** v4.4 — Battery percentage (0-100) at the time of this fix.
49
+ * v4.5: persisted in SQLite (battery_level INTEGER). */
50
+ @property (nonatomic, retain) NSNumber *batteryLevel;
51
+ /** v4.4 — Whether the device is charging at the time of this fix.
52
+ * v4.5: persisted in SQLite (is_charging INTEGER). */
53
+ @property (nonatomic, retain) NSNumber *isCharging;
42
54
 
43
55
  + (instancetype) fromCLLocation:(CLLocation*)location;
44
56
  + (NSTimeInterval) locationAge:(CLLocation*)location;
@@ -20,13 +20,25 @@ enum {
20
20
  + (instancetype) map:(MAURLocation*)location;
21
21
  @end
22
22
 
23
- @implementation MAURLocationMapper
24
- MAURLocation* _location;
23
+ // v4.5.1 — _location was previously a file-scope global. With real-time post (background queue)
24
+ // and background sync (NSURLSession queue) running concurrently, the second [+map:] invocation
25
+ // overwrote _location while the first mapper was mid-serialize, producing mixed location fields
26
+ // at the backend. Now per-instance ivar.
27
+ @implementation MAURLocationMapper {
28
+ MAURLocation *_location;
29
+ }
25
30
 
26
31
  - (id) mapValue:(id)value
27
32
  {
28
33
  if ([value isKindOfClass:[NSString class]]) {
29
34
  id locationValue = [_location getValueForKey:value];
35
+ // v4.5.1 — for placeholder keys ("@time", "@events", "@battery", ...), if the location
36
+ // has no value, return NSNull instead of leaking the literal "@events" string to the
37
+ // backend. For non-placeholder static strings (e.g. a `deviceId` literal in postTemplate)
38
+ // keep the previous behaviour and return the string as-is.
39
+ if ([value hasPrefix:@"@"]) {
40
+ return locationValue != nil ? locationValue : [NSNull null];
41
+ }
30
42
  return locationValue != nil ? locationValue : value;
31
43
  } else if ([value isKindOfClass:[NSDictionary class]]) {
32
44
  return [self withDictionary:value];
@@ -63,7 +75,7 @@ MAURLocation* _location;
63
75
  + (instancetype) map:(MAURLocation*)location
64
76
  {
65
77
  MAURLocationMapper *instance = [[MAURLocationMapper alloc] init];
66
- _location = location;
78
+ instance->_location = location;
67
79
  return instance;
68
80
  }
69
81
  @end
@@ -71,7 +83,7 @@ MAURLocation* _location;
71
83
 
72
84
  @implementation MAURLocation
73
85
 
74
- @synthesize locationId, time, accuracy, altitudeAccuracy, speed, heading, altitude, latitude, longitude, provider, locationProvider, radius, isValid, recordedAt, simulated;
86
+ @synthesize locationId, time, accuracy, altitudeAccuracy, speed, heading, altitude, latitude, longitude, provider, locationProvider, radius, isValid, recordedAt, simulated, drivingEvents, batteryLevel, isCharging;
75
87
 
76
88
  + (instancetype) fromCLLocation:(CLLocation*)location;
77
89
  {
@@ -172,6 +184,11 @@ MAURLocation* _location;
172
184
  if (radius != nil) [dict setObject:radius forKey:@"radius"];
173
185
  if (recordedAt != nil) [dict setObject:[NSNumber numberWithDouble:([recordedAt timeIntervalSince1970] * 1000)] forKey:@"recordedAt"];
174
186
  if (simulated != nil) [dict setObject:simulated forKey:@"simulated"];
187
+ // v4.3 — driving events anexados a este fix
188
+ if (drivingEvents != nil && [drivingEvents count] > 0) [dict setObject:drivingEvents forKey:@"events"];
189
+ // v4.4 — battery snapshot
190
+ if (batteryLevel != nil) [dict setObject:batteryLevel forKey:@"battery"];
191
+ if (isCharging != nil) [dict setObject:isCharging forKey:@"isCharging"];
175
192
 
176
193
  return dict;
177
194
  }
@@ -227,6 +244,13 @@ MAURLocation* _location;
227
244
  if ([key isEqualToString:@"@simulated"]) {
228
245
  return simulated;
229
246
  }
247
+ // v4.3 — driving events array (nil when no events on this fix; mapper drops nil keys).
248
+ if ([key isEqualToString:@"@events"]) {
249
+ return (drivingEvents != nil && [drivingEvents count] > 0) ? drivingEvents : nil;
250
+ }
251
+ // v4.4 — battery snapshot
252
+ if ([key isEqualToString:@"@battery"]) return batteryLevel;
253
+ if ([key isEqualToString:@"@isCharging"]) return isCharging;
230
254
 
231
255
  return nil;
232
256
  }
@@ -355,6 +379,11 @@ MAURLocation* _location;
355
379
  copy.radius = radius;
356
380
  copy.isValid = isValid;
357
381
  copy.simulated = simulated;
382
+ // v4.3: copy driving events array reference (transient; mutating one affects the other,
383
+ // but in practice the original is discarded right after the copy is posted).
384
+ copy.drivingEvents = drivingEvents != nil ? [drivingEvents mutableCopy] : nil;
385
+ copy.batteryLevel = batteryLevel;
386
+ copy.isCharging = isCharging;
358
387
  }
359
388
 
360
389
  return copy;
@@ -23,6 +23,10 @@
23
23
  #define LC_COLUMN_NAME_LOCATION_PROVIDER "service_provider"
24
24
  #define LC_COLUMN_NAME_STATUS "valid"
25
25
  #define LC_COLUMN_NAME_RECORDED_AT "recorded_at"
26
+ // v4.5 — survive sync queue
27
+ #define LC_COLUMN_NAME_EVENTS_JSON "events_json"
28
+ #define LC_COLUMN_NAME_BATTERY_LEVEL "battery_level"
29
+ #define LC_COLUMN_NAME_IS_CHARGING "is_charging"
26
30
 
27
31
  @interface MAURLocationContract : NSObject
28
32
 
@@ -26,7 +26,11 @@
26
26
  @{ @"name": @LC_COLUMN_NAME_PROVIDER, @"type": [SQLColumnType sqlColumnWithType: kText]},
27
27
  @{ @"name": @LC_COLUMN_NAME_LOCATION_PROVIDER, @"type": [SQLColumnType sqlColumnWithType: kText]},
28
28
  @{ @"name": @LC_COLUMN_NAME_STATUS, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
29
- @{ @"name": @LC_COLUMN_NAME_RECORDED_AT, @"type": [SQLColumnType sqlColumnWithType: kInteger]}
29
+ @{ @"name": @LC_COLUMN_NAME_RECORDED_AT, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
30
+ // v4.5 — survive sync queue
31
+ @{ @"name": @LC_COLUMN_NAME_EVENTS_JSON, @"type": [SQLColumnType sqlColumnWithType: kText]},
32
+ @{ @"name": @LC_COLUMN_NAME_BATTERY_LEVEL, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
33
+ @{ @"name": @LC_COLUMN_NAME_IS_CHARGING, @"type": [SQLColumnType sqlColumnWithType: kInteger]}
30
34
  ];
31
35
 
32
36
  return [MAURSQLiteHelper createTableSqlStatement:@LC_TABLE_NAME columns:columns];
@@ -213,10 +213,28 @@ static NSString *const Domain = @"com.marianhello";
213
213
  }
214
214
  }
215
215
 
216
+ // v4.5.2: iOS 14+ delegate callback. The legacy
217
+ // `locationManager:didChangeAuthorizationStatus:` is deprecated in iOS 14 but
218
+ // still delivered alongside this one, so we ignore the legacy variant when
219
+ // running on iOS 14+ to avoid double-notifying delegates (RAW + ACTIVITY
220
+ // providers go through this MAURLocationManager singleton).
221
+ - (void) locationManagerDidChangeAuthorization:(CLLocationManager *)manager API_AVAILABLE(ios(14.0))
222
+ {
223
+ [self maurDispatchAuthorizationStatus:manager.authorizationStatus];
224
+ }
225
+
216
226
  - (void) locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
227
+ {
228
+ if (@available(iOS 14.0, *)) {
229
+ return; // delivered by locationManagerDidChangeAuthorization: above
230
+ }
231
+ [self maurDispatchAuthorizationStatus:status];
232
+ }
233
+
234
+ - (void) maurDispatchAuthorizationStatus:(CLAuthorizationStatus)status
217
235
  {
218
236
  MAURLocationAuthorizationStatus authStatus;
219
-
237
+
220
238
  switch(status) {
221
239
  case kCLAuthorizationStatusRestricted:
222
240
  case kCLAuthorizationStatusDenied: