@josuelmm/cordova-background-geolocation 4.2.2 → 4.5.1
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 +213 -0
- package/HISTORY.md +73 -0
- package/README.md +45 -74
- package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +24 -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 +6 -3
- package/android/common/src/main/java/com/marianhello/bgloc/Config.java +65 -1
- package/android/common/src/main/java/com/marianhello/bgloc/PostLocationTask.java +1 -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 +205 -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/DistanceFilterLocationProvider.java +23 -8
- 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/MAURBackgroundGeolocationFacade.m +111 -5
- package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +20 -0
- package/ios/common/BackgroundGeolocation/MAURConfig.h +2 -0
- package/ios/common/BackgroundGeolocation/MAURConfig.m +16 -2
- package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +3 -0
- package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +3 -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/MAURPostLocationTask.h +9 -0
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +59 -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 +36 -1
- package/plugin.xml +3 -2
- package/www/BackgroundGeolocation.d.ts +114 -3
- package/www/BackgroundGeolocation.js +11 -4
- package/CLAUDE.md +0 -56
- 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/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
|
@@ -188,12 +188,22 @@ public class BatchManager {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
private void writeValue(Object value) throws IOException {
|
|
191
|
-
if (value
|
|
191
|
+
if (value == null || value == JSONObject.NULL) {
|
|
192
|
+
writer.nullValue();
|
|
193
|
+
} else if (value instanceof String ) {
|
|
192
194
|
writer.value((String) value);
|
|
193
195
|
} else if (value instanceof Map) {
|
|
194
196
|
writeMap((Map) value);
|
|
195
197
|
} else if (value instanceof List) {
|
|
196
198
|
writeList((List) value);
|
|
199
|
+
// v4.5.1 — handle JSONArray / JSONObject so that placeholders that resolve to
|
|
200
|
+
// structured data (@events → JSONArray of events) are written as real JSON arrays /
|
|
201
|
+
// objects, not as escaped strings. Without this, a fix coming out of the sync queue
|
|
202
|
+
// serialised "events" as "[{\"type\":\"hardBrake\"}]" literal.
|
|
203
|
+
} else if (value instanceof org.json.JSONArray) {
|
|
204
|
+
writeJsonArray((org.json.JSONArray) value);
|
|
205
|
+
} else if (value instanceof org.json.JSONObject) {
|
|
206
|
+
writeJsonObject((org.json.JSONObject) value);
|
|
197
207
|
} else if (Integer.class.isInstance(value)) {
|
|
198
208
|
writer.value((Integer) value);
|
|
199
209
|
} else if (Double.class.isInstance(value)) {
|
|
@@ -204,13 +214,44 @@ public class BatchManager {
|
|
|
204
214
|
writer.value((Long) value);
|
|
205
215
|
} else if (Boolean.class.isInstance(value)) {
|
|
206
216
|
writer.value((Boolean) value);
|
|
207
|
-
} else if (value == JSONObject.NULL) {
|
|
208
|
-
writer.nullValue();
|
|
209
217
|
} else {
|
|
210
218
|
writer.value(String.valueOf(value));
|
|
211
219
|
}
|
|
212
220
|
}
|
|
213
221
|
|
|
222
|
+
private void writeJsonArray(org.json.JSONArray arr) throws IOException {
|
|
223
|
+
writer.beginArray();
|
|
224
|
+
for (int i = 0; i < arr.length(); i++) {
|
|
225
|
+
Object v = arr.opt(i);
|
|
226
|
+
writeValue(v == null ? JSONObject.NULL : v);
|
|
227
|
+
}
|
|
228
|
+
writer.endArray();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private void writeJsonObject(org.json.JSONObject obj) throws IOException {
|
|
232
|
+
writer.beginObject();
|
|
233
|
+
Iterator<String> keys = obj.keys();
|
|
234
|
+
while (keys.hasNext()) {
|
|
235
|
+
String k = keys.next();
|
|
236
|
+
writer.name(k);
|
|
237
|
+
writeValue(obj.opt(k));
|
|
238
|
+
}
|
|
239
|
+
writer.endObject();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** v4.5.1 — resolve a template string. If it is a placeholder ("@foo") and the location
|
|
243
|
+
* has no value, return JSONObject.NULL so the writer emits `null` instead of the literal
|
|
244
|
+
* "@foo" string. */
|
|
245
|
+
private Object resolveTemplateValue(Object value) {
|
|
246
|
+
if (value instanceof String) {
|
|
247
|
+
String s = (String) value;
|
|
248
|
+
Object resolved = location.getValueForKey(s);
|
|
249
|
+
if (resolved != null) return resolved;
|
|
250
|
+
if (s.startsWith("@")) return JSONObject.NULL;
|
|
251
|
+
}
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
|
|
214
255
|
public void writeMap(Map values) throws IOException {
|
|
215
256
|
writer.beginObject();
|
|
216
257
|
Iterator<?> it = values.entrySet().iterator();
|
|
@@ -218,12 +259,8 @@ public class BatchManager {
|
|
|
218
259
|
Map.Entry<String, Object> pair = (Map.Entry) it.next();
|
|
219
260
|
String key = pair.getKey();
|
|
220
261
|
Object value = pair.getValue();
|
|
221
|
-
Object locationValue = null;
|
|
222
|
-
if (value instanceof String) {
|
|
223
|
-
locationValue = location.getValueForKey((String)value);
|
|
224
|
-
}
|
|
225
262
|
writer.name(key);
|
|
226
|
-
writeValue(
|
|
263
|
+
writeValue(resolveTemplateValue(value));
|
|
227
264
|
}
|
|
228
265
|
writer.endObject();
|
|
229
266
|
}
|
|
@@ -233,11 +270,7 @@ public class BatchManager {
|
|
|
233
270
|
Iterator<?> it = values.iterator();
|
|
234
271
|
while (it.hasNext()) {
|
|
235
272
|
Object value = it.next();
|
|
236
|
-
|
|
237
|
-
if (value instanceof String) {
|
|
238
|
-
locationValue = location.getValueForKey((String) value);
|
|
239
|
-
}
|
|
240
|
-
writeValue(locationValue != null ? locationValue : value);
|
|
273
|
+
writeValue(resolveTemplateValue(value));
|
|
241
274
|
}
|
|
242
275
|
writer.endArray();
|
|
243
276
|
}
|
|
@@ -135,6 +135,28 @@ static NSString * const TAG = @"CDVBackgroundGeolocation";
|
|
|
135
135
|
callbackId:command.callbackId];
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// v4.5: runtime permission helpers — paridad de API con Android. iOS no expone gates
|
|
139
|
+
// separados para background location / activity recognition / notifications, así que
|
|
140
|
+
// resolvemos siempre con notRequired:YES.
|
|
141
|
+
- (void) requestBackgroundLocationPermission:(CDVInvokedUrlCommand *)command
|
|
142
|
+
{
|
|
143
|
+
[self sendNotRequiredPermissionResult:command];
|
|
144
|
+
}
|
|
145
|
+
- (void) requestActivityRecognitionPermission:(CDVInvokedUrlCommand *)command
|
|
146
|
+
{
|
|
147
|
+
[self sendNotRequiredPermissionResult:command];
|
|
148
|
+
}
|
|
149
|
+
- (void) requestNotificationPermission:(CDVInvokedUrlCommand *)command
|
|
150
|
+
{
|
|
151
|
+
[self sendNotRequiredPermissionResult:command];
|
|
152
|
+
}
|
|
153
|
+
- (void) sendNotRequiredPermissionResult:(CDVInvokedUrlCommand *)command
|
|
154
|
+
{
|
|
155
|
+
NSDictionary *r = @{ @"granted": @YES, @"notRequired": @YES };
|
|
156
|
+
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:r]
|
|
157
|
+
callbackId:command.callbackId];
|
|
158
|
+
}
|
|
159
|
+
|
|
138
160
|
// v4.1 GPS-derived sensor-like events
|
|
139
161
|
- (void) sendDrivingEventN:(NSString *)name note:(NSNotification *)note
|
|
140
162
|
{
|
|
@@ -513,7 +535,7 @@ static NSString * const TAG = @"CDVBackgroundGeolocation";
|
|
|
513
535
|
- (void) getPluginVersion:(CDVInvokedUrlCommand*)command
|
|
514
536
|
{
|
|
515
537
|
NSLog(@"%@ #%@", TAG, @"getPluginVersion");
|
|
516
|
-
NSString *version = @"4.
|
|
538
|
+
NSString *version = @"4.5.1"; // keep in sync with plugin.xml and Android PLUGIN_VERSION
|
|
517
539
|
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:version];
|
|
518
540
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
519
541
|
}
|
|
@@ -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)}];
|
|
@@ -917,7 +1017,10 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
|
|
|
917
1017
|
{
|
|
918
1018
|
DDLogDebug(@"%@ #onStationaryChanged", TAG);
|
|
919
1019
|
stationaryLocation = location;
|
|
920
|
-
|
|
1020
|
+
|
|
1021
|
+
// v4.5.1 — enrichment moved into MAURPostLocationTask.add: so pending events / battery
|
|
1022
|
+
// land on the post-transform instance (and survive transforms that return new instances).
|
|
1023
|
+
|
|
921
1024
|
[postLocationTask add:location];
|
|
922
1025
|
|
|
923
1026
|
MAURConfig *config = [self getConfig];
|
|
@@ -946,10 +1049,13 @@ NSString * const MAURPhoneUsageWhileDrivingNotification = @"MAURPhoneUsageWhileD
|
|
|
946
1049
|
stationaryLocation = nil;
|
|
947
1050
|
lastReceivedLocation = location; // v3.5 Phase 4: cached for heartbeat payload
|
|
948
1051
|
|
|
949
|
-
// v4.0 Phase 6: feed driver-insights state machine.
|
|
1052
|
+
// v4.0 Phase 6: feed driver-insights state machine. Listener may attach events to `location`.
|
|
950
1053
|
[self drivingDetectorFeed:location];
|
|
951
1054
|
// v4.2 Phase 8: keep sensor pipeline aware of the latest fix.
|
|
952
1055
|
sensorFusion.lastLocation = location;
|
|
1056
|
+
// v4.5.1 — pending events drain + battery snapshot moved into MAURPostLocationTask.add:
|
|
1057
|
+
// so they run AFTER any user-supplied locationTransform. The previous order lost them
|
|
1058
|
+
// whenever the transform returned nil.
|
|
953
1059
|
|
|
954
1060
|
[postLocationTask add:location];
|
|
955
1061
|
|
|
@@ -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,8 @@ 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;
|
|
47
49
|
@property NSNumber *_saveBatteryOnBackground;
|
|
48
50
|
@property NSNumber *maxLocations;
|
|
49
51
|
@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, _saveBatteryOnBackground, maxLocations, _pauseLocationUpdates, locationProvider, _template;
|
|
16
16
|
|
|
17
17
|
-(instancetype) initWithDefaults {
|
|
18
18
|
self = [super init];
|
|
@@ -117,6 +117,9 @@
|
|
|
117
117
|
if ([config[@"drivingEvents"] isKindOfClass:[NSDictionary class]]) {
|
|
118
118
|
instance.drivingEvents = config[@"drivingEvents"];
|
|
119
119
|
}
|
|
120
|
+
if (isNotNull(config[@"includeBattery"])) {
|
|
121
|
+
instance.includeBattery = config[@"includeBattery"];
|
|
122
|
+
}
|
|
120
123
|
if (isNotNull(config[@"saveBatteryOnBackground"])) {
|
|
121
124
|
instance._saveBatteryOnBackground = config[@"saveBatteryOnBackground"];
|
|
122
125
|
}
|
|
@@ -215,6 +218,9 @@
|
|
|
215
218
|
if (newConfig.drivingEvents != nil) {
|
|
216
219
|
merger.drivingEvents = newConfig.drivingEvents;
|
|
217
220
|
}
|
|
221
|
+
if (newConfig.includeBattery != nil) {
|
|
222
|
+
merger.includeBattery = newConfig.includeBattery;
|
|
223
|
+
}
|
|
218
224
|
if ([newConfig hasSaveBatteryOnBackground]) {
|
|
219
225
|
merger._saveBatteryOnBackground = newConfig._saveBatteryOnBackground;
|
|
220
226
|
}
|
|
@@ -259,6 +265,7 @@
|
|
|
259
265
|
copy.heartbeatInterval = heartbeatInterval;
|
|
260
266
|
copy.mockLocationPolicy = mockLocationPolicy;
|
|
261
267
|
copy.drivingEvents = drivingEvents;
|
|
268
|
+
copy.includeBattery = includeBattery;
|
|
262
269
|
copy._saveBatteryOnBackground = _saveBatteryOnBackground;
|
|
263
270
|
copy.maxLocations = maxLocations;
|
|
264
271
|
copy._pauseLocationUpdates = _pauseLocationUpdates;
|
|
@@ -512,9 +519,15 @@
|
|
|
512
519
|
@"altitude": @"@altitude",
|
|
513
520
|
@"latitude": @"@latitude",
|
|
514
521
|
@"longitude": @"@longitude",
|
|
515
|
-
@"provider": @"provider",
|
|
522
|
+
@"provider": @"@provider", // v4.5.1 — was literal "provider" (bug)
|
|
516
523
|
@"locationProvider": @"@locationProvider",
|
|
517
524
|
@"radius": @"@radius",
|
|
525
|
+
// v4.5.1 — README promete events/battery/isCharging en payload default. Sin esto,
|
|
526
|
+
// el template default que se usa siempre que la app no configura postTemplate omitía
|
|
527
|
+
// estos campos al serializar via toResultFromTemplate.
|
|
528
|
+
@"events": @"@events",
|
|
529
|
+
@"battery": @"@battery",
|
|
530
|
+
@"isCharging": @"@isCharging",
|
|
518
531
|
};
|
|
519
532
|
}
|
|
520
533
|
|
|
@@ -576,6 +589,7 @@
|
|
|
576
589
|
if (self.heartbeatInterval != nil) [dict setObject:self.heartbeatInterval forKey:@"heartbeatInterval"];
|
|
577
590
|
if (self.mockLocationPolicy != nil) [dict setObject:self.mockLocationPolicy forKey:@"mockLocationPolicy"];
|
|
578
591
|
if (self.drivingEvents != nil) [dict setObject:self.drivingEvents forKey:@"drivingEvents"];
|
|
592
|
+
if (self.includeBattery != nil) [dict setObject:self.includeBattery forKey:@"includeBattery"];
|
|
579
593
|
if ([self hasStationaryRadius]) [dict setObject:self.stationaryRadius forKey:@"stationaryRadius"];
|
|
580
594
|
if ([self hasDistanceFilter]) [dict setObject:self.distanceFilter forKey:@"distanceFilter"];
|
|
581
595
|
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];
|
|
@@ -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
|
|