@josuelmm/cordova-background-geolocation 3.1.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.npmignore +4 -0
  2. package/CHANGELOG.md +313 -0
  3. package/CLAUDE.md +56 -0
  4. package/HISTORY.md +124 -0
  5. package/README.md +198 -6
  6. package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +90 -0
  7. package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +362 -1
  8. package/android/common/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +153 -0
  9. package/android/common/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +27 -11
  10. package/android/common/src/main/java/com/marianhello/bgloc/Config.java +268 -0
  11. package/android/common/src/main/java/com/marianhello/bgloc/HttpPostService.java +86 -26
  12. package/android/common/src/main/java/com/marianhello/bgloc/PluginDelegate.java +26 -0
  13. package/android/common/src/main/java/com/marianhello/bgloc/PostLocationTask.java +48 -5
  14. package/android/common/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
  15. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +8 -1
  16. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
  17. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
  18. package/android/common/src/main/java/com/marianhello/bgloc/driving/DrivingEventsDetector.java +265 -0
  19. package/android/common/src/main/java/com/marianhello/bgloc/http/UrlTemplateResolver.java +115 -0
  20. package/android/common/src/main/java/com/marianhello/bgloc/oem/BatteryOemHelper.java +214 -0
  21. package/android/common/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +13 -9
  22. package/android/common/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +29 -40
  23. package/android/common/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +14 -34
  24. package/android/common/src/main/java/com/marianhello/bgloc/sensor/SensorFusionDetector.java +199 -0
  25. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +310 -7
  26. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +14 -2
  27. package/android/common/src/main/java/com/marianhello/bgloc/sync/SyncAdapter.java +50 -3
  28. package/android/dependencies.gradle +0 -3
  29. package/angular/background-geolocation-events.ts +21 -0
  30. package/angular/background-geolocation.service.ts +91 -0
  31. package/angular/dist/background-geolocation-events.d.ts +18 -1
  32. package/angular/dist/background-geolocation.service.d.ts +40 -0
  33. package/angular/dist/esm2022/background-geolocation-events.mjs +22 -1
  34. package/angular/dist/esm2022/background-geolocation.service.mjs +47 -1
  35. package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs +67 -0
  36. package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs.map +1 -1
  37. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.h +4 -0
  38. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +352 -1
  39. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +26 -0
  40. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +421 -15
  41. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.h +12 -0
  42. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +83 -5
  43. package/ios/common/BackgroundGeolocation/MAURConfig.h +15 -0
  44. package/ios/common/BackgroundGeolocation/MAURConfig.m +100 -3
  45. package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +29 -2
  46. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +12 -3
  47. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +4 -0
  48. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +102 -44
  49. package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.h +41 -0
  50. package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.m +137 -0
  51. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.h +29 -0
  52. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.m +31 -0
  53. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.h +25 -0
  54. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.m +153 -0
  55. package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.h +31 -0
  56. package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
  57. package/package.json +36 -1
  58. package/plugin.xml +26 -8
  59. package/www/BackgroundGeolocation.d.ts +559 -3
  60. package/www/BackgroundGeolocation.js +78 -1
  61. package/RELEASE.MD +0 -16
@@ -17,6 +17,7 @@
17
17
  #import "MAURPostLocationTask.h"
18
18
  #import "MAURSQLiteConfigurationDAO.h"
19
19
  #import "MAURSQLiteLocationDAO.h"
20
+ #import "MAURSessionLocationDAO.h"
20
21
  #import "MAURBackgroundTaskManager.h"
21
22
  #import "MAURLogging.h"
22
23
  #import "FMDBLogger.h"
@@ -28,6 +29,7 @@
28
29
  #import "MAURUncaughtExceptionLogger.h"
29
30
  #import "MAURPostLocationTask.h"
30
31
  #import "INTULocationManager.h"
32
+ #import "MAURSensorFusionDetector.h"
31
33
 
