@josuelmm/cordova-background-geolocation 3.2.0 → 4.2.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 +4 -0
- package/CHANGELOG.md +290 -0
- package/CLAUDE.md +56 -0
- package/HISTORY.md +125 -0
- package/README.md +189 -4
- package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +90 -0
- package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +310 -1
- package/android/common/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +127 -0
- package/android/common/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +27 -11
- package/android/common/src/main/java/com/marianhello/bgloc/Config.java +268 -0
- package/android/common/src/main/java/com/marianhello/bgloc/HttpPostService.java +86 -26
- package/android/common/src/main/java/com/marianhello/bgloc/PluginDelegate.java +26 -0
- package/android/common/src/main/java/com/marianhello/bgloc/PostLocationTask.java +42 -5
- package/android/common/src/main/java/com/marianhello/bgloc/driving/DrivingEventsDetector.java +265 -0
- package/android/common/src/main/java/com/marianhello/bgloc/http/UrlTemplateResolver.java +115 -0
- package/android/common/src/main/java/com/marianhello/bgloc/oem/BatteryOemHelper.java +214 -0
- package/android/common/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +13 -9
- package/android/common/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +29 -40
- package/android/common/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +14 -34
- package/android/common/src/main/java/com/marianhello/bgloc/sensor/SensorFusionDetector.java +199 -0
- package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +305 -6
- package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +14 -2
- package/android/common/src/main/java/com/marianhello/bgloc/sync/SyncAdapter.java +50 -3
- package/android/dependencies.gradle +0 -3
- package/angular/background-geolocation-events.ts +21 -0
- package/angular/background-geolocation.service.ts +63 -0
- package/angular/dist/background-geolocation-events.d.ts +18 -1
- package/angular/dist/background-geolocation.service.d.ts +36 -0
- package/angular/dist/esm2022/background-geolocation-events.mjs +22 -1
- package/angular/dist/esm2022/background-geolocation.service.mjs +35 -1
- package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs +55 -0
- package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs.map +1 -1
- package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +312 -1
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +22 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +400 -15
- package/ios/common/BackgroundGeolocation/MAURBackgroundSync.h +12 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +83 -5
- package/ios/common/BackgroundGeolocation/MAURConfig.h +15 -0
- package/ios/common/BackgroundGeolocation/MAURConfig.m +100 -3
- package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +29 -2
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +4 -0
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +97 -44
- package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.h +41 -0
- package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.m +137 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.h +31 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
- package/package.json +41 -1
- package/plugin.xml +19 -8
- package/www/BackgroundGeolocation.d.ts +517 -3
- package/www/BackgroundGeolocation.js +54 -1
- package/RELEASE.MD +0 -16
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
#import "MAURUncaughtExceptionLogger.h"
|
|
30
30
|
#import "MAURPostLocationTask.h"
|
|
31
31
|
#import "INTULocationManager.h"
|
|
32
|
+
#import "MAURSensorFusionDetector.h"
|
|
32
33
|
|
|
33
34
|
// error messages
|
|
34
35
|
#define CONFIGURE_ERROR_MSG "Configuration error."
|
|
@@ -46,21 +47,63 @@ static NSString * const TAG = @"BgGeo";
|
|
|
46
47
|
|
|
47
48
|
FMDBLogger *sqliteLogger;
|
|
48
49
|
|
|
49
|
-
@interface MAURBackgroundGeolocationFacade () <MAURProviderDelegate, MAURPostLocationTaskDelegate>
|
|
50
|
+
@interface MAURBackgroundGeolocationFacade () <MAURProviderDelegate, MAURPostLocationTaskDelegate, MAURSensorFusionListener>
|
|
50
51
|
@end
|
|
51
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
|
+
|
|
52
72
|
@implementation MAURBackgroundGeolocationFacade {
|
|
53
73
|
BOOL isStarted;
|
|
54
74
|
MAUROperationalMode operationMode;
|
|
55
|
-
|
|
75
|
+
|
|
56
76
|
UILocalNotification *localNotification;
|
|
57
|
-
|
|
77
|
+
|
|
58
78
|
// configurable options
|
|
59
79
|
MAURConfig *_config;
|
|
60
|
-
|
|
80
|
+
|
|
61
81
|
MAURLocation *stationaryLocation;
|
|
82
|
+
MAURLocation *lastReceivedLocation; // v3.5 Phase 4: heartbeat payload
|
|
83
|
+
NSTimer *heartbeatTimer; // v3.5 Phase 4
|
|
62
84
|
MAURAbstractLocationProvider<MAURLocationProvider> *locationProvider;
|
|
63
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;
|
|
64
107
|
}
|
|
65
108
|
|
|
66
109
|
|
|
@@ -136,25 +179,41 @@ FMDBLogger *sqliteLogger;
|
|
|
136
179
|
if (isStarted) {
|
|
137
180
|
// Note: CLLocationManager must be created on a thread with an active run loop (main thread)
|
|
138
181
|
[self runOnMainThread:^{
|
|
139
|
-
|
|
182
|
+
|
|
140
183
|
// requesting new provider
|
|
141
184
|
if (![currentConfig.locationProvider isEqual:_config.locationProvider]) {
|
|
142
185
|
[locationProvider onDestroy]; // destroy current provider
|
|
143
186
|
locationProvider = [self getProvider:_config.locationProvider.intValue error:&error];
|
|
144
187
|
}
|
|
145
|
-
|
|
188
|
+
|
|
146
189
|
if (locationProvider == nil) {
|
|
147
190
|
return;
|
|
148
191
|
}
|
|
149
|
-
|
|
192
|
+
|
|
150
193
|
// trap configuration errors
|
|
151
194
|
if (![locationProvider onConfigure:_config error:&error]) {
|
|
152
195
|
return;
|
|
153
196
|
}
|
|
154
|
-
|
|
197
|
+
|
|
155
198
|
isStarted = [locationProvider onStart:&error];
|
|
156
199
|
locationProvider.delegate = self;
|
|
157
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
|
+
}
|
|
158
217
|
}
|
|
159
218
|
|
|
160
219
|
if (error != nil) {
|
|
@@ -213,10 +272,16 @@ FMDBLogger *sqliteLogger;
|
|
|
213
272
|
if (outError != nil) {
|
|
214
273
|
*outError = error;
|
|
215
274
|
}
|
|
216
|
-
|
|
275
|
+
|
|
217
276
|
return NO;
|
|
218
277
|
}
|
|
219
|
-
|
|
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
|
+
|
|
220
285
|
return isStarted;
|
|
221
286
|
}
|
|
222
287
|
|
|
@@ -226,20 +291,334 @@ FMDBLogger *sqliteLogger;
|
|
|
226
291
|
- (BOOL) stop:(NSError * __autoreleasing *)outError
|
|
227
292
|
{
|
|
228
293
|
DDLogInfo(@"%@ #stop", TAG);
|
|
229
|
-
|
|
294
|
+
|
|
230
295
|
if (!isStarted) {
|
|
231
296
|
return YES;
|
|
232
297
|
}
|
|
233
|
-
|
|
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
|
+
|
|
234
307
|
[postLocationTask stop];
|
|
235
|
-
|
|
308
|
+
|
|
236
309
|
[self runOnMainThread:^{
|
|
237
310
|
isStarted = ![locationProvider onStop:outError];
|
|
238
311
|
}];
|
|
239
|
-
|
|
312
|
+
|
|
240
313
|
return isStarted;
|
|
241
314
|
}
|
|
242
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
|
+
|
|
243
622
|
/**
|
|
244
623
|
* toggle between foreground and background operation mode
|
|
245
624
|
*/
|
|
@@ -565,7 +944,13 @@ FMDBLogger *sqliteLogger;
|
|
|
565
944
|
{
|
|
566
945
|
DDLogDebug(@"%@ #onLocationChanged %@", TAG, location);
|
|
567
946
|
stationaryLocation = nil;
|
|
568
|
-
|
|
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
|
+
|
|
569
954
|
[postLocationTask add:location];
|
|
570
955
|
|
|
571
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
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
#import "MAURLogging.h"
|
|
10
10
|
#import "MAURBackgroundSync.h"
|
|
11
11
|
#import "MAURSQLiteLocationDAO.h"
|
|
12
|
+
#import <objc/runtime.h>
|
|
13
|
+
|
|
14
|
+
NSString * const MAURBackgroundSyncDidStartNotification = @"MAURBackgroundSyncDidStart";
|
|
15
|
+
NSString * const MAURBackgroundSyncDidSucceedNotification = @"MAURBackgroundSyncDidSucceed";
|
|
16
|
+
NSString * const MAURBackgroundSyncDidFailNotification = @"MAURBackgroundSyncDidFail";
|
|
17
|
+
NSString * const MAURBackgroundSyncDidProgressNotification = @"MAURBackgroundSyncDidProgress";
|
|
12
18
|
|
|
13
19
|
@interface MAURBackgroundSync () <NSURLSessionDelegate, NSURLSessionTaskDelegate>
|
|
14
20
|
{
|
|
@@ -22,11 +28,15 @@
|
|
|
22
28
|
- (instancetype) init
|
|
23
29
|
{
|
|
24
30
|
if(!(self = [super init])) return nil;
|
|
25
|
-
|
|
31
|
+
|
|
32
|
+
// v3.5 Phase 4: previously `tasks` was never allocated; addObject/removeObject/cancel/status
|
|
33
|
+
// silently no-op'd on nil. Allocate now so cancel and status actually work.
|
|
34
|
+
tasks = [[NSMutableArray alloc] init];
|
|
35
|
+
|
|
26
36
|
NSURLSessionConfiguration *conf = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.marianhello.session"];
|
|
27
37
|
conf.allowsCellularAccess = YES;
|
|
28
38
|
urlSession = [NSURLSession sessionWithConfiguration:conf delegate:self delegateQueue:[NSOperationQueue mainQueue]];
|
|
29
|
-
|
|
39
|
+
|
|
30
40
|
return self;
|
|
31
41
|
}
|
|
32
42
|
|
|
@@ -55,6 +65,11 @@
|
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
- (void) sync:(NSString * _Nonnull)url withTemplate:(id)locationTemplate withHttpHeaders:(NSMutableDictionary * _Nullable)httpHeaders
|
|
68
|
+
{
|
|
69
|
+
[self sync:url withTemplate:locationTemplate withHttpHeaders:httpHeaders withMethod:@"POST"];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void) sync:(NSString * _Nonnull)url withTemplate:(id)locationTemplate withHttpHeaders:(NSMutableDictionary * _Nullable)httpHeaders withMethod:(NSString * _Nullable)method
|
|
58
73
|
{
|
|
59
74
|
MAURSQLiteLocationDAO* locationDAO = [MAURSQLiteLocationDAO sharedInstance];
|
|
60
75
|
NSArray *locations = [locationDAO getLocationsForSync];
|
|
@@ -77,7 +92,8 @@
|
|
|
77
92
|
uint64_t bytesTotalForThisFile = [[[NSFileManager defaultManager] attributesOfItemAtPath:jsonUrl.path error:nil] fileSize];
|
|
78
93
|
|
|
79
94
|
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
|
|
80
|
-
[
|
|
95
|
+
NSString *resolvedMethod = (method != nil && method.length > 0) ? [method uppercaseString] : @"POST";
|
|
96
|
+
[request setHTTPMethod:resolvedMethod];
|
|
81
97
|
[request setTimeoutInterval:120]; // Prevents sync from hanging indefinitely if server does not respond
|
|
82
98
|
[request setValue:[NSString stringWithFormat:@"%llu", bytesTotalForThisFile] forHTTPHeaderField:@"Content-Length"];
|
|
83
99
|
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
|
@@ -92,8 +108,18 @@
|
|
|
92
108
|
task.taskDescription = fileName;
|
|
93
109
|
[tasks addObject:task];
|
|
94
110
|
DDLogInfo(@"Started upload for %@ as task %zu/%@/%@", jsonUrl.lastPathComponent, (unsigned long)task.taskIdentifier, task.taskDescription, task);
|
|
111
|
+
|
|
112
|
+
// v3.5 Phase 4: emit syncStart now that we are about to push to the server.
|
|
113
|
+
if (self.delegate && [self.delegate respondsToSelector:@selector(backgroundSyncStarted:)]) {
|
|
114
|
+
[self.delegate backgroundSyncStarted:self];
|
|
115
|
+
}
|
|
116
|
+
[[NSNotificationCenter defaultCenter] postNotificationName:MAURBackgroundSyncDidStartNotification object:self];
|
|
117
|
+
|
|
118
|
+
// Stash count so didCompleteWithError can report it as syncSuccess payload.
|
|
119
|
+
objc_setAssociatedObject(task, "locationsSent", @([jsonArray count]), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
120
|
+
|
|
95
121
|
[task resume];
|
|
96
|
-
|
|
122
|
+
|
|
97
123
|
}
|
|
98
124
|
|
|
99
125
|
// http://stackoverflow.com/a/572623/48125
|
|
@@ -133,6 +159,23 @@ NSString *stringFromFileSize(unsigned long long theSize)
|
|
|
133
159
|
|
|
134
160
|
|
|
135
161
|
#pragma mark -
|
|
162
|
+
// v3.5 Phase 4: forward upload progress as syncProgress (0..100).
|
|
163
|
+
- (void)URLSession:(NSURLSession *)session
|
|
164
|
+
task:(NSURLSessionTask *)task
|
|
165
|
+
didSendBodyData:(int64_t)bytesSent
|
|
166
|
+
totalBytesSent:(int64_t)totalBytesSent
|
|
167
|
+
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
|
168
|
+
{
|
|
169
|
+
if (totalBytesExpectedToSend <= 0) return;
|
|
170
|
+
NSInteger progress = (NSInteger)((totalBytesSent * 100) / totalBytesExpectedToSend);
|
|
171
|
+
if (progress < 0) progress = 0;
|
|
172
|
+
if (progress > 100) progress = 100;
|
|
173
|
+
[[NSNotificationCenter defaultCenter]
|
|
174
|
+
postNotificationName:MAURBackgroundSyncDidProgressNotification
|
|
175
|
+
object:self
|
|
176
|
+
userInfo:@{@"progress": @(progress)}];
|
|
177
|
+
}
|
|
178
|
+
|
|
136
179
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
|
|
137
180
|
{
|
|
138
181
|
NSInteger statusCode = [(NSHTTPURLResponse *)task.response statusCode];
|
|
@@ -157,7 +200,7 @@ NSString *stringFromFileSize(unsigned long long theSize)
|
|
|
157
200
|
}
|
|
158
201
|
|
|
159
202
|
if (statusCode == 401)
|
|
160
|
-
{
|
|
203
|
+
{
|
|
161
204
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
162
205
|
if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncHttpAuthorizationUpdates:)])
|
|
163
206
|
{
|
|
@@ -165,6 +208,41 @@ NSString *stringFromFileSize(unsigned long long theSize)
|
|
|
165
208
|
}
|
|
166
209
|
});
|
|
167
210
|
}
|
|
211
|
+
|
|
212
|
+
// v3.5 Phase 4: emit syncSuccess / syncError.
|
|
213
|
+
NSNumber *sentNum = objc_getAssociatedObject(task, "locationsSent");
|
|
214
|
+
NSInteger locationsSent = sentNum != nil ? [sentNum integerValue] : 0;
|
|
215
|
+
BOOL isStatusOkay = (statusCode >= 200 && statusCode < 300);
|
|
216
|
+
|
|
217
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
218
|
+
if (error != nil) {
|
|
219
|
+
NSString *msg = error.localizedDescription ?: @"";
|
|
220
|
+
if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncFailed:httpStatus:message:)]) {
|
|
221
|
+
[_delegate backgroundSyncFailed:self httpStatus:0 message:msg];
|
|
222
|
+
}
|
|
223
|
+
[[NSNotificationCenter defaultCenter]
|
|
224
|
+
postNotificationName:MAURBackgroundSyncDidFailNotification
|
|
225
|
+
object:self
|
|
226
|
+
userInfo:@{@"httpStatus": @0, @"message": msg}];
|
|
227
|
+
} else if (!isStatusOkay) {
|
|
228
|
+
NSString *msg = [NSString stringWithFormat:@"HTTP %ld", (long)statusCode];
|
|
229
|
+
if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncFailed:httpStatus:message:)]) {
|
|
230
|
+
[_delegate backgroundSyncFailed:self httpStatus:statusCode message:msg];
|
|
231
|
+
}
|
|
232
|
+
[[NSNotificationCenter defaultCenter]
|
|
233
|
+
postNotificationName:MAURBackgroundSyncDidFailNotification
|
|
234
|
+
object:self
|
|
235
|
+
userInfo:@{@"httpStatus": @(statusCode), @"message": msg}];
|
|
236
|
+
} else {
|
|
237
|
+
if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncSucceeded:locationsSent:)]) {
|
|
238
|
+
[_delegate backgroundSyncSucceeded:self locationsSent:locationsSent];
|
|
239
|
+
}
|
|
240
|
+
[[NSNotificationCenter defaultCenter]
|
|
241
|
+
postNotificationName:MAURBackgroundSyncDidSucceedNotification
|
|
242
|
+
object:self
|
|
243
|
+
userInfo:@{@"sent": @(locationsSent)}];
|
|
244
|
+
}
|
|
245
|
+
});
|
|
168
246
|
}
|
|
169
247
|
|
|
170
248
|
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
|