@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.
- package/.npmignore +11 -0
- package/CHANGELOG.md +261 -0
- package/README.md +306 -115
- package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +34 -0
- package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +61 -1
- package/android/common/src/main/AndroidManifest.xml +1 -1
- package/android/common/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +20 -3
- package/android/common/src/main/java/com/marianhello/bgloc/Config.java +87 -1
- package/android/common/src/main/java/com/marianhello/bgloc/data/BackgroundLocation.java +94 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/ConfigJsonMapper.java +211 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/LocationTemplateFactory.java +6 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationContract.java +5 -1
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationDAO.java +32 -1
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationContract.java +12 -2
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationDAO.java +33 -2
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +15 -1
- package/android/common/src/main/java/com/marianhello/bgloc/provider/AbstractLocationProvider.java +48 -1
- package/android/common/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +105 -6
- package/android/common/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +336 -250
- package/android/common/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +69 -19
- package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +246 -21
- package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +5 -2
- package/android/common/src/main/java/com/marianhello/bgloc/sync/BatchManager.java +46 -13
- package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +23 -1
- package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.m +208 -70
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +132 -5
- package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +20 -0
- package/ios/common/BackgroundGeolocation/MAURConfig.h +7 -0
- package/ios/common/BackgroundGeolocation/MAURConfig.m +37 -2
- package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +3 -0
- package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +3 -1
- package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +10 -1
- package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +15 -1
- package/ios/common/BackgroundGeolocation/MAURLocation.h +12 -0
- package/ios/common/BackgroundGeolocation/MAURLocation.m +33 -4
- package/ios/common/BackgroundGeolocation/MAURLocationContract.h +4 -0
- package/ios/common/BackgroundGeolocation/MAURLocationContract.m +5 -1
- package/ios/common/BackgroundGeolocation/MAURLocationManager.m +19 -1
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +9 -0
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +59 -1
- package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.m +10 -1
- package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.m +54 -4
- package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.h +12 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.m +125 -5
- package/package.json +31 -1
- package/plugin.xml +3 -10
- package/www/BackgroundGeolocation.d.ts +143 -3
- package/www/BackgroundGeolocation.js +11 -4
- package/CLAUDE.md +0 -56
- package/HISTORY.md +0 -871
- package/android/CDVBackgroundGeolocation/src/test/java/com/marianhello/ConfigMapperTest.java +0 -220
- package/android/common/src/androidTest/java/com/marianhello/bgloc/BackgroundGeolocationFacadeTest.java +0 -45
- package/android/common/src/androidTest/java/com/marianhello/bgloc/BatchManagerTest.java +0 -570
- package/android/common/src/androidTest/java/com/marianhello/bgloc/ConfigTest.java +0 -76
- package/android/common/src/androidTest/java/com/marianhello/bgloc/ContentProviderLocationDAOTest.java +0 -437
- package/android/common/src/androidTest/java/com/marianhello/bgloc/DBLogReaderTest.java +0 -95
- package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationContentProviderTest.java +0 -159
- package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationServiceProxyTest.java +0 -161
- package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationServiceTest.java +0 -247
- package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteConfigurationDAOTest.java +0 -200
- package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteLocationDAOTest.java +0 -457
- package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteLocationDAOThreadTest.java +0 -96
- package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteOpenHelperTest.java +0 -225
- package/android/common/src/androidTest/java/com/marianhello/bgloc/TestPluginDelegate.java +0 -46
- package/android/common/src/androidTest/java/com/marianhello/bgloc/TestResourceResolver.java +0 -14
- package/android/common/src/androidTest/java/com/marianhello/bgloc/provider/MockLocationProvider.java +0 -50
- package/android/common/src/androidTest/java/com/marianhello/bgloc/provider/TestLocationProviderFactory.java +0 -17
- package/android/common/src/androidTest/java/com/marianhello/bgloc/sqlite/SQLiteOpenHelper10.java +0 -92
- package/android/common/src/androidTest/java/com/marianhello/bgloc/test/LocationProviderTestCase.java +0 -107
- package/android/common/src/androidTest/java/com/marianhello/bgloc/test/TestConstants.java +0 -5
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/ArrayListLocationTemplateTest.java +0 -82
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/BackgroundLocationTest.java +0 -128
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/ConfigTest.java +0 -191
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/DBLogReaderTest.java +0 -37
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/HashMapLocationTemplateTest.java +0 -216
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/HttpPostServiceTest.java +0 -223
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/LocationTemplateFactoryTest.java +0 -50
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/PostLocationTaskTest.java +0 -180
- package/android/common/src/test/java/com/marianhello/backgroundgeolocation/TestHelper.java +0 -16
- package/ios/common/BackgroundGeolocation/SOMotionDetector/CHANGELOG.md +0 -2
- package/ios/common/BackgroundGeolocation/SOMotionDetector/LICENSE +0 -21
- package/ios/common/BackgroundGeolocation/SOMotionDetector/README.md +0 -135
- package/ios/common/BackgroundGeolocation/SOMotionDetector/SOLocationManager.h +0 -80
- package/ios/common/BackgroundGeolocation/SOMotionDetector/SOLocationManager.m +0 -147
- package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionActivity.h +0 -30
- package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionActivity.m +0 -42
- package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionDetector.h +0 -99
- package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionDetector.m +0 -327
- package/ios/common/BackgroundGeolocation/SOMotionDetector/SOStepDetector.h +0 -44
- package/ios/common/BackgroundGeolocation/SOMotionDetector/SOStepDetector.m +0 -94
- package/ios/common/BackgroundGeolocationTests/Info.plist +0 -24
- package/ios/common/BackgroundGeolocationTests/MAURBackgroundLocationTest.m +0 -185
- package/ios/common/BackgroundGeolocationTests/MAURConfigTest.m +0 -161
- package/ios/common/BackgroundGeolocationTests/MAURGeolocationOpenHelperTest.m +0 -102
- package/ios/common/BackgroundGeolocationTests/MAURLocationTest.m +0 -216
- package/ios/common/BackgroundGeolocationTests/MAURLocationUploaderTest.m +0 -55
- package/ios/common/BackgroundGeolocationTests/MAURLogReaderTest.m +0 -43
- package/ios/common/BackgroundGeolocationTests/MAURSQLiteConfigurationDAOTest.m +0 -102
- package/ios/common/BackgroundGeolocationTests/MAURSQLiteHelperTest.m +0 -41
- package/ios/common/BackgroundGeolocationTests/MAURSQLiteLocationDAOTests.m +0 -240
- package/ios/common/BackgroundGeolocationTests/MAURSQLiteLocationDAOThreadTest.m +0 -84
- package/ios/common/BackgroundGeolocationTests/MAURSQLiteOpenHelperTest.m +0 -144
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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:
|