32
34
  // error messages
33
35
  #define CONFIGURE_ERROR_MSG "Configuration error."
@@ -45,21 +47,63 @@ static NSString * const TAG = @"BgGeo";
45
47
 
46
48
  FMDBLogger *sqliteLogger;
47
49
 
48
- @interface MAURBackgroundGeolocationFacade () <MAURProviderDelegate, MAURPostLocationTaskDelegate>
50
+ @interface MAURBackgroundGeolocationFacade () <MAURProviderDelegate, MAURPostLocationTaskDelegate, MAURSensorFusionListener>
49
51
  @end
50
52
 
53
+ // v3.5 Phase 4: notification name for heartbeat events. CDVBackgroundGeolocation observes
54
+ // it to forward into the JS event "heartbeat" with the latest known location.
55
+ NSString * const MAURHeartbeatNotification = @"MAURHeartbeatNotification";
56
+ // v4.0 Phase 6: driver-insight notifications.
57
+ NSString * const MAURTripStartNotification = @"MAURTripStartNotification";
58
+ NSString * const MAURTripEndNotification = @"MAURTripEndNotification";
59
+ NSString * const MAURMovingNotification = @"MAURMovingNotification";
60
+ NSString * const MAURStoppedNotification = @"MAURStoppedNotification";
61
+ NSString * const MAURSpeedingNotification = @"MAURSpeedingNotification";
62
+ NSString * const MAURProviderChangeNotification = @"MAURProviderChangeNotification";
63
+ NSString * const MAURSOSNotification = @"MAURSOSNotification";
64
+ // v4.1
65
+ NSString * const MAURHardBrakeNotification = @"MAURHardBrakeNotification";
66
+ NSString * const MAURRapidAccelerationNotification = @"MAURRapidAccelerationNotification";
67
+ NSString * const MAURSharpTurnNotification = @"MAURSharpTurnNotification";
68
+ NSString * const MAURPossibleCrashNotification = @"MAURPossibleCrashNotification";
69
+ // v4.2
70
+ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileDrivingNotification";
71
+
51
72
  @implementation MAURBackgroundGeolocationFacade {
52
73
  BOOL isStarted;
53
74
  MAUROperationalMode operationMode;
54
-
75
+
55
76
  UILocalNotification *localNotification;
56
-
77
+
57
78
  // configurable options
58
79
  MAURConfig *_config;
59
-
80
+
60
81
  MAURLocation *stationaryLocation;
82
+ MAURLocation *lastReceivedLocation; // v3.5 Phase 4: heartbeat payload
83
+ NSTimer *heartbeatTimer; // v3.5 Phase 4
61
84
  MAURAbstractLocationProvider<MAURLocationProvider> *locationProvider;
62
85
  MAURPostLocationTask *postLocationTask;
86
+
87
+ // v4.0 Phase 6: driver-insights state
88
+ BOOL drIsMoving;
89
+ BOOL drTripActive;
90
+ NSTimeInterval drTripStartedAt;
91
+ double drTripDistanceMeters;
92
+ BOOL drHasPrev;
93
+ double drPrevLat, drPrevLon;
94
+ NSTimeInterval drAboveTripSpeedSince;
95
+ NSTimeInterval drBelowMovingSince;
96
+ BOOL drWasSpeeding;
97
+ NSString *drLastProvider;
98
+ // v4.1 GPS-derived sensor-like state
99
+ double drPrevSpeed;
100
+ NSTimeInterval drPrevSpeedAt;
101
+ double drPrevBearing;
102
+ BOOL drHasPrevBearing;
103
+ NSTimeInterval drPrevBearingAt;
104
+ NSTimeInterval drLastHardBrakeAt, drLastRapidAccelAt, drLastSharpTurnAt, drLastCrashAt;
105
+ // v4.2 sensor fusion
106
+ MAURSensorFusionDetector *sensorFusion;
63
107
  }
