@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
|
@@ -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;
|
|
@@ -78,9 +79,17 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
|
|
|
78
79
|
public static final String ACTION_CLEAR_SESSION = "clearSession";
|
|
79
80
|
public static final String ACTION_GET_SESSION_LOCATIONS_COUNT = "getSessionLocationsCount";
|
|
80
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";
|
|
81
90
|
|
|
82
91
|
/** Plugin version; keep in sync with plugin.xml. */
|
|
83
|
-
public static final String PLUGIN_VERSION = "
|
|
92
|
+
public static final String PLUGIN_VERSION = "4.2.2";
|
|
84
93
|
|
|
85
94
|
private BackgroundGeolocationFacade facade;
|
|
86
95
|
|
|
@@ -449,11 +458,155 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
|
|
|
449
458
|
} else if (ACTION_GET_PLUGIN_VERSION.equals(action)) {
|
|
450
459
|
callbackContext.success(PLUGIN_VERSION);
|
|
451
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;
|
|
452
510
|
}
|
|
453
511
|
|
|
454
512
|
return false;
|
|
455
513
|
}
|
|
456
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
|
+
try {
|
|
523
|
+
d.put("locationServicesEnabled", facade.locationServicesEnabled());
|
|
524
|
+
} catch (PluginException e) {
|
|
525
|
+
d.put("locationServicesEnabled", JSONObject.NULL);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
Config cfg = facade.getConfig();
|
|
530
|
+
if (cfg != null) {
|
|
531
|
+
d.put("startOnBoot", cfg.getStartOnBoot());
|
|
532
|
+
}
|
|
533
|
+
} catch (Exception ignored) { /* config may not be persisted yet */ }
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
d.put("pendingSyncCount", (int) Math.min(facade.getPendingSyncCount(), Integer.MAX_VALUE));
|
|
537
|
+
} catch (Exception ignored) { /* DAO might not be ready */ }
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
BackgroundLocation last = facade.getStationaryLocation();
|
|
541
|
+
// Last *received* location is closer to lastBest; we expose stationary as a fallback signal.
|
|
542
|
+
d.put("lastLocationAt", last != null ? last.getTime() : JSONObject.NULL);
|
|
543
|
+
} catch (Exception ignored) {
|
|
544
|
+
d.put("lastLocationAt", JSONObject.NULL);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Permissions
|
|
548
|
+
d.put("fineLocationGranted", hasPermission(ctx, android.Manifest.permission.ACCESS_FINE_LOCATION));
|
|
549
|
+
d.put("coarseLocationGranted", hasPermission(ctx, android.Manifest.permission.ACCESS_COARSE_LOCATION));
|
|
550
|
+
if (android.os.Build.VERSION.SDK_INT >= 29) {
|
|
551
|
+
d.put("backgroundLocationGranted", hasPermission(ctx, android.Manifest.permission.ACCESS_BACKGROUND_LOCATION));
|
|
552
|
+
} else {
|
|
553
|
+
d.put("backgroundLocationGranted", true);
|
|
554
|
+
}
|
|
555
|
+
if (android.os.Build.VERSION.SDK_INT >= 33) {
|
|
556
|
+
d.put("notificationPermissionGranted", hasPermission(ctx, "android.permission.POST_NOTIFICATIONS"));
|
|
557
|
+
} else {
|
|
558
|
+
d.put("notificationPermissionGranted", true);
|
|
559
|
+
}
|
|
560
|
+
if (android.os.Build.VERSION.SDK_INT >= 29) {
|
|
561
|
+
d.put("activityRecognitionGranted", hasPermission(ctx, "android.permission.ACTIVITY_RECOGNITION"));
|
|
562
|
+
} else {
|
|
563
|
+
d.put("activityRecognitionGranted", true);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Battery / OEM
|
|
567
|
+
d.put("batteryOptimizationIgnored", isIgnoringBatteryOptimizations(ctx));
|
|
568
|
+
d.put("manufacturer", android.os.Build.MANUFACTURER != null ? android.os.Build.MANUFACTURER : "");
|
|
569
|
+
|
|
570
|
+
// Foreground service type read from manifest (only meaningful on API 34+; reported as-is otherwise)
|
|
571
|
+
d.put("foregroundServiceType", readForegroundServiceTypeFromManifest(ctx));
|
|
572
|
+
|
|
573
|
+
return d;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private static boolean hasPermission(Context ctx, String permission) {
|
|
577
|
+
try {
|
|
578
|
+
return ctx.getPackageManager().checkPermission(permission, ctx.getPackageName())
|
|
579
|
+
== android.content.pm.PackageManager.PERMISSION_GRANTED;
|
|
580
|
+
} catch (Exception e) {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private static boolean isIgnoringBatteryOptimizations(Context ctx) {
|
|
586
|
+
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) return true;
|
|
587
|
+
try {
|
|
588
|
+
android.os.PowerManager pm = (android.os.PowerManager) ctx.getSystemService(Context.POWER_SERVICE);
|
|
589
|
+
return pm != null && pm.isIgnoringBatteryOptimizations(ctx.getPackageName());
|
|
590
|
+
} catch (Exception e) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
private static int readForegroundServiceTypeFromManifest(Context ctx) {
|
|
596
|
+
if (android.os.Build.VERSION.SDK_INT < 34) return 0;
|
|
597
|
+
try {
|
|
598
|
+
android.content.ComponentName cn = new android.content.ComponentName(
|
|
599
|
+
ctx, com.marianhello.bgloc.service.LocationServiceImpl.class);
|
|
600
|
+
android.content.pm.ServiceInfo si = ctx.getPackageManager().getServiceInfo(
|
|
601
|
+
cn, android.content.pm.PackageManager.ComponentInfoFlags.of(0));
|
|
602
|
+
java.lang.reflect.Field f = android.content.pm.ServiceInfo.class.getField("foregroundServiceType");
|
|
603
|
+
Object v = f.get(si);
|
|
604
|
+
return (v instanceof Integer) ? (Integer) v : 0;
|
|
605
|
+
} catch (Throwable e) {
|
|
606
|
+
return 0;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
457
610
|
/**
|
|
458
611
|
* Called when the system is about to start resuming a previous activity.
|
|
459
612
|
*
|
|
@@ -693,4 +846,160 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Plugin
|
|
|
693
846
|
public void onError(PluginException e) {
|
|
694
847
|
sendError(e);
|
|
695
848
|
}
|
|
849
|
+
|
|
850
|
+
// v3.5 Phase 4: sync queue events
|
|
851
|
+
@Override
|
|
852
|
+
public void onSyncStart() {
|
|
853
|
+
sendEvent("syncStart");
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
@Override
|
|
857
|
+
public void onSyncSuccess(int locationsSent) {
|
|
858
|
+
try {
|
|
859
|
+
JSONObject payload = new JSONObject();
|
|
860
|
+
payload.put("sent", locationsSent);
|
|
861
|
+
sendEvent("syncSuccess", payload);
|
|
862
|
+
} catch (JSONException e) {
|
|
863
|
+
sendEvent("syncSuccess");
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
@Override
|
|
868
|
+
public void onSyncError(int httpStatus, String message) {
|
|
869
|
+
try {
|
|
870
|
+
JSONObject payload = new JSONObject();
|
|
871
|
+
payload.put("httpStatus", httpStatus);
|
|
872
|
+
payload.put("message", message != null ? message : "");
|
|
873
|
+
sendEvent("syncError", payload);
|
|
874
|
+
} catch (JSONException e) {
|
|
875
|
+
sendEvent("syncError");
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
@Override
|
|
880
|
+
public void onSyncProgress(int progress) {
|
|
881
|
+
sendEvent("syncProgress", Integer.valueOf(progress));
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
@Override
|
|
885
|
+
public void onHeartbeat(BackgroundLocation location) {
|
|
886
|
+
if (location == null) {
|
|
887
|
+
sendEvent("heartbeat");
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
sendEvent("heartbeat", location.toJSONObjectWithId());
|
|
892
|
+
} catch (JSONException e) {
|
|
893
|
+
sendEvent("heartbeat");
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// v4.0 Phase 6 — driver-insight events
|
|
898
|
+
@Override
|
|
899
|
+
public void onTripStart(BackgroundLocation location) {
|
|
900
|
+
sendLocationEvent("tripStart", location);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
@Override
|
|
904
|
+
public void onTripEnd(BackgroundLocation location, double distance, long durationMs) {
|
|
905
|
+
try {
|
|
906
|
+
JSONObject p = new JSONObject();
|
|
907
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
908
|
+
p.put("distance", distance);
|
|
909
|
+
p.put("durationMs", durationMs);
|
|
910
|
+
sendEvent("tripEnd", p);
|
|
911
|
+
} catch (JSONException e) { sendEvent("tripEnd"); }
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
@Override
|
|
915
|
+
public void onMoving(BackgroundLocation location) {
|
|
916
|
+
sendLocationEvent("moving", location);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
@Override
|
|
920
|
+
public void onStopped(BackgroundLocation location) {
|
|
921
|
+
sendLocationEvent("stopped", location);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
@Override
|
|
925
|
+
public void onSpeeding(BackgroundLocation location, double speedKmh, double limitKmh) {
|
|
926
|
+
try {
|
|
927
|
+
JSONObject p = new JSONObject();
|
|
928
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
929
|
+
p.put("speedKmh", speedKmh);
|
|
930
|
+
p.put("limitKmh", limitKmh);
|
|
931
|
+
sendEvent("speeding", p);
|
|
932
|
+
} catch (JSONException e) { sendEvent("speeding"); }
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
@Override
|
|
936
|
+
public void onProviderChange(String provider) {
|
|
937
|
+
try {
|
|
938
|
+
JSONObject p = new JSONObject();
|
|
939
|
+
p.put("provider", provider != null ? provider : "");
|
|
940
|
+
sendEvent("providerChange", p);
|
|
941
|
+
} catch (JSONException e) { sendEvent("providerChange"); }
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
@Override
|
|
945
|
+
public void onSOS(BackgroundLocation location, JSONObject userPayload) {
|
|
946
|
+
try {
|
|
947
|
+
JSONObject p = userPayload != null ? new JSONObject(userPayload.toString()) : new JSONObject();
|
|
948
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
949
|
+
sendEvent("sos", p);
|
|
950
|
+
} catch (JSONException e) { sendEvent("sos"); }
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
private void sendLocationEvent(String name, BackgroundLocation location) {
|
|
954
|
+
if (location == null) { sendEvent(name); return; }
|
|
955
|
+
try { sendEvent(name, location.toJSONObjectWithId()); }
|
|
956
|
+
catch (JSONException e) { sendEvent(name); }
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// v4.1 GPS-derived sensor-like events
|
|
960
|
+
@Override
|
|
961
|
+
public void onHardBrake(BackgroundLocation location, double decelMps2) {
|
|
962
|
+
sendDrivingEvent("hardBrake", location, decelMps2);
|
|
963
|
+
}
|
|
964
|
+
@Override
|
|
965
|
+
public void onRapidAcceleration(BackgroundLocation location, double accelMps2) {
|
|
966
|
+
sendDrivingEvent("rapidAcceleration", location, accelMps2);
|
|
967
|
+
}
|
|
968
|
+
@Override
|
|
969
|
+
public void onSharpTurn(BackgroundLocation location, double degPerSec) {
|
|
970
|
+
sendDrivingEvent("sharpTurn", location, degPerSec);
|
|
971
|
+
}
|
|
972
|
+
@Override
|
|
973
|
+
public void onPossibleCrash(BackgroundLocation location, double velocityDropKmh) {
|
|
974
|
+
sendDrivingEvent("possibleCrash", location, velocityDropKmh);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// v4.2 sensor fusion: enriched possibleCrash with `source` ("gps"|"sensor") and phone-usage event.
|
|
978
|
+
@Override
|
|
979
|
+
public void onPossibleCrash(BackgroundLocation location, double value, String source) {
|
|
980
|
+
try {
|
|
981
|
+
JSONObject p = new JSONObject();
|
|
982
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
983
|
+
p.put("value", value);
|
|
984
|
+
p.put("source", source != null ? source : "gps");
|
|
985
|
+
sendEvent("possibleCrash", p);
|
|
986
|
+
} catch (JSONException e) {
|
|
987
|
+
sendEvent("possibleCrash");
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
@Override
|
|
991
|
+
public void onPhoneUsageWhileDriving(BackgroundLocation location) {
|
|
992
|
+
sendLocationEvent("phoneUsageWhileDriving", location);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
private void sendDrivingEvent(String name, BackgroundLocation location, double value) {
|
|
996
|
+
try {
|
|
997
|
+
JSONObject p = new JSONObject();
|
|
998
|
+
p.put("location", location != null ? location.toJSONObjectWithId() : JSONObject.NULL);
|
|
999
|
+
p.put("value", value);
|
|
1000
|
+
sendEvent(name, p);
|
|
1001
|
+
} catch (JSONException e) {
|
|
1002
|
+
sendEvent(name);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
696
1005
|
}
|
|
@@ -174,6 +174,117 @@ public class BackgroundGeolocationFacade {
|
|
|
174
174
|
|
|
175
175
|
return;
|
|
176
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
|
+
}
|
|
177
288
|
}
|
|
178
289
|
}
|
|
179
290
|
};
|
|
@@ -448,6 +559,22 @@ public class BackgroundGeolocationFacade {
|
|
|
448
559
|
SyncService.sync(syncAccount, resolver.getAuthority(), true);
|
|
449
560
|
}
|
|
450
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
|
+
|
|
451
578
|
/**
|
|
452
579
|
* Returns the number of locations pending to be synced (not yet sent to syncUrl).
|
|
453
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
|
}
|