@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.
- package/.npmignore +4 -0
- package/CHANGELOG.md +313 -0
- package/CLAUDE.md +56 -0
- package/HISTORY.md +124 -0
- package/README.md +198 -6
- 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 +362 -1
- package/android/common/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +153 -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 +48 -5
- package/android/common/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +8 -1
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
- 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 +310 -7
- 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 +91 -0
- package/angular/dist/background-geolocation-events.d.ts +18 -1
- package/angular/dist/background-geolocation.service.d.ts +40 -0
- package/angular/dist/esm2022/background-geolocation-events.mjs +22 -1
- package/angular/dist/esm2022/background-geolocation.service.mjs +47 -1
- package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs +67 -0
- package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs.map +1 -1
- package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.h +4 -0
- package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +352 -1
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +26 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +421 -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/MAURGeolocationOpenHelper.m +12 -3
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +4 -0
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +102 -44
- package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.h +41 -0
- package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.m +137 -0
- package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.h +29 -0
- package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.m +31 -0
- package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.h +25 -0
- package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.m +153 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.h +31 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
- package/package.json +36 -1
- package/plugin.xml +26 -8
- package/www/BackgroundGeolocation.d.ts +559 -3
- package/www/BackgroundGeolocation.js +78 -1
- package/RELEASE.MD +0 -16
|
@@ -21,6 +21,7 @@ import com.marianhello.bgloc.PluginDelegate;
|
|
|
21
21
|
import com.marianhello.bgloc.PluginException;
|
|
22
22
|
import com.marianhello.bgloc.cordova.ConfigMapper;
|
|
23
23
|
import com.marianhello.bgloc.cordova.PluginRegistry;
|
|
24
|
+
import com.marianhello.bgloc.oem.BatteryOemHelper;
|
|
24
25
|
import com.marianhello.bgloc.cordova.headless.JsEvaluatorTaskRunner;
|
|
25
26
|
import com.marianhello.bgloc.data.BackgroundActivity;
|
|
26
27
|
import com.marianhello.bgloc.data.BackgroundLocation;
|
|
@@ -73,10 +74,22 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
|
|
|
73
74
|
public static final String ACTION_FORCE_SYNC = "forceSync";
|
|
74
75
|
public static final String ACTION_CLEAR_SYNC = "clearSync";
|
|
75
76
|
public static final String ACTION_GET_PENDING_SYNC_COUNT = "getPendingSyncCount";
|
|
77
|
+
public static final String ACTION_START_SESSION = "startSession";
|
|
78
|
+
public static final String ACTION_GET_SESSION_LOCATIONS = "getSessionLocations";
|
|
79
|
+
public static final String ACTION_CLEAR_SESSION = "clearSession";
|
|
80
|
+
public static final String ACTION_GET_SESSION_LOCATIONS_COUNT = "getSessionLocationsCount";
|
|
76
81
|
public static final String ACTION_GET_PLUGIN_VERSION = "getPluginVersion";
|
|
82
|
+
public static final String ACTION_GET_DIAGNOSTICS = "getDiagnostics";
|
|
83
|
+
// v3.6 Phase 5
|
|
84
|
+
public static final String ACTION_IS_IGNORING_BATTERY_OPT = "isIgnoringBatteryOptimizations";
|
|
85
|
+
public static final String ACTION_REQUEST_IGNORE_BATTERY_OPT = "requestIgnoreBatteryOptimizations";
|
|
86
|
+
public static final String ACTION_OPEN_BATTERY_SETTINGS = "openBatterySettings";
|
|
87
|
+
public static final String ACTION_OPEN_AUTOSTART_SETTINGS = "openAutoStartSettings";
|
|
88
|
+
public static final String ACTION_GET_MANUFACTURER_HELP = "getManufacturerHelp";
|
|
89
|
+
public static final String ACTION_TRIGGER_SOS = "triggerSOS";
|
|
77
90
|
|
|
78
91
|
/** Plugin version; keep in sync with plugin.xml. */
|
|
79
|
-
public static final String PLUGIN_VERSION = "
|
|
92
|
+
public static final String PLUGIN_VERSION = "4.2.0";
|
|
80
93
|
|
|
81
94
|
private BackgroundGeolocationFacade facade;
|
|
82
95
|
|
|
@@ -399,14 +412,197 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
|
|
|
399
412
|
}
|
|
400
413
|
});
|
|
401
414
|
return true;
|
|
415
|
+
} else if (ACTION_START_SESSION.equals(action)) {
|
|
416
|
+
runOnWebViewThread(new Runnable() {
|
|
417
|
+
@Override
|
|
418
|
+
public void run() {
|
|
419
|
+
facade.startSession();
|
|
420
|
+
callbackContext.success();
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
return true;
|
|
424
|
+
} else if (ACTION_GET_SESSION_LOCATIONS.equals(action)) {
|
|
425
|
+
runOnWebViewThread(new Runnable() {
|
|
426
|
+
@Override
|
|
427
|
+
public void run() {
|
|
428
|
+
try {
|
|
429
|
+
callbackContext.success(getSessionLocations());
|
|
430
|
+
} catch (JSONException e) {
|
|
431
|
+
callbackContext.sendPluginResult(ErrorPluginResult.from("getSessionLocations failed", e, PluginException.JSON_ERROR));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
return true;
|
|
436
|
+
} else if (ACTION_CLEAR_SESSION.equals(action)) {
|
|
437
|
+
runOnWebViewThread(new Runnable() {
|
|
438
|
+
@Override
|
|
439
|
+
public void run() {
|
|
440
|
+
facade.clearSession();
|
|
441
|
+
callbackContext.success();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
return true;
|
|
445
|
+
} else if (ACTION_GET_SESSION_LOCATIONS_COUNT.equals(action)) {
|
|
446
|
+
runOnWebViewThread(new Runnable() {
|
|
447
|
+
@Override
|
|
448
|
+
public void run() {
|
|
449
|
+
try {
|
|
450
|
+
int count = facade.getSessionLocationsCount();
|
|
451
|
+
callbackContext.success(count);
|
|
452
|
+
} catch (Exception e) {
|
|
453
|
+
callbackContext.sendPluginResult(ErrorPluginResult.from("getSessionLocationsCount failed", e, PluginException.SERVICE_ERROR));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
return true;
|
|
402
458
|
} else if (ACTION_GET_PLUGIN_VERSION.equals(action)) {
|
|
403
459
|
callbackContext.success(PLUGIN_VERSION);
|
|
404
460
|
return true;
|
|
461
|
+
} else if (ACTION_GET_DIAGNOSTICS.equals(action)) {
|
|
462
|
+
runOnWebViewThread(new Runnable() {
|
|
463
|
+
@Override
|
|
464
|
+
public void run() {
|
|
465
|
+
try {
|
|
466
|
+
callbackContext.success(buildDiagnostics());
|
|
467
|
+
} catch (Exception e) {
|
|
468
|
+
callbackContext.sendPluginResult(ErrorPluginResult.from("getDiagnostics failed", e, PluginException.SERVICE_ERROR));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
return true;
|
|
473
|
+
} else if (ACTION_IS_IGNORING_BATTERY_OPT.equals(action)) {
|
|
474
|
+
Context ctx = cordova.getActivity().getApplicationContext();
|
|
475
|
+
callbackContext.success(BatteryOemHelper.isIgnoringBatteryOptimizations(ctx) ? 1 : 0);
|
|
476
|
+
return true;
|
|
477
|
+
} else if (ACTION_REQUEST_IGNORE_BATTERY_OPT.equals(action)) {
|
|
478
|
+
BatteryOemHelper.requestIgnoreBatteryOptimizations(cordova.getActivity());
|
|
479
|
+
// Resolve with the (possibly unchanged) current state; the user accepts the dialog asynchronously.
|
|
480
|
+
Context ctx = cordova.getActivity().getApplicationContext();
|
|
481
|
+
callbackContext.success(BatteryOemHelper.isIgnoringBatteryOptimizations(ctx) ? 1 : 0);
|
|
482
|
+
return true;
|
|
483
|
+
} else if (ACTION_OPEN_BATTERY_SETTINGS.equals(action)) {
|
|
484
|
+
BatteryOemHelper.openBatterySettings(cordova.getActivity());
|
|
485
|
+
callbackContext.success();
|
|
486
|
+
return true;
|
|
487
|
+
} else if (ACTION_OPEN_AUTOSTART_SETTINGS.equals(action)) {
|
|
488
|
+
try {
|
|
489
|
+
callbackContext.success(BatteryOemHelper.openAutoStartSettings(cordova.getActivity()));
|
|
490
|
+
} catch (Exception e) {
|
|
491
|
+
callbackContext.sendPluginResult(ErrorPluginResult.from("openAutoStartSettings failed", e, PluginException.SERVICE_ERROR));
|
|
492
|
+
}
|
|
493
|
+
return true;
|
|
494
|
+
} else if (ACTION_GET_MANUFACTURER_HELP.equals(action)) {
|
|
495
|
+
try {
|
|
496
|
+
callbackContext.success(BatteryOemHelper.getManufacturerHelp());
|
|
497
|
+
} catch (Exception e) {
|
|
498
|
+
callbackContext.sendPluginResult(ErrorPluginResult.from("getManufacturerHelp failed", e, PluginException.SERVICE_ERROR));
|
|
499
|
+
}
|
|
500
|
+
return true;
|
|
501
|
+
} else if (ACTION_TRIGGER_SOS.equals(action)) {
|
|
502
|
+
try {
|
|
503
|
+
JSONObject payload = data.optJSONObject(0);
|
|
504
|
+
facade.triggerSOS(payload);
|
|
505
|
+
callbackContext.success();
|
|
506
|
+
} catch (Exception e) {
|
|
507
|
+
callbackContext.sendPluginResult(ErrorPluginResult.from("triggerSOS failed", e, PluginException.SERVICE_ERROR));
|
|
508
|
+
}
|
|
509
|
+
return true;
|
|
405
510
|
}
|
|
406
511
|
|
|
407
512
|
return false;
|
|
408
513
|
}
|
|
409
514
|
|
|
515
|
+
/** v3.5 Phase 4: extended diagnostics. */
|
|
516
|
+
private JSONObject buildDiagnostics() throws JSONException {
|
|
517
|
+
JSONObject d = new JSONObject();
|
|
518
|
+
Context ctx = cordova.getActivity().getApplicationContext();
|
|
519
|
+
|
|
520
|
+
// Common
|
|
521
|
+
d.put("isRunning", facade.isRunning());
|
|
522
|
+
d.put("locationServicesEnabled", facade.locationServicesEnabled());
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
Config cfg = facade.getConfig();
|
|
526
|
+
if (cfg != null) {
|
|
527
|
+
d.put("startOnBoot", cfg.getStartOnBoot());
|
|
528
|
+
}
|
|
529
|
+
} catch (Exception ignored) { /* config may not be persisted yet */ }
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
d.put("pendingSyncCount", (int) Math.min(facade.getPendingSyncCount(), Integer.MAX_VALUE));
|
|
533
|
+
} catch (Exception ignored) { /* DAO might not be ready */ }
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
BackgroundLocation last = facade.getStationaryLocation();
|
|
537
|
+
// Last *received* location is closer to lastBest; we expose stationary as a fallback signal.
|
|
538
|
+
d.put("lastLocationAt", last != null ? last.getTime() : JSONObject.NULL);
|
|
539
|
+
} catch (Exception ignored) {
|
|
540
|
+
d.put("lastLocationAt", JSONObject.NULL);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Permissions
|
|
544
|
+
d.put("fineLocationGranted", hasPermission(ctx, android.Manifest.permission.ACCESS_FINE_LOCATION));
|
|
545
|
+
d.put("coarseLocationGranted", hasPermission(ctx, android.Manifest.permission.ACCESS_COARSE_LOCATION));
|
|
546
|
+
if (android.os.Build.VERSION.SDK_INT >= 29) {
|
|
547
|
+
d.put("backgroundLocationGranted", hasPermission(ctx, android.Manifest.permission.ACCESS_BACKGROUND_LOCATION));
|
|
548
|
+
} else {
|
|
549
|
+
d.put("backgroundLocationGranted", true);
|
|
550
|
+
}
|
|
551
|
+
if (android.os.Build.VERSION.SDK_INT >= 33) {
|
|
552
|
+
d.put("notificationPermissionGranted", hasPermission(ctx, "android.permission.POST_NOTIFICATIONS"));
|
|
553
|
+
} else {
|
|
554
|
+
d.put("notificationPermissionGranted", true);
|
|
555
|
+
}
|
|
556
|
+
if (android.os.Build.VERSION.SDK_INT >= 29) {
|
|
557
|
+
d.put("activityRecognitionGranted", hasPermission(ctx, "android.permission.ACTIVITY_RECOGNITION"));
|
|
558
|
+
} else {
|
|
559
|
+
d.put("activityRecognitionGranted", true);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Battery / OEM
|
|
563
|
+
d.put("batteryOptimizationIgnored", isIgnoringBatteryOptimizations(ctx));
|
|
564
|
+
d.put("manufacturer", android.os.Build.MANUFACTURER != null ? android.os.Build.MANUFACTURER : "");
|
|
565
|
+
|
|
566
|
+
// Foreground service type read from manifest (only meaningful on API 34+; reported as-is otherwise)
|
|
567
|
+
d.put("foregroundServiceType", readForegroundServiceTypeFromManifest(ctx));
|
|
568
|
+
|
|
569
|
+
return d;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private static boolean hasPermission(Context ctx, String permission) {
|
|
573
|
+
try {
|
|
574
|
+
return ctx.getPackageManager().checkPermission(permission, ctx.getPackageName())
|
|
575
|
+
== android.content.pm.PackageManager.PERMISSION_GRANTED;
|
|
576
|
+
} catch (Exception e) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private static boolean isIgnoringBatteryOptimizations(Context ctx) {
|
|
582
|
+
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) return true;
|
|
583
|
+
try {
|
|
584
|
+
android.os.PowerManager pm = (android.os.PowerManager) ctx.getSystemService(Context.POWER_SERVICE);
|
|
585
|
+
return pm != null && pm.isIgnoringBatteryOptimizations(ctx.getPackageName());
|
|
586
|
+
} catch (Exception e) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
private static int readForegroundServiceTypeFromManifest(Context ctx) {
|
|
592
|
+
if (android.os.Build.VERSION.SDK_INT < 34) return 0;
|
|
593
|
+
try {
|
|
594
|
+
android.content.ComponentName cn = new android.content.ComponentName(
|
|
595
|
+
ctx, com.marianhello.bgloc.service.LocationServiceImpl.class);
|
|
596
|
+
android.content.pm.ServiceInfo si = ctx.getPackageManager().getServiceInfo(
|
|
597
|
+
cn, android.content.pm.PackageManager.ComponentInfoFlags.of(0));
|
|
598
|
+
java.lang.reflect.Field f = android.content.pm.ServiceInfo.class.getField("foregroundServiceType");
|
|
599
|
+
Object v = f.get(si);
|
|
600
|
+
return (v instanceof Integer) ? (Integer) v : 0;
|
|
601
|
+
} catch (Throwable e) {
|
|
602
|
+
return 0;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
410
606
|
/**
|
|
411
607
|
* Called when the system is about to start resuming a previous activity.
|
|
412
608
|
*
|
|
@@ -557,6 +753,15 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
|
|
|
557
753
|
return jsonLocationsArray;
|
|
558
754
|
}
|
|
559
755
|
|
|
756
|
+
private JSONArray getSessionLocations() throws JSONException {
|
|
757
|
+
JSONArray jsonLocationsArray = new JSONArray();
|
|
758
|
+
Collection<BackgroundLocation> locations = facade.getSessionLocations();
|
|
759
|
+
for (BackgroundLocation location : locations) {
|
|
760
|
+
jsonLocationsArray.put(location.toJSONObjectWithId());
|
|
761
|
+
}
|
|
762
|
+
return jsonLocationsArray;
|
|
763
|
+
}
|
|
764
|
+
|
|
560
765
|
private JSONArray getLogs(Integer limit, int offset, String minLevel) throws Exception {
|
|
561
766
|
JSONArray jsonLogsArray = new JSONArray();
|
|
562
767
|
Collection<LogEntry> logEntries = facade.getLogEntries(limit, offset, minLevel);
|
|
@@ -637,4 +842,160 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
|
|
|
637
842
|
public void onError(PluginException e) {
|
|
638
843
|
sendError(e);
|
|
639
844
|
}
|
|
845
|
+
|
|
846
|
+
// v3.5 Phase 4: sync queue events
|
|
847
|
+
@Override
|
|
848
|
+
public void onSyncStart() {
|
|
849
|
+
sendEvent("syncStart");
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
@Override
|
|
853
|
+
public void onSyncSuccess(int locationsSent) {
|
|
854
|
+
try {
|
|
855
|
+
JSONObject payload = new JSONObject();
|
|
856
|
+
payload.put("sent", locationsSent);
|
|
857
|
+
sendEvent("syncSuccess", payload);
|
|
858
|
+
} catch (JSONException e) {
|
|
859
|
+
sendEvent("syncSuccess");
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
@Override
|
|
864
|
+
public void onSyncError(int httpStatus, String message) {
|
|
865
|
+
try {
|
|
866
|
+
JSONObject payload = new JSONObject();
|
|
867
|
+
payload.put("httpStatus", httpStatus);
|
|
868
|
+
payload.put("message", message != null ? message : "");
|
|
869
|
+
sendEvent("syncError", payload);
|
|
870
|
+
} catch (JSONException e) {
|
|
871
|
+
sendEvent("syncError");
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
@Override
|
|
876
|
+
public void onSyncProgress(int progress) {
|
|
877
|
+
sendEvent("syncProgress", Integer.valueOf(progress));
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
@Override
|
|
881
|
+
public void onHeartbeat(BackgroundLocation location) {
|
|
882
|
+
if (location == null) {
|
|
883
|
+
sendEvent("heartbeat");
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
try {
|
|
887
|
+
sendEvent("heartbeat", location.toJSONObjectWithId());
|
|
888
|
+
} catch (JSONException e) {
|
|
889
|
+
sendEvent("heartbeat");
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// v4.0 Phase 6 — driver-insight events
|
|
894
|
+
@Override
|
|
895
|
+
public void onTripStart(BackgroundLocation location) {
|
|
896
|
+
sendLocationEvent("tripStart", location);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
@Override
|
|
900
|
+
public void onTripEnd(BackgroundLocation location, double distance, long durationMs) {
|
|
901
|
+
try {
|
|
902
|
+
JSONObject p = new JSONObject();
|
|
903
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
904
|
+
p.put("distance", distance);
|
|
905
|
+
p.put("durationMs", durationMs);
|
|
906
|
+
sendEvent("tripEnd", p);
|
|
907
|
+
} catch (JSONException e) { sendEvent("tripEnd"); }
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
@Override
|
|
911
|
+
public void onMoving(BackgroundLocation location) {
|
|
912
|
+
sendLocationEvent("moving", location);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
@Override
|
|
916
|
+
public void onStopped(BackgroundLocation location) {
|
|
917
|
+
sendLocationEvent("stopped", location);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
@Override
|
|
921
|
+
public void onSpeeding(BackgroundLocation location, double speedKmh, double limitKmh) {
|
|
922
|
+
try {
|
|
923
|
+
JSONObject p = new JSONObject();
|
|
924
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
925
|
+
p.put("speedKmh", speedKmh);
|
|
926
|
+
p.put("limitKmh", limitKmh);
|
|
927
|
+
sendEvent("speeding", p);
|
|
928
|
+
} catch (JSONException e) { sendEvent("speeding"); }
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
@Override
|
|
932
|
+
public void onProviderChange(String provider) {
|
|
933
|
+
try {
|
|
934
|
+
JSONObject p = new JSONObject();
|
|
935
|
+
p.put("provider", provider != null ? provider : "");
|
|
936
|
+
sendEvent("providerChange", p);
|
|
937
|
+
} catch (JSONException e) { sendEvent("providerChange"); }
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
@Override
|
|
941
|
+
public void onSOS(BackgroundLocation location, JSONObject userPayload) {
|
|
942
|
+
try {
|
|
943
|
+
JSONObject p = userPayload != null ? new JSONObject(userPayload.toString()) : new JSONObject();
|
|
944
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
945
|
+
sendEvent("sos", p);
|
|
946
|
+
} catch (JSONException e) { sendEvent("sos"); }
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private void sendLocationEvent(String name, BackgroundLocation location) {
|
|
950
|
+
if (location == null) { sendEvent(name); return; }
|
|
951
|
+
try { sendEvent(name, location.toJSONObjectWithId()); }
|
|
952
|
+
catch (JSONException e) { sendEvent(name); }
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// v4.1 GPS-derived sensor-like events
|
|
956
|
+
@Override
|
|
957
|
+
public void onHardBrake(BackgroundLocation location, double decelMps2) {
|
|
958
|
+
sendDrivingEvent("hardBrake", location, decelMps2);
|
|
959
|
+
}
|
|
960
|
+
@Override
|
|
961
|
+
public void onRapidAcceleration(BackgroundLocation location, double accelMps2) {
|
|
962
|
+
sendDrivingEvent("rapidAcceleration", location, accelMps2);
|
|
963
|
+
}
|
|
964
|
+
@Override
|
|
965
|
+
public void onSharpTurn(BackgroundLocation location, double degPerSec) {
|
|
966
|
+
sendDrivingEvent("sharpTurn", location, degPerSec);
|
|
967
|
+
}
|
|
968
|
+
@Override
|
|
969
|
+
public void onPossibleCrash(BackgroundLocation location, double velocityDropKmh) {
|
|
970
|
+
sendDrivingEvent("possibleCrash", location, velocityDropKmh);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// v4.2 sensor fusion: enriched possibleCrash with `source` ("gps"|"sensor") and phone-usage event.
|
|
974
|
+
@Override
|
|
975
|
+
public void onPossibleCrash(BackgroundLocation location, double value, String source) {
|
|
976
|
+
try {
|
|
977
|
+
JSONObject p = new JSONObject();
|
|
978
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
979
|
+
p.put("value", value);
|
|
980
|
+
p.put("source", source != null ? source : "gps");
|
|
981
|
+
sendEvent("possibleCrash", p);
|
|
982
|
+
} catch (JSONException e) {
|
|
983
|
+
sendEvent("possibleCrash");
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
@Override
|
|
987
|
+
public void onPhoneUsageWhileDriving(BackgroundLocation location) {
|
|
988
|
+
sendLocationEvent("phoneUsageWhileDriving", location);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
private void sendDrivingEvent(String name, BackgroundLocation location, double value) {
|
|
992
|
+
try {
|
|
993
|
+
JSONObject p = new JSONObject();
|
|
994
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
995
|
+
p.put("value", value);
|
|
996
|
+
sendEvent(name, p);
|
|
997
|
+
} catch (JSONException e) {
|
|
998
|
+
sendEvent(name);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
640
1001
|
}
|
|
@@ -26,11 +26,13 @@ import com.marianhello.bgloc.data.BackgroundLocation;
|
|
|
26
26
|
import com.marianhello.bgloc.data.ConfigurationDAO;
|
|
27
27
|
import com.marianhello.bgloc.data.DAOFactory;
|
|
28
28
|
import com.marianhello.bgloc.data.LocationDAO;
|
|
29
|
+
import com.marianhello.bgloc.data.SessionLocationDAO;
|
|
29
30
|
import com.marianhello.bgloc.provider.LocationProvider;
|
|
30
31
|
import com.marianhello.bgloc.service.LocationService;
|
|
31
32
|
import com.marianhello.bgloc.service.LocationServiceImpl;
|
|
32
33
|
import com.marianhello.bgloc.service.LocationServiceProxy;
|
|
33
34
|
import com.marianhello.bgloc.data.LocationTransform;
|
|
35
|
+
import com.marianhello.bgloc.data.sqlite.SQLiteSessionLocationDAO;
|
|
34
36
|
import com.marianhello.bgloc.sync.AccountHelper;
|
|
35
37
|
import com.marianhello.bgloc.sync.NotificationHelper;
|
|
36
38
|
import com.marianhello.bgloc.sync.SyncService;
|
|
@@ -172,6 +174,117 @@ public class BackgroundGeolocationFacade {
|
|
|
172
174
|
|
|
173
175
|
return;
|
|
174
176
|
}
|
|
177
|
+
|
|
178
|
+
case LocationServiceImpl.MSG_ON_SYNC_START: {
|
|
179
|
+
logger.debug("Received MSG_ON_SYNC_START");
|
|
180
|
+
if (mDelegate != null) mDelegate.onSyncStart();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case LocationServiceImpl.MSG_ON_SYNC_SUCCESS: {
|
|
185
|
+
int sent = bundle != null ? bundle.getInt("sent", 0) : 0;
|
|
186
|
+
logger.debug("Received MSG_ON_SYNC_SUCCESS sent={}", sent);
|
|
187
|
+
if (mDelegate != null) mDelegate.onSyncSuccess(sent);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case LocationServiceImpl.MSG_ON_SYNC_ERROR: {
|
|
192
|
+
int status = bundle != null ? bundle.getInt("httpStatus", 0) : 0;
|
|
193
|
+
String msg = bundle != null ? bundle.getString("message", "") : "";
|
|
194
|
+
logger.debug("Received MSG_ON_SYNC_ERROR status={} message={}", status, msg);
|
|
195
|
+
if (mDelegate != null) mDelegate.onSyncError(status, msg);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case LocationServiceImpl.MSG_ON_SYNC_PROGRESS: {
|
|
200
|
+
int progress = bundle != null ? bundle.getInt("progress", 0) : 0;
|
|
201
|
+
if (mDelegate != null) mDelegate.onSyncProgress(progress);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case LocationServiceImpl.MSG_ON_HEARTBEAT: {
|
|
206
|
+
if (bundle != null) {
|
|
207
|
+
bundle.setClassLoader(LocationServiceImpl.class.getClassLoader());
|
|
208
|
+
}
|
|
209
|
+
BackgroundLocation hb = bundle != null ? (BackgroundLocation) bundle.getParcelable("payload") : null;
|
|
210
|
+
if (mDelegate != null) mDelegate.onHeartbeat(hb);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// v4.0 Phase 6: driver-insights events
|
|
215
|
+
case LocationServiceImpl.MSG_ON_TRIP_START:
|
|
216
|
+
case LocationServiceImpl.MSG_ON_TRIP_END:
|
|
217
|
+
case LocationServiceImpl.MSG_ON_MOVING:
|
|
218
|
+
case LocationServiceImpl.MSG_ON_STOPPED:
|
|
219
|
+
case LocationServiceImpl.MSG_ON_SPEEDING:
|
|
220
|
+
case LocationServiceImpl.MSG_ON_SOS: {
|
|
221
|
+
if (bundle != null) bundle.setClassLoader(LocationServiceImpl.class.getClassLoader());
|
|
222
|
+
BackgroundLocation loc = bundle != null ? (BackgroundLocation) bundle.getParcelable("payload") : null;
|
|
223
|
+
if (mDelegate == null) return;
|
|
224
|
+
switch (action) {
|
|
225
|
+
case LocationServiceImpl.MSG_ON_TRIP_START:
|
|
226
|
+
mDelegate.onTripStart(loc); break;
|
|
227
|
+
case LocationServiceImpl.MSG_ON_TRIP_END:
|
|
228
|
+
double dist = bundle != null ? bundle.getDouble("distance", 0.0) : 0.0;
|
|
229
|
+
long durMs = bundle != null ? bundle.getLong("durationMs", 0L) : 0L;
|
|
230
|
+
mDelegate.onTripEnd(loc, dist, durMs); break;
|
|
231
|
+
case LocationServiceImpl.MSG_ON_MOVING:
|
|
232
|
+
mDelegate.onMoving(loc); break;
|
|
233
|
+
case LocationServiceImpl.MSG_ON_STOPPED:
|
|
234
|
+
mDelegate.onStopped(loc); break;
|
|
235
|
+
case LocationServiceImpl.MSG_ON_SPEEDING:
|
|
236
|
+
double sKmh = bundle != null ? bundle.getDouble("speedKmh", 0.0) : 0.0;
|
|
237
|
+
double lKmh = bundle != null ? bundle.getDouble("limitKmh", 0.0) : 0.0;
|
|
238
|
+
mDelegate.onSpeeding(loc, sKmh, lKmh); break;
|
|
239
|
+
case LocationServiceImpl.MSG_ON_SOS:
|
|
240
|
+
org.json.JSONObject sosPayload = null;
|
|
241
|
+
if (bundle != null) {
|
|
242
|
+
String s = bundle.getString("sosPayload");
|
|
243
|
+
if (s != null) {
|
|
244
|
+
try { sosPayload = new org.json.JSONObject(s); }
|
|
245
|
+
catch (org.json.JSONException ignored) { sosPayload = new org.json.JSONObject(); }
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
mDelegate.onSOS(loc, sosPayload); break;
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case LocationServiceImpl.MSG_ON_PROVIDER_CHANGE: {
|
|
254
|
+
String provider = bundle != null ? bundle.getString("provider", "") : "";
|
|
255
|
+
if (mDelegate != null) mDelegate.onProviderChange(provider);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// v4.1 GPS-derived sensor-like events (and v4.2 sensor-driven possibleCrash)
|
|
260
|
+
case LocationServiceImpl.MSG_ON_HARD_BRAKE:
|
|
261
|
+
case LocationServiceImpl.MSG_ON_RAPID_ACCELERATION:
|
|
262
|
+
case LocationServiceImpl.MSG_ON_SHARP_TURN:
|
|
263
|
+
case LocationServiceImpl.MSG_ON_POSSIBLE_CRASH: {
|
|
264
|
+
if (bundle != null) bundle.setClassLoader(LocationServiceImpl.class.getClassLoader());
|
|
265
|
+
BackgroundLocation drvLoc = bundle != null ? (BackgroundLocation) bundle.getParcelable("payload") : null;
|
|
266
|
+
double drvVal = bundle != null ? bundle.getDouble("value", 0.0) : 0.0;
|
|
267
|
+
String drvSrc = bundle != null ? bundle.getString("source", "gps") : "gps";
|
|
268
|
+
if (mDelegate == null) return;
|
|
269
|
+
switch (action) {
|
|
270
|
+
case LocationServiceImpl.MSG_ON_HARD_BRAKE:
|
|
271
|
+
mDelegate.onHardBrake(drvLoc, drvVal); break;
|
|
272
|
+
case LocationServiceImpl.MSG_ON_RAPID_ACCELERATION:
|
|
273
|
+
mDelegate.onRapidAcceleration(drvLoc, drvVal); break;
|
|
274
|
+
case LocationServiceImpl.MSG_ON_SHARP_TURN:
|
|
275
|
+
mDelegate.onSharpTurn(drvLoc, drvVal); break;
|
|
276
|
+
case LocationServiceImpl.MSG_ON_POSSIBLE_CRASH:
|
|
277
|
+
mDelegate.onPossibleCrash(drvLoc, drvVal, drvSrc); break;
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// v4.2 sensor fusion: phone usage while driving
|
|
282
|
+
case LocationServiceImpl.MSG_ON_PHONE_USAGE_WHILE_DRIVING: {
|
|
283
|
+
if (bundle != null) bundle.setClassLoader(LocationServiceImpl.class.getClassLoader());
|
|
284
|
+
BackgroundLocation puLoc = bundle != null ? (BackgroundLocation) bundle.getParcelable("payload") : null;
|
|
285
|
+
if (mDelegate != null) mDelegate.onPhoneUsageWhileDriving(puLoc);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
175
288
|
}
|
|
176
289
|
}
|
|
177
290
|
};
|
|
@@ -294,6 +407,30 @@ public class BackgroundGeolocationFacade {
|
|
|
294
407
|
return dao.getValidLocationsAndDelete();
|
|
295
408
|
}
|
|
296
409
|
|
|
410
|
+
/** Clear session table and start storing all new locations in session. Call when user starts a route. */
|
|
411
|
+
public void startSession() {
|
|
412
|
+
SessionLocationDAO dao = new SQLiteSessionLocationDAO(getContext());
|
|
413
|
+
dao.startSession();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Return all locations stored in the current session (ordered by time). */
|
|
417
|
+
public Collection<BackgroundLocation> getSessionLocations() {
|
|
418
|
+
SessionLocationDAO dao = new SQLiteSessionLocationDAO(getContext());
|
|
419
|
+
return dao.getSessionLocations();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Clear session table and stop storing. Call when route is finished and sync OK. */
|
|
423
|
+
public void clearSession() {
|
|
424
|
+
SessionLocationDAO dao = new SQLiteSessionLocationDAO(getContext());
|
|
425
|
+
dao.clearSession();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Number of locations in the current session. */
|
|
429
|
+
public int getSessionLocationsCount() {
|
|
430
|
+
SessionLocationDAO dao = new SQLiteSessionLocationDAO(getContext());
|
|
431
|
+
return dao.getSessionLocationsCount();
|
|
432
|
+
}
|
|
433
|
+
|
|
297
434
|
public BackgroundLocation getStationaryLocation() {
|
|
298
435
|
return mStationaryLocation;
|
|
299
436
|
}
|
|
@@ -422,6 +559,22 @@ public class BackgroundGeolocationFacade {
|
|
|
422
559
|
SyncService.sync(syncAccount, resolver.getAuthority(), true);
|
|
423
560
|
}
|
|
424
561
|
|
|
562
|
+
/**
|
|
563
|
+
* v4.0 Phase 6 — Trigger an SOS event. The plugin emits a single `sos` JS event
|
|
564
|
+
* carrying the latest known location and the user-supplied JSON payload.
|
|
565
|
+
*/
|
|
566
|
+
public void triggerSOS(org.json.JSONObject payload) {
|
|
567
|
+
Bundle b = new Bundle();
|
|
568
|
+
b.putInt("action", LocationServiceImpl.MSG_ON_SOS);
|
|
569
|
+
BackgroundLocation last = LocationServiceImpl.getLastReceivedLocation();
|
|
570
|
+
if (last != null) b.putParcelable("payload", last);
|
|
571
|
+
b.putString("sosPayload", payload != null ? payload.toString() : "{}");
|
|
572
|
+
Intent intent = new Intent(LocationServiceImpl.ACTION_BROADCAST);
|
|
573
|
+
intent.putExtras(b);
|
|
574
|
+
androidx.localbroadcastmanager.content.LocalBroadcastManager
|
|
575
|
+
.getInstance(getContext().getApplicationContext()).sendBroadcast(intent);
|
|
576
|
+
}
|
|
577
|
+
|
|
425
578
|
/**
|
|
426
579
|
* Returns the number of locations pending to be synced (not yet sent to syncUrl).
|
|
427
580
|
*/
|
|
@@ -31,7 +31,8 @@ public class BootCompletedReceiver extends BroadcastReceiver {
|
|
|
31
31
|
|
|
32
32
|
@Override
|
|
33
33
|
public void onReceive(Context context, Intent intent) {
|
|
34
|
-
|
|
34
|
+
String action = intent != null ? intent.getAction() : null;
|
|
35
|
+
Log.d(TAG, "Received boot/replace broadcast: " + action);
|
|
35
36
|
ConfigurationDAO dao = DAOFactory.createConfigurationDAO(context);
|
|
36
37
|
Config config = null;
|
|
37
38
|
|
|
@@ -43,23 +44,34 @@ public class BootCompletedReceiver extends BroadcastReceiver {
|
|
|
43
44
|
|
|
44
45
|
if (config == null) { return; }
|
|
45
46
|
|
|
46
|
-
Log.d(TAG, "Boot
|
|
47
|
+
Log.d(TAG, "Boot/replace handler " + config.toString());
|
|
47
48
|
|
|
48
|
-
if (config.getStartOnBoot()) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
if (!config.getStartOnBoot()) { return; }
|
|
50
|
+
|
|
51
|
+
if (!hasLocationPermission(context)) {
|
|
52
|
+
Log.w(TAG, "Skipping start on boot: ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION not granted");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (Build.VERSION.SDK_INT >= 29 && !hasBackgroundLocationPermission(context)) {
|
|
56
|
+
Log.w(TAG, "Skipping start on boot: ACCESS_BACKGROUND_LOCATION not granted (Android 10+)");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
57
59
|
|
|
60
|
+
Log.i(TAG, "Starting service after boot/replace");
|
|
61
|
+
Intent locationServiceIntent = new Intent(context, LocationServiceImpl.class);
|
|
62
|
+
locationServiceIntent.addFlags(Intent.FLAG_FROM_BACKGROUND);
|
|
63
|
+
locationServiceIntent.putExtra("config", config);
|
|
64
|
+
|
|
65
|
+
try {
|
|
58
66
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
59
67
|
context.startForegroundService(locationServiceIntent);
|
|
60
68
|
} else {
|
|
61
69
|
context.startService(locationServiceIntent);
|
|
62
70
|
}
|
|
71
|
+
} catch (Exception e) {
|
|
72
|
+
// Android 12+ may throw ForegroundServiceStartNotAllowedException.
|
|
73
|
+
// Log and exit; do NOT fall back to a non-foreground service for tracking.
|
|
74
|
+
Log.e(TAG, "Start on boot blocked: " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
77
|
|
|
@@ -67,4 +79,8 @@ public class BootCompletedReceiver extends BroadcastReceiver {
|
|
|
67
79
|
return context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
68
80
|
|| context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
|
69
81
|
}
|
|
82
|
+
|
|
83
|
+
private static boolean hasBackgroundLocationPermission(Context context) {
|
|
84
|
+
return context.checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
|
85
|
+
}
|
|
70
86
|
}
|