64
108
 
65
109
 
@@ -135,25 +179,41 @@ FMDBLogger *sqliteLogger;
135
179
  if (isStarted) {
136
180
  // Note: CLLocationManager must be created on a thread with an active run loop (main thread)
137
181
  [self runOnMainThread:^{
138
-
182
+
139
183
  // requesting new provider
140
184
  if (![currentConfig.locationProvider isEqual:_config.locationProvider]) {
141
185
  [locationProvider onDestroy]; // destroy current provider
142
186
  locationProvider = [self getProvider:_config.locationProvider.intValue error:&error];
143
187
  }
144
-
188
+
145
189
  if (locationProvider == nil) {
146
190
  return;
147
191
  }
148
-
192
+
149
193
  // trap configuration errors
150
194
  if (![locationProvider onConfigure:_config error:&error]) {
151
195
  return;
152
196
  }
153
-
197
+
154
198
  isStarted = [locationProvider onStart:&error];
155
199
  locationProvider.delegate = self;
156
200
  }];
201
+
202
+ // v4.1: hot-reload heartbeat scheduler if heartbeatInterval changed.
203
+ NSInteger prevHb = currentConfig.heartbeatInterval != nil ? [currentConfig.heartbeatInterval integerValue] : 0;
204
+ NSInteger newHb = _config.heartbeatInterval != nil ? [_config.heartbeatInterval integerValue] : 0;
205
+ if (prevHb != newHb) {
206
+ [self scheduleHeartbeat]; // cancels and reschedules; is a no-op if 0.
207
+ }
208
+ // Driver-insights detector reads `_config.drivingEvents` on every feed; no rebuild needed
209
+ // unless the dictionary identity changed in a way that toggles `enabled`. Always reset
210
+ // accumulators to apply the new thresholds cleanly from this point on.
211
+ if (![[currentConfig.drivingEvents description] isEqualToString:[_config.drivingEvents description]]) {
212
+ [self drivingDetectorReset];
213
+ // v4.2: re-evaluate sensor fusion as well (might have just been enabled/disabled).
214
+ [self configureSensorFusion];
215
+ if (isStarted) [sensorFusion start];
216
+ }
157
217
  }
158
218
 
159
219
  if (error != nil) {
@@ -212,10 +272,16 @@ FMDBLogger *sqliteLogger;
212
272
  if (outError != nil) {
213
273
  *outError = error;
214
274
  }
215
-
275
+
216
276
  return NO;
217
277
  }
218
-
278
+
279
+ // v3.5 Phase 4: schedule heartbeat once provider is up.
280
+ [self scheduleHeartbeat];
281
+ // v4.2 Phase 8: configure & start sensor fusion if requested.
282
+ [self configureSensorFusion];
283
+ [sensorFusion start];
284
+
219
285
  return isStarted;
220
286
  }
221
287
 
@@ -225,20 +291,334 @@ FMDBLogger *sqliteLogger;
225
291
  - (BOOL) stop:(NSError * __autoreleasing *)outError
226
292
  {
227
293
  DDLogInfo(@"%@ #stop", TAG);
228
-
294
+
229
295
  if (!isStarted) {
230
296
  return YES;
231
297
  }
232
-
298
+
299
+ // v3.5 Phase 4: cancel heartbeat scheduler.
300
+ [self cancelHeartbeat];
301
+ // v4.0 Phase 6: reset driver-insights state machine.
302
+ [self drivingDetectorReset];
303
+ // v4.2 Phase 8: stop sensor fusion sampling.
304
+ sensorFusion.tripActive = NO;
305
+ [sensorFusion stop];
306
+
233
307
  [postLocationTask stop];
234
-
308
+
235
309
  [self runOnMainThread:^{
236
310
  isStarted = ![locationProvider onStop:outError];
237
311
  }];
238
-
312
+
239
313
  return isStarted;
240
314
  }
241
315
 
