@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
|
@@ -113,6 +113,29 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
113
113
|
|
|
114
114
|
public static final int MSG_ON_HTTP_AUTHORIZATION = 107;
|
|
115
115
|
|
|
116
|
+
/** v3.5 Phase 4: sync queue events. */
|
|
117
|
+
public static final int MSG_ON_SYNC_START = 108;
|
|
118
|
+
public static final int MSG_ON_SYNC_SUCCESS = 109;
|
|
119
|
+
public static final int MSG_ON_SYNC_ERROR = 110;
|
|
120
|
+
public static final int MSG_ON_SYNC_PROGRESS = 111;
|
|
121
|
+
public static final int MSG_ON_HEARTBEAT = 112;
|
|
122
|
+
/** v4.0 Phase 6 — driver insight events. */
|
|
123
|
+
public static final int MSG_ON_TRIP_START = 113;
|
|
124
|
+
public static final int MSG_ON_TRIP_END = 114;
|
|
125
|
+
public static final int MSG_ON_MOVING = 115;
|
|
126
|
+
public static final int MSG_ON_STOPPED = 116;
|
|
127
|
+
public static final int MSG_ON_SPEEDING = 117;
|
|
128
|
+
public static final int MSG_ON_PROVIDER_CHANGE = 118;
|
|
129
|
+
public static final int MSG_ON_SOS = 119;
|
|
130
|
+
/** v4.1 — sensor-like GPS-derived driving events. */
|
|
131
|
+
public static final int MSG_ON_HARD_BRAKE = 120;
|
|
132
|
+
public static final int MSG_ON_RAPID_ACCELERATION = 121;
|
|
133
|
+
public static final int MSG_ON_SHARP_TURN = 122;
|
|
134
|
+
public static final int MSG_ON_POSSIBLE_CRASH = 123;
|
|
135
|
+
/** v4.2 — sensor-fusion-only events. {@code MSG_ON_POSSIBLE_CRASH} is reused
|
|
136
|
+
* by the sensor pipeline; phone-usage is a brand-new event. */
|
|
137
|
+
public static final int MSG_ON_PHONE_USAGE_WHILE_DRIVING = 124;
|
|
138
|
+
|
|
116
139
|
/** notification id */
|
|
117
140
|
private static int NOTIFICATION_ID = 1;
|
|
118
141
|
|
|
@@ -141,6 +164,21 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
141
164
|
|
|
142
165
|
/** Last time we received a location (for watchdog). */
|
|
143
166
|
private volatile long mLastLocationTime = 0L;
|
|
167
|
+
/** v3.5 Phase 4: latest received location, used as heartbeat payload. */
|
|
168
|
+
private volatile BackgroundLocation mLastReceivedLocation;
|
|
169
|
+
/** v4.0 Phase 6: static accessor for {@link com.marianhello.bgloc.BackgroundGeolocationFacade#triggerSOS}. */
|
|
170
|
+
private static volatile BackgroundLocation sLastReceivedLocation;
|
|
171
|
+
public static BackgroundLocation getLastReceivedLocation() { return sLastReceivedLocation; }
|
|
172
|
+
/** v3.5 Phase 4: heartbeat scheduler. */
|
|
173
|
+
private java.util.concurrent.ScheduledExecutorService mHeartbeatExecutor;
|
|
174
|
+
private java.util.concurrent.ScheduledFuture<?> mHeartbeatTask;
|
|
175
|
+
|
|
176
|
+
/** v4.0 Phase 6: driver-insights detector. Created lazily when config has drivingEvents.enabled. */
|
|
177
|
+
private com.marianhello.bgloc.driving.DrivingEventsDetector mDrivingDetector;
|
|
178
|
+
/** v4.2 Phase 8: real sensor-fusion detector. Created when drivingEvents.sensorFusion=true. */
|
|
179
|
+
private com.marianhello.bgloc.sensor.SensorFusionDetector mSensorFusion;
|
|
180
|
+
/** v4.2 Phase 8: cached tripActive state so hot-reload can re-inject it. */
|
|
181
|
+
private volatile boolean mDrivingTripActive = false;
|
|
144
182
|
private static final long WATCHDOG_INTERVAL_MS = 60_000L;
|
|
145
183
|
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
|
|
146
184
|
private final Runnable mWatchdogRunnable = new Runnable() {
|
|
@@ -469,6 +507,170 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
469
507
|
bundle.putInt("action", MSG_ON_SERVICE_STARTED);
|
|
470
508
|
bundle.putLong("serviceId", mServiceId);
|
|
471
509
|
broadcastMessage(bundle);
|
|
510
|
+
|
|
511
|
+
// v3.5 Phase 4: kick off heartbeat scheduler when the service starts.
|
|
512
|
+
scheduleHeartbeat();
|
|
513
|
+
|
|
514
|
+
// v4.0 Phase 6: build driver-insights detector if enabled in config.
|
|
515
|
+
configureDrivingDetector();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/** v4.0 Phase 6: instantiate / reconfigure the GPS-based driver-insights detector. */
|
|
519
|
+
private void configureDrivingDetector() {
|
|
520
|
+
if (mConfig == null) return;
|
|
521
|
+
com.marianhello.bgloc.Config.DrivingEventsOptions opts = mConfig.getDrivingEvents();
|
|
522
|
+
if (opts == null || !opts.enabled) {
|
|
523
|
+
if (mDrivingDetector != null) mDrivingDetector.reset();
|
|
524
|
+
mDrivingDetector = null;
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
com.marianhello.bgloc.driving.DrivingEventsDetector.Config c =
|
|
528
|
+
new com.marianhello.bgloc.driving.DrivingEventsDetector.Config();
|
|
529
|
+
c.enabled = true;
|
|
530
|
+
c.speedLimitKmh = opts.speedLimitKmh;
|
|
531
|
+
c.minMovingSpeedMps = opts.minMovingSpeedMps;
|
|
532
|
+
c.stoppedDurationMs = opts.stoppedDurationMs;
|
|
533
|
+
c.minTripSpeedMps = opts.minTripSpeedMps;
|
|
534
|
+
c.minTripDurationMs = opts.minTripDurationMs;
|
|
535
|
+
|
|
536
|
+
mDrivingDetector = new com.marianhello.bgloc.driving.DrivingEventsDetector(
|
|
537
|
+
new com.marianhello.bgloc.driving.DrivingEventsDetector.Listener() {
|
|
538
|
+
@Override public void onMoving(BackgroundLocation l) {
|
|
539
|
+
Bundle b = new Bundle();
|
|
540
|
+
b.putInt("action", MSG_ON_MOVING);
|
|
541
|
+
if (l != null) b.putParcelable("payload", l);
|
|
542
|
+
broadcastMessage(b);
|
|
543
|
+
}
|
|
544
|
+
@Override public void onStopped(BackgroundLocation l) {
|
|
545
|
+
Bundle b = new Bundle();
|
|
546
|
+
b.putInt("action", MSG_ON_STOPPED);
|
|
547
|
+
if (l != null) b.putParcelable("payload", l);
|
|
548
|
+
broadcastMessage(b);
|
|
549
|
+
}
|
|
550
|
+
@Override public void onTripStart(BackgroundLocation l) {
|
|
551
|
+
Bundle b = new Bundle();
|
|
552
|
+
b.putInt("action", MSG_ON_TRIP_START);
|
|
553
|
+
if (l != null) b.putParcelable("payload", l);
|
|
554
|
+
broadcastMessage(b);
|
|
555
|
+
mDrivingTripActive = true;
|
|
556
|
+
if (mSensorFusion != null) mSensorFusion.setTripActive(true);
|
|
557
|
+
}
|
|
558
|
+
@Override public void onTripEnd(BackgroundLocation l, double distance, long durationMs) {
|
|
559
|
+
Bundle b = new Bundle();
|
|
560
|
+
b.putInt("action", MSG_ON_TRIP_END);
|
|
561
|
+
if (l != null) b.putParcelable("payload", l);
|
|
562
|
+
b.putDouble("distance", distance);
|
|
563
|
+
b.putLong("durationMs", durationMs);
|
|
564
|
+
broadcastMessage(b);
|
|
565
|
+
mDrivingTripActive = false;
|
|
566
|
+
if (mSensorFusion != null) mSensorFusion.setTripActive(false);
|
|
567
|
+
}
|
|
568
|
+
@Override public void onSpeeding(BackgroundLocation l, double speedKmh, double limitKmh) {
|
|
569
|
+
Bundle b = new Bundle();
|
|
570
|
+
b.putInt("action", MSG_ON_SPEEDING);
|
|
571
|
+
if (l != null) b.putParcelable("payload", l);
|
|
572
|
+
b.putDouble("speedKmh", speedKmh);
|
|
573
|
+
b.putDouble("limitKmh", limitKmh);
|
|
574
|
+
broadcastMessage(b);
|
|
575
|
+
}
|
|
576
|
+
@Override public void onProviderChange(String provider) {
|
|
577
|
+
Bundle b = new Bundle();
|
|
578
|
+
b.putInt("action", MSG_ON_PROVIDER_CHANGE);
|
|
579
|
+
b.putString("provider", provider != null ? provider : "");
|
|
580
|
+
broadcastMessage(b);
|
|
581
|
+
}
|
|
582
|
+
@Override public void onHardBrake(BackgroundLocation l, double decelMps2) {
|
|
583
|
+
Bundle b = new Bundle();
|
|
584
|
+
b.putInt("action", MSG_ON_HARD_BRAKE);
|
|
585
|
+
if (l != null) b.putParcelable("payload", l);
|
|
586
|
+
b.putDouble("value", decelMps2);
|
|
587
|
+
broadcastMessage(b);
|
|
588
|
+
}
|
|
589
|
+
@Override public void onRapidAcceleration(BackgroundLocation l, double accelMps2) {
|
|
590
|
+
Bundle b = new Bundle();
|
|
591
|
+
b.putInt("action", MSG_ON_RAPID_ACCELERATION);
|
|
592
|
+
if (l != null) b.putParcelable("payload", l);
|
|
593
|
+
b.putDouble("value", accelMps2);
|
|
594
|
+
broadcastMessage(b);
|
|
595
|
+
}
|
|
596
|
+
@Override public void onSharpTurn(BackgroundLocation l, double degPerSec) {
|
|
597
|
+
Bundle b = new Bundle();
|
|
598
|
+
b.putInt("action", MSG_ON_SHARP_TURN);
|
|
599
|
+
if (l != null) b.putParcelable("payload", l);
|
|
600
|
+
b.putDouble("value", degPerSec);
|
|
601
|
+
broadcastMessage(b);
|
|
602
|
+
}
|
|
603
|
+
@Override public void onPossibleCrash(BackgroundLocation l, double velocityDropKmh) {
|
|
604
|
+
Bundle b = new Bundle();
|
|
605
|
+
b.putInt("action", MSG_ON_POSSIBLE_CRASH);
|
|
606
|
+
if (l != null) b.putParcelable("payload", l);
|
|
607
|
+
b.putDouble("value", velocityDropKmh);
|
|
608
|
+
b.putString("source", "gps");
|
|
609
|
+
broadcastMessage(b);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
// Pass v4.1 thresholds from app config (with defaults from c).
|
|
613
|
+
com.marianhello.bgloc.Config.DrivingEventsOptions optsRef = mConfig.getDrivingEvents();
|
|
614
|
+
if (optsRef != null) {
|
|
615
|
+
c.hardBrakeMps2 = optsRef.hardBrakeMps2;
|
|
616
|
+
c.rapidAccelMps2 = optsRef.rapidAccelMps2;
|
|
617
|
+
c.sharpTurnDegPerSec = optsRef.sharpTurnDegPerSec;
|
|
618
|
+
c.crashImpactKmh = optsRef.crashImpactKmh;
|
|
619
|
+
c.crashWindowMs = optsRef.crashWindowMs;
|
|
620
|
+
}
|
|
621
|
+
mDrivingDetector.setConfig(c);
|
|
622
|
+
configureSensorFusion();
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** v4.2 Phase 8: instantiate / reconfigure the real sensor-fusion detector. */
|
|
626
|
+
private void configureSensorFusion() {
|
|
627
|
+
if (mConfig == null) {
|
|
628
|
+
if (mSensorFusion != null) { mSensorFusion.stop(); mSensorFusion = null; }
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
com.marianhello.bgloc.Config.DrivingEventsOptions opts = mConfig.getDrivingEvents();
|
|
632
|
+
boolean wantSF = opts != null && opts.enabled && opts.sensorFusion;
|
|
633
|
+
if (!wantSF) {
|
|
634
|
+
if (mSensorFusion != null) { mSensorFusion.stop(); mSensorFusion = null; }
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
com.marianhello.bgloc.sensor.SensorFusionDetector.Listener l =
|
|
639
|
+
new com.marianhello.bgloc.sensor.SensorFusionDetector.Listener() {
|
|
640
|
+
@Override public void onSensorCrash(BackgroundLocation lastLocation, double impactG) {
|
|
641
|
+
Bundle b = new Bundle();
|
|
642
|
+
b.putInt("action", MSG_ON_POSSIBLE_CRASH);
|
|
643
|
+
if (lastLocation != null) b.putParcelable("payload", lastLocation);
|
|
644
|
+
b.putDouble("value", impactG);
|
|
645
|
+
b.putString("source", "sensor");
|
|
646
|
+
broadcastMessage(b);
|
|
647
|
+
}
|
|
648
|
+
@Override public void onPhoneUsageWhileDriving(BackgroundLocation lastLocation) {
|
|
649
|
+
Bundle b = new Bundle();
|
|
650
|
+
b.putInt("action", MSG_ON_PHONE_USAGE_WHILE_DRIVING);
|
|
651
|
+
if (lastLocation != null) b.putParcelable("payload", lastLocation);
|
|
652
|
+
broadcastMessage(b);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
if (mSensorFusion == null) {
|
|
657
|
+
mSensorFusion = new com.marianhello.bgloc.sensor.SensorFusionDetector(this, l);
|
|
658
|
+
}
|
|
659
|
+
com.marianhello.bgloc.sensor.SensorFusionDetector.Config sfc =
|
|
660
|
+
new com.marianhello.bgloc.sensor.SensorFusionDetector.Config();
|
|
661
|
+
sfc.enabled = true;
|
|
662
|
+
sfc.crashImpactG = opts.crashImpactG;
|
|
663
|
+
sfc.crashCooldownMs = opts.sensorCrashCooldownMs;
|
|
664
|
+
sfc.phoneUsageWindowMs = opts.phoneUsageWindowMs;
|
|
665
|
+
sfc.phoneUsageCooldownMs = opts.phoneUsageCooldownMs;
|
|
666
|
+
mSensorFusion.setConfig(sfc);
|
|
667
|
+
// v4.2 hot-reload: re-inject current tripActive state and last location so the
|
|
668
|
+
// sensor pipeline starts in the right mode (e.g. config arrives mid-trip).
|
|
669
|
+
mSensorFusion.setTripActive(mDrivingTripActive);
|
|
670
|
+
if (mLastReceivedLocation != null) {
|
|
671
|
+
mSensorFusion.setLastLocation(mLastReceivedLocation);
|
|
672
|
+
}
|
|
673
|
+
if (sIsRunning) mSensorFusion.start();
|
|
472
674
|
}
|
|
473
675
|
|
|
474
676
|
@Override
|
|
@@ -503,6 +705,17 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
503
705
|
stopForeground(true);
|
|
504
706
|
stopSelf();
|
|
505
707
|
|
|
708
|
+
// v3.5 Phase 4: stop heartbeat scheduler.
|
|
709
|
+
cancelHeartbeat();
|
|
710
|
+
// v4.0 Phase 6: reset driver-insights state machine.
|
|
711
|
+
if (mDrivingDetector != null) mDrivingDetector.reset();
|
|
712
|
+
// v4.2 Phase 8: stop sensor fusion sampling.
|
|
713
|
+
mDrivingTripActive = false;
|
|
714
|
+
if (mSensorFusion != null) {
|
|
715
|
+
mSensorFusion.setTripActive(false);
|
|
716
|
+
mSensorFusion.stop();
|
|
717
|
+
}
|
|
718
|
+
|
|
506
719
|
broadcastMessage(MSG_ON_SERVICE_STOPPED);
|
|
507
720
|
sIsRunning = false;
|
|
508
721
|
}
|
|
@@ -516,13 +729,10 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
516
729
|
|| checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
|
517
730
|
}
|
|
518
731
|
|
|
519
|
-
/** FOREGROUND_SERVICE_TYPE_LOCATION = 4 when compileSdk >= 34. */
|
|
520
|
-
private static final int FOREGROUND_SERVICE_TYPE_LOCATION = 4;
|
|
521
|
-
|
|
522
732
|
/**
|
|
523
733
|
* Reads this service's foregroundServiceType from the merged AndroidManifest (API 34+).
|
|
524
734
|
* Uses ComponentInfoFlags.of(0) (not GET_META_DATA) so getServiceInfo returns complete ServiceInfo.
|
|
525
|
-
* Returns the real value; never invents
|
|
735
|
+
* Returns the real value; never invents a hardcoded type. If unknown, returns 0 so callers must not call startForeground.
|
|
526
736
|
* Requires compileSdk 33+ (ComponentInfoFlags); 34+ for ServiceInfo.foregroundServiceType.
|
|
527
737
|
*/
|
|
528
738
|
private int getManifestForegroundServiceType() {
|
|
@@ -581,9 +791,14 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
581
791
|
mProvider.onCommand(LocationProvider.CMD_SWITCH_MODE,
|
|
582
792
|
LocationProvider.FOREGROUND_MODE);
|
|
583
793
|
}
|
|
584
|
-
// Android 14+ (API 34): type must match the merged manifest.
|
|
794
|
+
// Android 14+ (API 34): type must match the merged manifest. Read it dynamically; if unknown, do not start.
|
|
585
795
|
if (Build.VERSION.SDK_INT >= 34) {
|
|
586
|
-
|
|
796
|
+
int type = getManifestForegroundServiceType();
|
|
797
|
+
if (type == 0) {
|
|
798
|
+
logger.error("Cannot start foreground: manifest foregroundServiceType missing or unreadable");
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
super.startForeground(NOTIFICATION_ID, notification, type);
|
|
587
802
|
} else {
|
|
588
803
|
super.startForeground(NOTIFICATION_ID, notification);
|
|
589
804
|
}
|
|
@@ -724,10 +939,49 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
724
939
|
} else {
|
|
725
940
|
mProvider.onConfigure(mConfig);
|
|
726
941
|
}
|
|
942
|
+
|
|
943
|
+
// v4.1: re-evaluate hot-reload features when config changes while service is running.
|
|
944
|
+
if (sIsRunning) {
|
|
945
|
+
Integer prevHb = currentConfig.getHeartbeatInterval();
|
|
946
|
+
Integer newHb = mConfig.getHeartbeatInterval();
|
|
947
|
+
if (prevHb == null) prevHb = 0;
|
|
948
|
+
if (newHb == null) newHb = 0;
|
|
949
|
+
if (!prevHb.equals(newHb)) {
|
|
950
|
+
scheduleHeartbeat(); // cancels and reschedules with the new interval (or stops if 0)
|
|
951
|
+
}
|
|
952
|
+
// Driver-insights detector: rebuild if the config dict changed.
|
|
953
|
+
Config.DrivingEventsOptions prevDe = currentConfig.getDrivingEvents();
|
|
954
|
+
Config.DrivingEventsOptions newDe = mConfig.getDrivingEvents();
|
|
955
|
+
if (!equalsDrivingEvents(prevDe, newDe)) {
|
|
956
|
+
configureDrivingDetector();
|
|
957
|
+
}
|
|
958
|
+
}
|
|
727
959
|
}
|
|
728
960
|
});
|
|
729
961
|
}
|
|
730
962
|
|
|
963
|
+
/** Shallow value equality for DrivingEventsOptions; avoids needless detector rebuilds. */
|
|
964
|
+
private static boolean equalsDrivingEvents(Config.DrivingEventsOptions a, Config.DrivingEventsOptions b) {
|
|
965
|
+
if (a == b) return true;
|
|
966
|
+
if (a == null || b == null) return false;
|
|
967
|
+
return a.enabled == b.enabled
|
|
968
|
+
&& a.speedLimitKmh == b.speedLimitKmh
|
|
969
|
+
&& a.minMovingSpeedMps == b.minMovingSpeedMps
|
|
970
|
+
&& a.stoppedDurationMs == b.stoppedDurationMs
|
|
971
|
+
&& a.minTripSpeedMps == b.minTripSpeedMps
|
|
972
|
+
&& a.minTripDurationMs == b.minTripDurationMs
|
|
973
|
+
&& a.hardBrakeMps2 == b.hardBrakeMps2
|
|
974
|
+
&& a.rapidAccelMps2 == b.rapidAccelMps2
|
|
975
|
+
&& a.sharpTurnDegPerSec == b.sharpTurnDegPerSec
|
|
976
|
+
&& a.crashImpactKmh == b.crashImpactKmh
|
|
977
|
+
&& a.crashWindowMs == b.crashWindowMs
|
|
978
|
+
&& a.sensorFusion == b.sensorFusion
|
|
979
|
+
&& a.crashImpactG == b.crashImpactG
|
|
980
|
+
&& a.sensorCrashCooldownMs == b.sensorCrashCooldownMs
|
|
981
|
+
&& a.phoneUsageWindowMs == b.phoneUsageWindowMs
|
|
982
|
+
&& a.phoneUsageCooldownMs == b.phoneUsageCooldownMs;
|
|
983
|
+
}
|
|
984
|
+
|
|
731
985
|
@Override
|
|
732
986
|
public synchronized void registerHeadlessTask(String taskRunnerClass) {
|
|
733
987
|
logger.debug("Registering headless task");
|
|
@@ -769,6 +1023,17 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
769
1023
|
@Override
|
|
770
1024
|
public void onLocation(BackgroundLocation location) {
|
|
771
1025
|
mLastLocationTime = System.currentTimeMillis();
|
|
1026
|
+
mLastReceivedLocation = location;
|
|
1027
|
+
sLastReceivedLocation = location;
|
|
1028
|
+
|
|
1029
|
+
// v4.0 Phase 6: feed the driver-insights state machine.
|
|
1030
|
+
if (mDrivingDetector != null) {
|
|
1031
|
+
mDrivingDetector.onLocation(location);
|
|
1032
|
+
}
|
|
1033
|
+
// v4.2 Phase 8: keep sensor pipeline aware of the latest fix.
|
|
1034
|
+
if (mSensorFusion != null) {
|
|
1035
|
+
mSensorFusion.setLastLocation(location);
|
|
1036
|
+
}
|
|
772
1037
|
if (Boolean.TRUE.equals(mConfig != null ? mConfig.getShowDistance() : null)) {
|
|
773
1038
|
double lat = location.getLatitude();
|
|
774
1039
|
double lon = location.getLongitude();
|
|
@@ -889,6 +1154,40 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
|
|
|
889
1154
|
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
|
|
890
1155
|
}
|
|
891
1156
|
|
|
1157
|
+
/** v3.5 Phase 4: schedule periodic heartbeat broadcasts using {@link Config#getHeartbeatInterval()}. */
|
|
1158
|
+
private void scheduleHeartbeat() {
|
|
1159
|
+
cancelHeartbeat();
|
|
1160
|
+
if (mConfig == null) return;
|
|
1161
|
+
Integer interval = mConfig.getHeartbeatInterval();
|
|
1162
|
+
if (interval == null || interval <= 0) return;
|
|
1163
|
+
logger.debug("Scheduling heartbeat every {} ms", interval);
|
|
1164
|
+
mHeartbeatExecutor = java.util.concurrent.Executors.newSingleThreadScheduledExecutor();
|
|
1165
|
+
mHeartbeatTask = mHeartbeatExecutor.scheduleAtFixedRate(new Runnable() {
|
|
1166
|
+
@Override public void run() {
|
|
1167
|
+
try {
|
|
1168
|
+
Bundle b = new Bundle();
|
|
1169
|
+
b.putInt("action", MSG_ON_HEARTBEAT);
|
|
1170
|
+
BackgroundLocation last = mLastReceivedLocation;
|
|
1171
|
+
if (last != null) b.putParcelable("payload", last);
|
|
1172
|
+
broadcastMessage(b);
|
|
1173
|
+
} catch (Throwable t) {
|
|
1174
|
+
logger.warn("Heartbeat tick failed: {}", t.getMessage());
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}, interval, interval, java.util.concurrent.TimeUnit.MILLISECONDS);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
private void cancelHeartbeat() {
|
|
1181
|
+
if (mHeartbeatTask != null) {
|
|
1182
|
+
mHeartbeatTask.cancel(false);
|
|
1183
|
+
mHeartbeatTask = null;
|
|
1184
|
+
}
|
|
1185
|
+
if (mHeartbeatExecutor != null) {
|
|
1186
|
+
mHeartbeatExecutor.shutdownNow();
|
|
1187
|
+
mHeartbeatExecutor = null;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
892
1191
|
@Override
|
|
893
1192
|
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
|
|
894
1193
|
return super.registerReceiver(receiver, filter, null, mServiceHandler, RECEIVER_NOT_EXPORTED);
|
package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java
CHANGED
|
@@ -5,10 +5,12 @@ import android.content.Context;
|
|
|
5
5
|
import android.content.Intent;
|
|
6
6
|
import android.content.pm.PackageManager;
|
|
7
7
|
import android.os.Build;
|
|
8
|
+
import android.util.Log;
|
|
8
9
|
|
|
9
10
|
import com.marianhello.bgloc.Config;
|
|
10
11
|
|
|
11
12
|
public class LocationServiceProxy implements LocationService, LocationServiceInfo {
|
|
13
|
+
private static final String TAG = LocationServiceProxy.class.getSimpleName();
|
|
12
14
|
private final Context mContext;
|
|
13
15
|
private final LocationServiceIntentBuilder mIntentBuilder;
|
|
14
16
|
|
|
@@ -78,9 +80,19 @@ public class LocationServiceProxy implements LocationService, LocationServiceInf
|
|
|
78
80
|
Intent intent = mIntentBuilder.setCommand(CommandId.START_FOREGROUND_SERVICE).build();
|
|
79
81
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
80
82
|
if (!hasLocationPermission()) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
// Do NOT fall back to startService(): would create a non-foreground service that crashes
|
|
84
|
+
// on first location update. Caller must request the permission first.
|
|
85
|
+
Log.w(TAG, "Cannot start foreground service: ACCESS_FINE_LOCATION/COARSE_LOCATION not granted");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Note: ACCESS_BACKGROUND_LOCATION is only required when the service is started from
|
|
89
|
+
// background (e.g. BootCompletedReceiver). When called from foreground, the OS allows
|
|
90
|
+
// a location-typed FGS to run with only fine/coarse location and inherit "while-in-use".
|
|
91
|
+
try {
|
|
83
92
|
mContext.startForegroundService(intent);
|
|
93
|
+
} catch (Exception e) {
|
|
94
|
+
// Android 12+ may throw ForegroundServiceStartNotAllowedException.
|
|
95
|
+
Log.e(TAG, "startForegroundService blocked: " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
|
|
84
96
|
}
|
|
85
97
|
} else {
|
|
86
98
|
mContext.startService(intent);
|
|
@@ -140,7 +140,12 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements HttpPost
|
|
|
140
140
|
}
|
|
141
141
|
httpHeaders.put("x-batch-id", String.valueOf(batchStartMillis));
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
// For URL templating in sync mode we can only resolve static queryParams keys; per-location
|
|
144
|
+
// placeholders (like {lat}) cannot apply to a multi-location batch. If the user wants per-location
|
|
145
|
+
// URL substitution they should use httpMode="single" + url= ... (real-time) or syncMode="single".
|
|
146
|
+
String resolvedUrl = com.marianhello.bgloc.http.UrlTemplateResolver.resolve(url, null, config.getQueryParams());
|
|
147
|
+
String syncMethod = config.getSyncHttpMethod();
|
|
148
|
+
if (uploadLocations(file, resolvedUrl, httpHeaders, syncMethod)) {
|
|
144
149
|
logger.info("Batch sync successful");
|
|
145
150
|
batchManager.setBatchCompleted(batchStartMillis);
|
|
146
151
|
if (file.delete()) {
|
|
@@ -154,7 +159,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements HttpPost
|
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
private boolean uploadLocations(File file, String url, HashMap httpHeaders) {
|
|
162
|
+
private boolean uploadLocations(File file, String url, HashMap httpHeaders, String method) {
|
|
158
163
|
NotificationCompat.Builder builder = null;
|
|
159
164
|
|
|
160
165
|
if (notificationsEnabled) {
|
|
@@ -166,8 +171,29 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements HttpPost
|
|
|
166
171
|
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
|
167
172
|
}
|
|
168
173
|
|
|
174
|
+
// v3.5 Phase 4: emit syncStart event.
|
|
175
|
+
Bundle syncStart = new Bundle();
|
|
176
|
+
syncStart.putInt("action", LocationServiceImpl.MSG_ON_SYNC_START);
|
|
177
|
+
broadcastMessage(syncStart);
|
|
178
|
+
|
|
179
|
+
// Count locations being uploaded (best-effort, API-21+ safe).
|
|
180
|
+
int locationsAttempted = 0;
|
|
181
|
+
java.io.FileInputStream fis = null;
|
|
169
182
|
try {
|
|
170
|
-
|
|
183
|
+
fis = new java.io.FileInputStream(file);
|
|
184
|
+
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
185
|
+
byte[] buf = new byte[4096];
|
|
186
|
+
int n;
|
|
187
|
+
while ((n = fis.read(buf)) > 0) baos.write(buf, 0, n);
|
|
188
|
+
org.json.JSONArray arr = new org.json.JSONArray(new String(baos.toByteArray(), "UTF-8"));
|
|
189
|
+
locationsAttempted = arr.length();
|
|
190
|
+
} catch (Throwable ignored) { /* best-effort; emit 0 if we cannot read */
|
|
191
|
+
} finally {
|
|
192
|
+
if (fis != null) try { fis.close(); } catch (Exception ignored) {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
int responseCode = HttpPostService.postJSONFile(url, file, httpHeaders, this, method);
|
|
171
197
|
|
|
172
198
|
// All 2xx statuses are okay
|
|
173
199
|
boolean isStatusOkay = responseCode >= 200 && responseCode < 300;
|
|
@@ -198,6 +224,16 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements HttpPost
|
|
|
198
224
|
|
|
199
225
|
if (!isStatusOkay) {
|
|
200
226
|
logger.warn("Batch sync failed: server returned HTTP {} (check server logs or sync URL)", responseCode);
|
|
227
|
+
Bundle errBundle = new Bundle();
|
|
228
|
+
errBundle.putInt("action", LocationServiceImpl.MSG_ON_SYNC_ERROR);
|
|
229
|
+
errBundle.putInt("httpStatus", responseCode);
|
|
230
|
+
errBundle.putString("message", "HTTP " + responseCode);
|
|
231
|
+
broadcastMessage(errBundle);
|
|
232
|
+
} else {
|
|
233
|
+
Bundle okBundle = new Bundle();
|
|
234
|
+
okBundle.putInt("action", LocationServiceImpl.MSG_ON_SYNC_SUCCESS);
|
|
235
|
+
okBundle.putInt("sent", locationsAttempted);
|
|
236
|
+
broadcastMessage(okBundle);
|
|
201
237
|
}
|
|
202
238
|
|
|
203
239
|
return isStatusOkay;
|
|
@@ -208,6 +244,11 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements HttpPost
|
|
|
208
244
|
if (builder != null) {
|
|
209
245
|
builder.setContentText(currentSyncConfig.getNotificationSyncFailedText() + ": " + errMsg);
|
|
210
246
|
}
|
|
247
|
+
Bundle errBundle = new Bundle();
|
|
248
|
+
errBundle.putInt("action", LocationServiceImpl.MSG_ON_SYNC_ERROR);
|
|
249
|
+
errBundle.putInt("httpStatus", 0);
|
|
250
|
+
errBundle.putString("message", errMsg);
|
|
251
|
+
broadcastMessage(errBundle);
|
|
211
252
|
} finally {
|
|
212
253
|
logger.info("Syncing endAt: {}", System.currentTimeMillis());
|
|
213
254
|
|
|
@@ -244,6 +285,12 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements HttpPost
|
|
|
244
285
|
builder.setProgress(100, progress, false);
|
|
245
286
|
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
|
246
287
|
}
|
|
288
|
+
|
|
289
|
+
// v3.5 Phase 4: forward progress percentage to JS via syncProgress event.
|
|
290
|
+
Bundle progBundle = new Bundle();
|
|
291
|
+
progBundle.putInt("action", LocationServiceImpl.MSG_ON_SYNC_PROGRESS);
|
|
292
|
+
progBundle.putInt("progress", progress);
|
|
293
|
+
broadcastMessage(progBundle);
|
|
247
294
|
}
|
|
248
295
|
|
|
249
296
|
private void broadcastMessage(Bundle bundle) {
|
|
@@ -14,4 +14,25 @@ export enum BackgroundGeolocationEvents {
|
|
|
14
14
|
activity = 'activity',
|
|
15
15
|
stationary = 'stationary',
|
|
16
16
|
location = 'location',
|
|
17
|
+
// v3.5+
|
|
18
|
+
heartbeat = 'heartbeat',
|
|
19
|
+
syncStart = 'syncStart',
|
|
20
|
+
syncProgress = 'syncProgress',
|
|
21
|
+
syncSuccess = 'syncSuccess',
|
|
22
|
+
syncError = 'syncError',
|
|
23
|
+
// v4.0
|
|
24
|
+
tripStart = 'tripStart',
|
|
25
|
+
tripEnd = 'tripEnd',
|
|
26
|
+
moving = 'moving',
|
|
27
|
+
stopped = 'stopped',
|
|
28
|
+
speeding = 'speeding',
|
|
29
|
+
providerChange = 'providerChange',
|
|
30
|
+
sos = 'sos',
|
|
31
|
+
// v4.1
|
|
32
|
+
hardBrake = 'hardBrake',
|
|
33
|
+
rapidAcceleration = 'rapidAcceleration',
|
|
34
|
+
sharpTurn = 'sharpTurn',
|
|
35
|
+
possibleCrash = 'possibleCrash',
|
|
36
|
+
// v4.2 sensor fusion
|
|
37
|
+
phoneUsageWhileDriving = 'phoneUsageWhileDriving',
|
|
17
38
|
}
|
|
@@ -83,6 +83,69 @@ export class BackgroundGeolocationService {
|
|
|
83
83
|
return this.ensurePlugin().checkStatus(success, fail);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Extended diagnostics. Returns permissions, battery optimisation state,
|
|
88
|
+
* last fix age, pending sync count, OEM info and (on iOS) precise location /
|
|
89
|
+
* background refresh / low power flags.
|
|
90
|
+
*
|
|
91
|
+
* @since 3.5.0
|
|
92
|
+
*/
|
|
93
|
+
getDiagnostics(
|
|
94
|
+
success?: (diagnostics: any) => void,
|
|
95
|
+
fail?: (error: any) => void
|
|
96
|
+
): Promise<any> {
|
|
97
|
+
return this.ensurePlugin().getDiagnostics(success, fail);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** @since 3.6.0 */
|
|
101
|
+
isIgnoringBatteryOptimizations(
|
|
102
|
+
success?: (whitelisted: boolean) => void,
|
|
103
|
+
fail?: (error: any) => void
|
|
104
|
+
): Promise<boolean> {
|
|
105
|
+
return this.ensurePlugin().isIgnoringBatteryOptimizations(success, fail);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @since 3.6.0 */
|
|
109
|
+
requestIgnoreBatteryOptimizations(
|
|
110
|
+
success?: (whitelisted: boolean) => void,
|
|
111
|
+
fail?: (error: any) => void
|
|
112
|
+
): Promise<boolean> {
|
|
113
|
+
return this.ensurePlugin().requestIgnoreBatteryOptimizations(success, fail);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** @since 3.6.0 */
|
|
117
|
+
openBatterySettings(
|
|
118
|
+
success?: () => void,
|
|
119
|
+
fail?: (error: any) => void
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
return this.ensurePlugin().openBatterySettings(success, fail);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** @since 3.6.0 */
|
|
125
|
+
openAutoStartSettings(
|
|
126
|
+
success?: (info: { opened: boolean; manufacturer: string; screen: string }) => void,
|
|
127
|
+
fail?: (error: any) => void
|
|
128
|
+
): Promise<{ opened: boolean; manufacturer: string; screen: string }> {
|
|
129
|
+
return this.ensurePlugin().openAutoStartSettings(success, fail);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @since 3.6.0 */
|
|
133
|
+
getManufacturerHelp(
|
|
134
|
+
success?: (info: { manufacturer: string; steps: string[] }) => void,
|
|
135
|
+
fail?: (error: any) => void
|
|
136
|
+
): Promise<{ manufacturer: string; steps: string[] }> {
|
|
137
|
+
return this.ensurePlugin().getManufacturerHelp(success, fail);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** @since 4.0.0 */
|
|
141
|
+
triggerSOS(
|
|
142
|
+
payload?: { [key: string]: any },
|
|
143
|
+
success?: () => void,
|
|
144
|
+
fail?: (error: any) => void
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
return this.ensurePlugin().triggerSOS(payload, success, fail);
|
|
147
|
+
}
|
|
148
|
+
|
|
86
149
|
showAppSettings(): Promise<void> {
|
|
87
150
|
return this.ensurePlugin().showAppSettings();
|
|
88
151
|
}
|
|
@@ -13,5 +13,22 @@ export declare enum BackgroundGeolocationEvents {
|
|
|
13
13
|
start = "start",
|
|
14
14
|
activity = "activity",
|
|
15
15
|
stationary = "stationary",
|
|
16
|
-
location = "location"
|
|
16
|
+
location = "location",
|
|
17
|
+
heartbeat = "heartbeat",
|
|
18
|
+
syncStart = "syncStart",
|
|
19
|
+
syncProgress = "syncProgress",
|
|
20
|
+
syncSuccess = "syncSuccess",
|
|
21
|
+
syncError = "syncError",
|
|
22
|
+
tripStart = "tripStart",
|
|
23
|
+
tripEnd = "tripEnd",
|
|
24
|
+
moving = "moving",
|
|
25
|
+
stopped = "stopped",
|
|
26
|
+
speeding = "speeding",
|
|
27
|
+
providerChange = "providerChange",
|
|
28
|
+
sos = "sos",
|
|
29
|
+
hardBrake = "hardBrake",
|
|
30
|
+
rapidAcceleration = "rapidAcceleration",
|
|
31
|
+
sharpTurn = "sharpTurn",
|
|
32
|
+
possibleCrash = "possibleCrash",
|
|
33
|
+
phoneUsageWhileDriving = "phoneUsageWhileDriving"
|
|
17
34
|
}
|