316
+ // v3.5 Phase 4: heartbeat scheduler.
317
+ - (void) scheduleHeartbeat
318
+ {
319
+ [self cancelHeartbeat];
320
+ if (_config == nil || _config.heartbeatInterval == nil) return;
321
+ NSInteger ms = [_config.heartbeatInterval integerValue];
322
+ if (ms <= 0) return;
323
+ NSTimeInterval seconds = ms / 1000.0;
324
+ DDLogDebug(@"%@ scheduling heartbeat every %.2fs", TAG, seconds);
325
+ dispatch_async(dispatch_get_main_queue(), ^{
326
+ heartbeatTimer = [NSTimer scheduledTimerWithTimeInterval:seconds
327
+ target:self
328
+ selector:@selector(onHeartbeatTick:)
329
+ userInfo:nil
330
+ repeats:YES];
331
+ });
332
+ }
333
+
334
+ - (void) cancelHeartbeat
335
+ {
336
+ if (heartbeatTimer != nil) {
337
+ [heartbeatTimer invalidate];
338
+ heartbeatTimer = nil;
339
+ }
340
+ }
341
+
342
+ - (void) onHeartbeatTick:(NSTimer *)timer
343
+ {
344
+ NSDictionary *userInfo = lastReceivedLocation != nil
345
+ ? @{ @"location": lastReceivedLocation }
346
+ : @{};
347
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURHeartbeatNotification
348
+ object:self
349
+ userInfo:userInfo];
350
+ }
351
+
352
+ #pragma mark - v4.0 Phase 6 driver-insights state machine
353
+
354
+ - (void) drivingDetectorReset
355
+ {
356
+ drIsMoving = NO;
357
+ drTripActive = NO;
358
+ drTripStartedAt = 0;
359
+ drTripDistanceMeters = 0;
360
+ drHasPrev = NO;
361
+ drAboveTripSpeedSince = 0;
362
+ drBelowMovingSince = 0;
363
+ drWasSpeeding = NO;
364
+ drLastProvider = nil;
365
+ drPrevSpeed = 0;
366
+ drPrevSpeedAt = 0;
367
+ drPrevBearing = 0;
368
+ drHasPrevBearing = NO;
369
+ drPrevBearingAt = 0;
370
+ drLastHardBrakeAt = drLastRapidAccelAt = drLastSharpTurnAt = drLastCrashAt = 0;
371
+ }
372
+
373
+ #pragma mark - v4.2 Phase 8 sensor fusion
374
+
375
+ - (void) configureSensorFusion
376
+ {
377
+ NSDictionary *de = _config.drivingEvents;
378
+ BOOL want = [de isKindOfClass:[NSDictionary class]]
379
+ && [de[@"enabled"] boolValue]
380
+ && [de[@"sensorFusion"] boolValue];
381
+ if (!want) {
382
+ [sensorFusion stop];
383
+ sensorFusion = nil;
384
+ return;
385
+ }
386
+ if (sensorFusion == nil) {
387
+ sensorFusion = [[MAURSensorFusionDetector alloc] init];
388
+ sensorFusion.listener = self;
389
+ }
390
+ sensorFusion.enabled = YES;
391
+ if (de[@"crashImpactG"]) sensorFusion.crashImpactG = [de[@"crashImpactG"] doubleValue];
392
+ if (de[@"sensorCrashCooldownMs"]) sensorFusion.crashCooldownMs = [de[@"sensorCrashCooldownMs"] doubleValue];
393
+ if (de[@"phoneUsageWindowMs"]) sensorFusion.phoneUsageWindowMs = [de[@"phoneUsageWindowMs"] doubleValue];
394
+ if (de[@"phoneUsageCooldownMs"]) sensorFusion.phoneUsageCooldownMs = [de[@"phoneUsageCooldownMs"] doubleValue];
395
+ // v4.2 hot-reload: re-inject current trip state + last location so a config change
396
+ // mid-trip starts the sensor pipeline in the correct mode.
397
+ sensorFusion.tripActive = drTripActive;
398
+ sensorFusion.lastLocation = lastReceivedLocation;
399
+ }
400
+
401
+ // MAURSensorFusionListener
402
+ - (void) onSensorCrashWithImpactG:(double)impactG location:(MAURLocation *)location
403
+ {
404
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
405
+ if (location != nil) userInfo[@"location"] = location;
406
+ userInfo[@"value"] = @(impactG);
407
+ userInfo[@"source"] = @"sensor";
408
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURPossibleCrashNotification
409
+ object:self
410
+ userInfo:userInfo];
411
+ }
412
+ - (void) onPhoneUsageWhileDriving:(MAURLocation *)location
413
+ {
414
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
415
+ if (location != nil) userInfo[@"location"] = location;
416
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURPhoneUsageWhileDrivingNotification
417
+ object:self
418
+ userInfo:userInfo];
419
+ }
420
+
421
+ - (void) drivingDetectorFeed:(MAURLocation *)loc
422
+ {
423
+ if (loc == nil || _config == nil) return;
424
+ BOOL enabled = NO;
425
+ double speedLimit = 0;
426
+ double minMovingSpeed = 1.0;
427
+ NSTimeInterval stoppedDuration = 60.0;
428
+ double minTripSpeed = 3.0;
429
+ NSTimeInterval minTripDuration = 30.0;
430
+ NSDictionary *de = [_config valueForKey:@"drivingEvents"]; // see MAURConfig: provided as NSDictionary
431
+ if ([de isKindOfClass:[NSDictionary class]]) {
432
+ enabled = [[de objectForKey:@"enabled"] boolValue];
433
+ speedLimit = [[de objectForKey:@"speedLimit"] doubleValue];
434
+ if ([de objectForKey:@"minMovingSpeed"]) minMovingSpeed = [[de objectForKey:@"minMovingSpeed"] doubleValue];
435
+ if ([de objectForKey:@"stoppedDuration"]) stoppedDuration = [[de objectForKey:@"stoppedDuration"] doubleValue] / 1000.0;
436
+ if ([de objectForKey:@"minTripSpeed"]) minTripSpeed = [[de objectForKey:@"minTripSpeed"] doubleValue];
437
+ if ([de objectForKey:@"minTripDuration"]) minTripDuration = [[de objectForKey:@"minTripDuration"] doubleValue] / 1000.0;
438
+ }
439
+ if (!enabled) return;
440
+
441
+ NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
442
+ double speed = loc.speed != nil ? [loc.speed doubleValue] : 0.0;
443
+ if (speed < 0) speed = 0;
444
+
445
+ // Provider change
446
+ NSString *provider = loc.provider;
447
+ if (provider != nil && ![provider isEqualToString:drLastProvider]) {
448
+ drLastProvider = provider;
449
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURProviderChangeNotification
450
+ object:self
451
+ userInfo:@{@"provider": provider}];
452
+ }
453
+
454
+ double curLat = [loc.latitude doubleValue];
455
+ double curLon = [loc.longitude doubleValue];
456
+ if (drHasPrev && drTripActive) {
457
+ drTripDistanceMeters += [self drHaversineFromLat:drPrevLat lon:drPrevLon toLat:curLat lon:curLon];
458
+ }
459
+ drPrevLat = curLat;
460
+ drPrevLon = curLon;
461
+ drHasPrev = YES;
462
+
463
+ BOOL nowMoving = speed >= minMovingSpeed;
464
+ if (nowMoving) {
465
+ drBelowMovingSince = 0;
466
+ if (!drIsMoving) {
467
+ drIsMoving = YES;
468
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURMovingNotification
469
+ object:self
470
+ userInfo:@{@"location": loc}];
471
+ }
472
+ if (!drTripActive) {
473
+ if (speed >= minTripSpeed) {
474
+ if (drAboveTripSpeedSince == 0) drAboveTripSpeedSince = now;
475
+ if (now - drAboveTripSpeedSince >= minTripDuration) {
476
+ drTripActive = YES;
477
+ drTripStartedAt = now;
478
+ drTripDistanceMeters = 0;
479
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURTripStartNotification
480
+ object:self
481
+ userInfo:@{@"location": loc}];
482
+ sensorFusion.tripActive = YES;
483
+ }
484
+ } else {
485
+ drAboveTripSpeedSince = 0;
486
+ }
487
+ }
488
+ } else {
489
+ drAboveTripSpeedSince = 0;
490
+ if (drBelowMovingSince == 0) drBelowMovingSince = now;
491
+ if (drIsMoving && (now - drBelowMovingSince) >= stoppedDuration) {
492
+ drIsMoving = NO;
493
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURStoppedNotification
494
+ object:self
495
+ userInfo:@{@"location": loc}];
496
+ if (drTripActive) {
497
+ NSTimeInterval durMs = (now - drTripStartedAt) * 1000.0;
498
+ double dist = drTripDistanceMeters;
499
+ drTripActive = NO;
500
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURTripEndNotification
501
+ object:self
502
+ userInfo:@{
503
+ @"location": loc,
504
+ @"distance": @(dist),
505
+ @"durationMs": @((long long)durMs)
506
+ }];
507
+ sensorFusion.tripActive = NO;
508
+ }
509
+ }
510
+ }
511
+
512
+ if (speedLimit > 0) {
513
+ double kmh = speed * 3.6;
514
+ if (kmh > speedLimit) {
515
+ if (!drWasSpeeding) {
516
+ drWasSpeeding = YES;
517
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURSpeedingNotification
518
+ object:self
519
+ userInfo:@{
520
+ @"location": loc,
521
+ @"speedKmh": @(kmh),
522
+ @"limitKmh": @(speedLimit)
523
+ }];
524
+ }
525
+ } else {
526
+ drWasSpeeding = NO;
527
+ }
528
+ }
529
+
530
+ // v4.1 GPS-derived sensor-like events
531
+ double hardBrakeMps2 = 3.5, rapidAccelMps2 = 3.5, sharpTurnDegPerSec = 30, crashImpactKmh = 25;
532
+ NSTimeInterval crashWindow = 2.0;
533
+ if ([de isKindOfClass:[NSDictionary class]]) {
534
+ if ([de objectForKey:@"hardBrakeMps2"]) hardBrakeMps2 = [[de objectForKey:@"hardBrakeMps2"] doubleValue];
535
+ if ([de objectForKey:@"rapidAccelMps2"]) rapidAccelMps2 = [[de objectForKey:@"rapidAccelMps2"] doubleValue];
536
+ if ([de objectForKey:@"sharpTurnDegPerSec"]) sharpTurnDegPerSec = [[de objectForKey:@"sharpTurnDegPerSec"] doubleValue];
537
+ if ([de objectForKey:@"crashImpactKmh"]) crashImpactKmh = [[de objectForKey:@"crashImpactKmh"] doubleValue];
538
+ if ([de objectForKey:@"crashWindowMs"]) crashWindow = [[de objectForKey:@"crashWindowMs"] doubleValue] / 1000.0;
539
+ }
540
+ static const NSTimeInterval kCooldown = 4.0;
541
+
542
+ if (drTripActive && drPrevSpeedAt > 0) {
543
+ NSTimeInterval dt = now - drPrevSpeedAt;
544
+ if (dt > 0 && dt <= 5.0) {
545
+ double dv = speed - drPrevSpeed;
546
+ double accel = dv / dt;
547
+ if (hardBrakeMps2 > 0 && accel <= -hardBrakeMps2 && (now - drLastHardBrakeAt) >= kCooldown) {
548
+ drLastHardBrakeAt = now;
549
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURHardBrakeNotification
550
+ object:self
551
+ userInfo:@{@"location": loc, @"value": @(accel)}];
552
+ }
553
+ if (rapidAccelMps2 > 0 && accel >= rapidAccelMps2 && (now - drLastRapidAccelAt) >= kCooldown) {
554
+ drLastRapidAccelAt = now;
555
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURRapidAccelerationNotification
556
+ object:self
557
+ userInfo:@{@"location": loc, @"value": @(accel)}];
558
+ }
559
+ if (crashImpactKmh > 0 && dt <= crashWindow) {
560
+ double dropKmh = (drPrevSpeed - speed) * 3.6;
561
+ if (dropKmh >= crashImpactKmh
562
+ && speed < 1.5
563
+ && drPrevSpeed * 3.6 >= crashImpactKmh
564
+ && (now - drLastCrashAt) >= kCooldown) {
565
+ drLastCrashAt = now;
566
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURPossibleCrashNotification
567
+ object:self
568
+ userInfo:@{@"location": loc, @"value": @(dropKmh), @"source": @"gps"}];
569
+ }
570
+ }
571
+ }
572
+ }
573
+
574
+ // Sharp turn (bearing rate)
575
+ if (sharpTurnDegPerSec > 0 && loc.heading != nil && speed >= 5.0 && drHasPrevBearing) {
576
+ NSTimeInterval dt = now - drPrevBearingAt;
577
+ if (dt > 0 && dt <= 5.0) {
578
+ double bearing = [loc.heading doubleValue];
579
+ double diff = fabs(bearing - drPrevBearing);
580
+ if (diff > 180) diff = 360 - diff;
581
+ double rate = diff / dt;
582
+ if (rate >= sharpTurnDegPerSec && (now - drLastSharpTurnAt) >= kCooldown) {
583
+ drLastSharpTurnAt = now;
584
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURSharpTurnNotification
585
+ object:self
586
+ userInfo:@{@"location": loc, @"value": @(rate)}];
587
+ }
588
+ }
589
+ drPrevBearing = [loc.heading doubleValue];
590
+ drPrevBearingAt = now;
591
+ } else if (loc.heading != nil) {
592
+ drPrevBearing = [loc.heading doubleValue];
593
+ drPrevBearingAt = now;
594
+ drHasPrevBearing = YES;
595
+ }
596
+
597
+ drPrevSpeed = speed;
598
+ drPrevSpeedAt = now;
599
+ }
600
+
601
+ - (double) drHaversineFromLat:(double)lat1 lon:(double)lon1 toLat:(double)lat2 lon:(double)lon2
602
+ {
603
+ const double R = 6371000.0;
604
+ double dLat = (lat2 - lat1) * M_PI / 180.0;
605
+ double dLon = (lon2 - lon1) * M_PI / 180.0;
606
+ double a = sin(dLat/2) * sin(dLat/2)
607
+ + cos(lat1 * M_PI / 180.0) * cos(lat2 * M_PI / 180.0)
608
+ * sin(dLon/2) * sin(dLon/2);
609
+ return 2 * R * asin(sqrt(a));
610
+ }
611
+
612
+ - (void) triggerSOS:(NSDictionary *)payload
613
+ {
614
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
615
+ if (lastReceivedLocation != nil) userInfo[@"location"] = lastReceivedLocation;
616
+ userInfo[@"payload"] = payload != nil ? payload : @{};
617
+ [[NSNotificationCenter defaultCenter] postNotificationName:MAURSOSNotification
618
+ object:self
619
+ userInfo:userInfo];
620
+ }
621
+
242
622
  /**
243
623
  * toggle between foreground and background operation mode
244
624
  */
@@ -358,6 +738,26 @@ FMDBLogger *sqliteLogger;
358
738
  return [locationDAO getLocationsForSync];
359
739
  }
360
740
 
741
+ - (void) startSession
742
+ {
743
+ [[MAURSessionLocationDAO sharedInstance] startSession];
744
+ }
745
+
746
+ - (NSArray<MAURLocation*>*) getSessionLocations
747
+ {
748
+ return [[MAURSessionLocationDAO sharedInstance] getSessionLocations];
749
+ }
750
+
751
+ - (void) clearSession
752
+ {
753
+ [[MAURSessionLocationDAO sharedInstance] clearSession];
754
+ }
755
+
756
+ - (NSInteger) getSessionLocationsCount
757
+ {
758
+ return [[MAURSessionLocationDAO sharedInstance] getSessionLocationsCount];
759
+ }
760
+
361
761
  - (BOOL) deleteLocation:(NSNumber*)locationId error:(NSError * __autoreleasing *)outError
362
762
  {
363
763
  MAURSQLiteLocationDAO* locationDAO = [MAURSQLiteLocationDAO sharedInstance];
@@ -544,7 +944,13 @@ FMDBLogger *sqliteLogger;
544
944
  {
545
945
  DDLogDebug(@"%@ #onLocationChanged %@", TAG, location);
546
946
  stationaryLocation = nil;
547
-
947
+ lastReceivedLocation = location; // v3.5 Phase 4: cached for heartbeat payload
948
+
949
+ // v4.0 Phase 6: feed driver-insights state machine.
950
+ [self drivingDetectorFeed:location];
951
+ // v4.2 Phase 8: keep sensor pipeline aware of the latest fix.
952
+ sensorFusion.lastLocation = location;
953
+
548
954
  [postLocationTask add:location];
549
955
 
550
956
  MAURConfig *config = [self getConfig];
@@ -12,11 +12,22 @@
12
12
 
13
13
  @class MAURBackgroundSync;
14
14
 
15
+ // v3.5 Phase 4: notification names for sync events. The plugin layer observes them
16
+ // via NSNotificationCenter to forward into JS as syncStart / syncSuccess / syncError / syncProgress.
17
+ extern NSString * _Nonnull const MAURBackgroundSyncDidStartNotification;
18
+ extern NSString * _Nonnull const MAURBackgroundSyncDidSucceedNotification;
19
+ extern NSString * _Nonnull const MAURBackgroundSyncDidFailNotification;
20
+ extern NSString * _Nonnull const MAURBackgroundSyncDidProgressNotification;
21
+
15
22
  @protocol MAURBackgroundSyncDelegate <NSObject>
16
23
 
17
24
  @optional
18
25
  - (void)backgroundSyncRequestedAbortUpdates:(MAURBackgroundSync * _Nonnull)task;
19
26
  - (void)backgroundSyncHttpAuthorizationUpdates:(MAURBackgroundSync * _Nonnull)task;
27
+ // v3.5 Phase 4
28
+ - (void)backgroundSyncStarted:(MAURBackgroundSync * _Nonnull)task;
29
+ - (void)backgroundSyncSucceeded:(MAURBackgroundSync * _Nonnull)task locationsSent:(NSInteger)locationsSent;
30
+ - (void)backgroundSyncFailed:(MAURBackgroundSync * _Nonnull)task httpStatus:(NSInteger)httpStatus message:(NSString * _Nullable)message;
20
31
 
21
32
  @end
22
33
 
@@ -27,6 +38,7 @@
27
38
  - (instancetype) init;
28
39
  - (NSString*) status;
29
40
  - (void) sync:(NSString * _Nonnull)url withTemplate:(id)locationTemplate withHttpHeaders:(NSMutableDictionary * _Nullable)httpHeaders;
41
+ - (void) sync:(NSString * _Nonnull)url withTemplate:(id)locationTemplate withHttpHeaders:(NSMutableDictionary * _Nullable)httpHeaders withMethod:(NSString * _Nullable)method;
30
42
  - (void) cancel;
31
43
 
32
44
  @end