@josuelmm/capacitor-background-geolocation 1.0.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/JosuelmmCapacitorBackgroundGeolocation.podspec +34 -0
- package/LICENSE +17 -0
- package/NOTICE.md +32 -0
- package/Package.swift +45 -0
- package/README.md +402 -0
- package/android/build.gradle +79 -0
- package/android/proguard-rules.pro +1 -0
- package/android/src/main/AndroidManifest.xml +83 -0
- package/android/src/main/java/com/evgenii/jsevaluator/HandlerWrapper.java +18 -0
- package/android/src/main/java/com/evgenii/jsevaluator/JavaScriptInterface.java +22 -0
- package/android/src/main/java/com/evgenii/jsevaluator/JsEvaluator.java +133 -0
- package/android/src/main/java/com/evgenii/jsevaluator/JsFunctionCallFormatter.java +37 -0
- package/android/src/main/java/com/evgenii/jsevaluator/WebViewWrapper.java +71 -0
- package/android/src/main/java/com/evgenii/jsevaluator/interfaces/CallJavaResultInterface.java +8 -0
- package/android/src/main/java/com/evgenii/jsevaluator/interfaces/HandlerWrapperInterface.java +5 -0
- package/android/src/main/java/com/evgenii/jsevaluator/interfaces/JsCallback.java +10 -0
- package/android/src/main/java/com/evgenii/jsevaluator/interfaces/JsEvaluatorInterface.java +18 -0
- package/android/src/main/java/com/evgenii/jsevaluator/interfaces/WebViewWrapperInterface.java +14 -0
- package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/BackgroundGeolocationPlugin.java +898 -0
- package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/ConfigMapper.java +303 -0
- package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/HeadlessTaskRegistry.java +34 -0
- package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/JsEvaluatorTaskRunner.java +63 -0
- package/android/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +699 -0
- package/android/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +103 -0
- package/android/src/main/java/com/marianhello/bgloc/Config.java +1155 -0
- package/android/src/main/java/com/marianhello/bgloc/ConnectivityListener.java +5 -0
- package/android/src/main/java/com/marianhello/bgloc/HttpPostService.java +362 -0
- package/android/src/main/java/com/marianhello/bgloc/LocationManager.java +138 -0
- package/android/src/main/java/com/marianhello/bgloc/PluginDelegate.java +45 -0
- package/android/src/main/java/com/marianhello/bgloc/PluginException.java +38 -0
- package/android/src/main/java/com/marianhello/bgloc/PostLocationTask.java +238 -0
- package/android/src/main/java/com/marianhello/bgloc/ResourceResolver.java +55 -0
- package/android/src/main/java/com/marianhello/bgloc/data/AbstractLocationTemplate.java +69 -0
- package/android/src/main/java/com/marianhello/bgloc/data/ArrayListLocationTemplate.java +88 -0
- package/android/src/main/java/com/marianhello/bgloc/data/BackgroundActivity.java +108 -0
- package/android/src/main/java/com/marianhello/bgloc/data/BackgroundLocation.java +1088 -0
- package/android/src/main/java/com/marianhello/bgloc/data/ConfigJsonMapper.java +211 -0
- package/android/src/main/java/com/marianhello/bgloc/data/ConfigurationDAO.java +13 -0
- package/android/src/main/java/com/marianhello/bgloc/data/DAOFactory.java +17 -0
- package/android/src/main/java/com/marianhello/bgloc/data/HashMapLocationTemplate.java +82 -0
- package/android/src/main/java/com/marianhello/bgloc/data/LocationDAO.java +27 -0
- package/android/src/main/java/com/marianhello/bgloc/data/LocationTemplate.java +12 -0
- package/android/src/main/java/com/marianhello/bgloc/data/LocationTemplateFactory.java +71 -0
- package/android/src/main/java/com/marianhello/bgloc/data/LocationTransform.java +19 -0
- package/android/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
- package/android/src/main/java/com/marianhello/bgloc/data/provider/ContentProviderLocationDAO.java +406 -0
- package/android/src/main/java/com/marianhello/bgloc/data/provider/LocationContentProvider.java +321 -0
- package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationContract.java +94 -0
- package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationDAO.java +227 -0
- package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationContract.java +122 -0
- package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationDAO.java +550 -0
- package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +189 -0
- package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
- package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
- package/android/src/main/java/com/marianhello/bgloc/driving/DrivingEventsDetector.java +265 -0
- package/android/src/main/java/com/marianhello/bgloc/headless/AbstractTaskRunner.java +15 -0
- package/android/src/main/java/com/marianhello/bgloc/headless/ActivityTask.java +48 -0
- package/android/src/main/java/com/marianhello/bgloc/headless/JsCallback.java +10 -0
- package/android/src/main/java/com/marianhello/bgloc/headless/LocationTask.java +60 -0
- package/android/src/main/java/com/marianhello/bgloc/headless/StationaryTask.java +25 -0
- package/android/src/main/java/com/marianhello/bgloc/headless/Task.java +8 -0
- package/android/src/main/java/com/marianhello/bgloc/headless/TaskRunner.java +5 -0
- package/android/src/main/java/com/marianhello/bgloc/headless/TaskRunnerFactory.java +8 -0
- package/android/src/main/java/com/marianhello/bgloc/http/UrlTemplateResolver.java +115 -0
- package/android/src/main/java/com/marianhello/bgloc/oem/BatteryOemHelper.java +214 -0
- package/android/src/main/java/com/marianhello/bgloc/provider/AbstractLocationProvider.java +218 -0
- package/android/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +385 -0
- package/android/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +685 -0
- package/android/src/main/java/com/marianhello/bgloc/provider/LocationProvider.java +32 -0
- package/android/src/main/java/com/marianhello/bgloc/provider/LocationProviderFactory.java +47 -0
- package/android/src/main/java/com/marianhello/bgloc/provider/ProviderDelegate.java +12 -0
- package/android/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +175 -0
- package/android/src/main/java/com/marianhello/bgloc/sensor/SensorFusionDetector.java +199 -0
- package/android/src/main/java/com/marianhello/bgloc/service/LocationService.java +16 -0
- package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +1531 -0
- package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceInfo.java +6 -0
- package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceInfoImpl.java +41 -0
- package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceIntentBuilder.java +203 -0
- package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +156 -0
- package/android/src/main/java/com/marianhello/bgloc/sync/AccountHelper.java +39 -0
- package/android/src/main/java/com/marianhello/bgloc/sync/Authenticator.java +68 -0
- package/android/src/main/java/com/marianhello/bgloc/sync/AuthenticatorService.java +28 -0
- package/android/src/main/java/com/marianhello/bgloc/sync/BatchManager.java +311 -0
- package/android/src/main/java/com/marianhello/bgloc/sync/NotificationHelper.java +148 -0
- package/android/src/main/java/com/marianhello/bgloc/sync/SyncAdapter.java +301 -0
- package/android/src/main/java/com/marianhello/bgloc/sync/SyncService.java +68 -0
- package/android/src/main/java/com/marianhello/logging/DBLogReader.java +208 -0
- package/android/src/main/java/com/marianhello/logging/LogEntry.java +99 -0
- package/android/src/main/java/com/marianhello/logging/LoggerManager.java +70 -0
- package/android/src/main/java/com/marianhello/logging/UncaughtExceptionLogger.java +36 -0
- package/android/src/main/java/com/marianhello/utils/CloneHelper.java +22 -0
- package/android/src/main/java/com/marianhello/utils/Convert.java +56 -0
- package/android/src/main/java/com/marianhello/utils/TextUtils.java +72 -0
- package/android/src/main/java/com/marianhello/utils/ToneGenerator.java +68 -0
- package/android/src/main/java/org/apache/commons/io/Charsets.java +153 -0
- package/android/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java +344 -0
- package/android/src/main/java/org/chromium/content/browser/ThreadUtils.java +134 -0
- package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlExpression.java +398 -0
- package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlSelectStatement.java +671 -0
- package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlStatement.java +29 -0
- package/android/src/main/java/ru/andremoniy/utils/TextUtils.java +61 -0
- package/android/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/android/src/main/res/values/strings.xml +15 -0
- package/android/src/main/res/xml/authenticator.xml +7 -0
- package/android/src/main/res/xml/syncadapter.xml +9 -0
- package/dist/esm/definitions.d.ts +1052 -0
- package/dist/esm/definitions.js +142 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +92 -0
- package/dist/esm/web.js +242 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +415 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +418 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin-Bridging-Header.h +18 -0
- package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin.m +52 -0
- package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin.swift +750 -0
- package/ios/Tests/BackgroundGeolocationPluginTests/BackgroundGeolocationPluginTests.swift +12 -0
- package/ios/common/BackgroundGeolocation/CocoaLumberjack.h +1945 -0
- package/ios/common/BackgroundGeolocation/CocoaLumberjack.m +5255 -0
- package/ios/common/BackgroundGeolocation/FMDB.h +2357 -0
- package/ios/common/BackgroundGeolocation/FMDB.m +2672 -0
- package/ios/common/BackgroundGeolocation/FMDBLogger.h +42 -0
- package/ios/common/BackgroundGeolocation/FMDBLogger.m +264 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTUHeadingRequest.h +41 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTUHeadingRequest.m +68 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager+Internal.h +33 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager.h +178 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager.m +1025 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequest.h +103 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequest.m +238 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequestDefines.h +163 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTURequestIDGenerator.h +39 -0
- package/ios/common/BackgroundGeolocation/INTULocationManager/INTURequestIDGenerator.m +37 -0
- package/ios/common/BackgroundGeolocation/MAURAbstractLocationProvider.h +51 -0
- package/ios/common/BackgroundGeolocation/MAURAbstractLocationProvider.m +53 -0
- package/ios/common/BackgroundGeolocation/MAURActivity.h +23 -0
- package/ios/common/BackgroundGeolocation/MAURActivity.m +52 -0
- package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.h +18 -0
- package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.m +340 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +88 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +1193 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundSync.h +46 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +283 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundTaskManager.h +25 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundTaskManager.m +105 -0
- package/ios/common/BackgroundGeolocation/MAURConfig.h +99 -0
- package/ios/common/BackgroundGeolocation/MAURConfig.m +636 -0
- package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +53 -0
- package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +54 -0
- package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.h +20 -0
- package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +550 -0
- package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.h +17 -0
- package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +124 -0
- package/ios/common/BackgroundGeolocation/MAURLocation.h +73 -0
- package/ios/common/BackgroundGeolocation/MAURLocation.m +392 -0
- package/ios/common/BackgroundGeolocation/MAURLocationContract.h +38 -0
- package/ios/common/BackgroundGeolocation/MAURLocationContract.m +39 -0
- package/ios/common/BackgroundGeolocation/MAURLocationManager.h +53 -0
- package/ios/common/BackgroundGeolocation/MAURLocationManager.m +305 -0
- package/ios/common/BackgroundGeolocation/MAURLogReader.h +26 -0
- package/ios/common/BackgroundGeolocation/MAURLogReader.m +122 -0
- package/ios/common/BackgroundGeolocation/MAURLogging.h +19 -0
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +53 -0
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +367 -0
- package/ios/common/BackgroundGeolocation/MAURProviderDelegate.h +52 -0
- package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.h +18 -0
- package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.m +138 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.h +26 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.m +335 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteHelper.h +57 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteHelper.m +93 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.h +52 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.m +520 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteOpenHelper.h +32 -0
- package/ios/common/BackgroundGeolocation/MAURSQLiteOpenHelper.m +276 -0
- 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/MAURUncaughtExceptionLogger.h +20 -0
- package/ios/common/BackgroundGeolocation/MAURUncaughtExceptionLogger.m +62 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.h +31 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
- package/ios/common/BackgroundGeolocation/Reachability.h +102 -0
- package/ios/common/BackgroundGeolocation/Reachability.m +475 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/README.md +170 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/ext/NSString+ZIMString.h +55 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/ext/NSString+ZIMString.m +47 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlDataManipulationCommand.h +27 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlExpression.h +250 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlExpression.m +259 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlSelectStatement.h +360 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlSelectStatement.m +427 -0
- package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlStatement.h +37 -0
- package/ios/common/BackgroundGeolocation/module.modulemap +16 -0
- package/package.json +82 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
package com.marianhello.bgloc.driving;
|
|
2
|
+
|
|
3
|
+
import com.marianhello.bgloc.data.BackgroundLocation;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* v4.0 Phase 6 — Driver insights state machine (GPS-only).
|
|
7
|
+
*
|
|
8
|
+
* Pure-Java helper, no Android imports. Hosted by {@code LocationServiceImpl}, which
|
|
9
|
+
* feeds it every received location and surfaces emitted events via the plugin
|
|
10
|
+
* {@code MSG_ON_*} broadcast pipeline.
|
|
11
|
+
*
|
|
12
|
+
* Heuristics:
|
|
13
|
+
* moving = speed > minMovingSpeed
|
|
14
|
+
* stopped = !moving for stoppedDuration ms
|
|
15
|
+
* tripStart = stopped → moving with speed >= minTripSpeed sustained for minTripDuration ms
|
|
16
|
+
* tripEnd = moving → stopped (after a tripStart)
|
|
17
|
+
* speeding = first crossing above speedLimit (km/h); rearms on drop below
|
|
18
|
+
*
|
|
19
|
+
* Sensor-fusion events (hardBrake / sharpTurn / possibleCrash) are intentionally NOT
|
|
20
|
+
* implemented in this class. They require linear acceleration + gyroscope sampling and
|
|
21
|
+
* are planned for v4.1 in a separate {@code SensorFusionDetector}.
|
|
22
|
+
*/
|
|
23
|
+
public class DrivingEventsDetector {
|
|
24
|
+
|
|
25
|
+
public interface Listener {
|
|
26
|
+
void onMoving(BackgroundLocation location);
|
|
27
|
+
void onStopped(BackgroundLocation location);
|
|
28
|
+
void onTripStart(BackgroundLocation location);
|
|
29
|
+
/** distance in meters, durationMs in milliseconds. */
|
|
30
|
+
void onTripEnd(BackgroundLocation location, double distance, long durationMs);
|
|
31
|
+
void onSpeeding(BackgroundLocation location, double speedKmh, double limitKmh);
|
|
32
|
+
void onProviderChange(String provider);
|
|
33
|
+
// v4.1 GPS-derived driving events
|
|
34
|
+
/** GPS-derived deceleration (m/s²). Negative number, more negative = harder brake. */
|
|
35
|
+
void onHardBrake(BackgroundLocation location, double decelMps2);
|
|
36
|
+
/** GPS-derived acceleration (m/s²). */
|
|
37
|
+
void onRapidAcceleration(BackgroundLocation location, double accelMps2);
|
|
38
|
+
/** Bearing change rate (deg/s). */
|
|
39
|
+
void onSharpTurn(BackgroundLocation location, double degPerSec);
|
|
40
|
+
/** Velocity drop in km/h within {@code crashWindowMs} while tripActive. */
|
|
41
|
+
void onPossibleCrash(BackgroundLocation location, double velocityDropKmh);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static class Config {
|
|
45
|
+
public boolean enabled = false;
|
|
46
|
+
public double speedLimitKmh = 0; // 0 disables speeding
|
|
47
|
+
public double minMovingSpeedMps = 1.0; // ~3.6 km/h
|
|
48
|
+
public long stoppedDurationMs = 60_000;
|
|
49
|
+
public double minTripSpeedMps = 3.0; // ~10.8 km/h
|
|
50
|
+
public long minTripDurationMs = 30_000;
|
|
51
|
+
// v4.1 GPS-derived driving events. 0 disables each one.
|
|
52
|
+
public double hardBrakeMps2 = 3.5; // -m/s² threshold (positive value)
|
|
53
|
+
public double rapidAccelMps2 = 3.5; // m/s² threshold
|
|
54
|
+
public double sharpTurnDegPerSec = 30; // deg/sec, requires speed > 5 m/s
|
|
55
|
+
public double crashImpactKmh = 25; // velocity drop within crashWindow
|
|
56
|
+
public long crashWindowMs = 2_000; // window to evaluate the velocity drop
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private final Listener listener;
|
|
60
|
+
private Config cfg = new Config();
|
|
61
|
+
|
|
62
|
+
// State
|
|
63
|
+
private boolean isMoving = false;
|
|
64
|
+
private boolean tripActive = false;
|
|
65
|
+
private long tripStartedAt = 0;
|
|
66
|
+
private double tripDistanceMeters = 0;
|
|
67
|
+
private double tripStartLat, tripStartLon;
|
|
68
|
+
private boolean hasTripStartCoord = false;
|
|
69
|
+
|
|
70
|
+
private long aboveTripSpeedSince = 0; // first sample with speed >= minTripSpeed
|
|
71
|
+
private long belowMovingSinceMs = 0; // first sample with speed < minMovingSpeed
|
|
72
|
+
|
|
73
|
+
private boolean wasSpeeding = false;
|
|
74
|
+
private String lastProvider;
|
|
75
|
+
|
|
76
|
+
private double prevLat, prevLon;
|
|
77
|
+
private boolean hasPrev = false;
|
|
78
|
+
|
|
79
|
+
// v4.1 GPS-derived deltas
|
|
80
|
+
private double prevSpeedMps = 0.0;
|
|
81
|
+
private long prevSpeedAt = 0L;
|
|
82
|
+
private double prevBearingDeg = 0.0;
|
|
83
|
+
private boolean hasPrevBearing = false;
|
|
84
|
+
private long prevBearingAt = 0L;
|
|
85
|
+
/** Cooldown so we don't refire the same event on every fix in a sustained brake. */
|
|
86
|
+
private long lastHardBrakeAt = 0L, lastRapidAccelAt = 0L, lastSharpTurnAt = 0L, lastCrashAt = 0L;
|
|
87
|
+
private static final long DRIVING_EVENT_COOLDOWN_MS = 4_000L;
|
|
88
|
+
|
|
89
|
+
public DrivingEventsDetector(Listener listener) {
|
|
90
|
+
this.listener = listener;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public synchronized void setConfig(Config c) {
|
|
94
|
+
if (c != null) this.cfg = c;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Reset internal state. Called when service stops. */
|
|
98
|
+
public synchronized void reset() {
|
|
99
|
+
prevSpeedMps = 0.0;
|
|
100
|
+
prevSpeedAt = 0L;
|
|
101
|
+
prevBearingDeg = 0.0;
|
|
102
|
+
hasPrevBearing = false;
|
|
103
|
+
prevBearingAt = 0L;
|
|
104
|
+
lastHardBrakeAt = lastRapidAccelAt = lastSharpTurnAt = lastCrashAt = 0L;
|
|
105
|
+
isMoving = false;
|
|
106
|
+
tripActive = false;
|
|
107
|
+
tripStartedAt = 0;
|
|
108
|
+
tripDistanceMeters = 0;
|
|
109
|
+
hasTripStartCoord = false;
|
|
110
|
+
aboveTripSpeedSince = 0;
|
|
111
|
+
belowMovingSinceMs = 0;
|
|
112
|
+
wasSpeeding = false;
|
|
113
|
+
lastProvider = null;
|
|
114
|
+
hasPrev = false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public synchronized void onLocation(BackgroundLocation loc) {
|
|
118
|
+
if (!cfg.enabled || loc == null) return;
|
|
119
|
+
long now = System.currentTimeMillis();
|
|
120
|
+
double speed = loc.hasSpeed() ? loc.getSpeed() : 0.0;
|
|
121
|
+
|
|
122
|
+
// Provider change
|
|
123
|
+
String provider = loc.getProvider();
|
|
124
|
+
if (provider != null && !provider.equals(lastProvider)) {
|
|
125
|
+
lastProvider = provider;
|
|
126
|
+
if (listener != null) listener.onProviderChange(provider);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Distance accumulator (very simple: planar haversine approximation via
|
|
130
|
+
// Location.distanceBetween is not used here to keep this class platform-free;
|
|
131
|
+
// consumer can swap to BackgroundLocation.distanceTo in onTripEnd if desired).
|
|
132
|
+
double curLat = loc.getLatitude();
|
|
133
|
+
double curLon = loc.getLongitude();
|
|
134
|
+
if (hasPrev && tripActive) {
|
|
135
|
+
tripDistanceMeters += haversineMeters(prevLat, prevLon, curLat, curLon);
|
|
136
|
+
}
|
|
137
|
+
prevLat = curLat;
|
|
138
|
+
prevLon = curLon;
|
|
139
|
+
hasPrev = true;
|
|
140
|
+
|
|
141
|
+
// Moving / stopped state
|
|
142
|
+
boolean nowMoving = speed >= cfg.minMovingSpeedMps;
|
|
143
|
+
if (nowMoving) {
|
|
144
|
+
belowMovingSinceMs = 0;
|
|
145
|
+
if (!isMoving) {
|
|
146
|
+
isMoving = true;
|
|
147
|
+
if (listener != null) listener.onMoving(loc);
|
|
148
|
+
}
|
|
149
|
+
// Trip start arming
|
|
150
|
+
if (!tripActive) {
|
|
151
|
+
if (speed >= cfg.minTripSpeedMps) {
|
|
152
|
+
if (aboveTripSpeedSince == 0) aboveTripSpeedSince = now;
|
|
153
|
+
if (now - aboveTripSpeedSince >= cfg.minTripDurationMs) {
|
|
154
|
+
tripActive = true;
|
|
155
|
+
tripStartedAt = now;
|
|
156
|
+
tripDistanceMeters = 0;
|
|
157
|
+
tripStartLat = curLat;
|
|
158
|
+
tripStartLon = curLon;
|
|
159
|
+
hasTripStartCoord = true;
|
|
160
|
+
if (listener != null) listener.onTripStart(loc);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
aboveTripSpeedSince = 0;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
aboveTripSpeedSince = 0;
|
|
168
|
+
if (belowMovingSinceMs == 0) belowMovingSinceMs = now;
|
|
169
|
+
if (isMoving && (now - belowMovingSinceMs) >= cfg.stoppedDurationMs) {
|
|
170
|
+
isMoving = false;
|
|
171
|
+
if (listener != null) listener.onStopped(loc);
|
|
172
|
+
if (tripActive) {
|
|
173
|
+
long durMs = now - tripStartedAt;
|
|
174
|
+
double dist = tripDistanceMeters;
|
|
175
|
+
tripActive = false;
|
|
176
|
+
if (listener != null) listener.onTripEnd(loc, dist, durMs);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Speeding (km/h)
|
|
182
|
+
if (cfg.speedLimitKmh > 0) {
|
|
183
|
+
double kmh = speed * 3.6;
|
|
184
|
+
if (kmh > cfg.speedLimitKmh) {
|
|
185
|
+
if (!wasSpeeding) {
|
|
186
|
+
wasSpeeding = true;
|
|
187
|
+
if (listener != null) listener.onSpeeding(loc, kmh, cfg.speedLimitKmh);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
// Rearm: emit again on next crossing.
|
|
191
|
+
wasSpeeding = false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// v4.1 GPS-derived driving events (only meaningful during an active trip)
|
|
196
|
+
if (tripActive && prevSpeedAt > 0) {
|
|
197
|
+
long dtMs = now - prevSpeedAt;
|
|
198
|
+
if (dtMs > 0 && dtMs <= 5_000) {
|
|
199
|
+
double dt = dtMs / 1000.0;
|
|
200
|
+
double dv = speed - prevSpeedMps; // m/s
|
|
201
|
+
double accel = dv / dt; // m/s²
|
|
202
|
+
|
|
203
|
+
if (cfg.hardBrakeMps2 > 0
|
|
204
|
+
&& accel <= -cfg.hardBrakeMps2
|
|
205
|
+
&& (now - lastHardBrakeAt) >= DRIVING_EVENT_COOLDOWN_MS) {
|
|
206
|
+
lastHardBrakeAt = now;
|
|
207
|
+
if (listener != null) listener.onHardBrake(loc, accel);
|
|
208
|
+
}
|
|
209
|
+
if (cfg.rapidAccelMps2 > 0
|
|
210
|
+
&& accel >= cfg.rapidAccelMps2
|
|
211
|
+
&& (now - lastRapidAccelAt) >= DRIVING_EVENT_COOLDOWN_MS) {
|
|
212
|
+
lastRapidAccelAt = now;
|
|
213
|
+
if (listener != null) listener.onRapidAcceleration(loc, accel);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Possible crash: sustained velocity drop greater than crashImpactKmh in <= crashWindow.
|
|
217
|
+
if (cfg.crashImpactKmh > 0 && dtMs <= cfg.crashWindowMs) {
|
|
218
|
+
double dropKmh = (prevSpeedMps - speed) * 3.6; // positive when slowing down
|
|
219
|
+
if (dropKmh >= cfg.crashImpactKmh
|
|
220
|
+
&& speed < 1.5 // ended near stop
|
|
221
|
+
&& prevSpeedMps * 3.6 >= cfg.crashImpactKmh
|
|
222
|
+
&& (now - lastCrashAt) >= DRIVING_EVENT_COOLDOWN_MS) {
|
|
223
|
+
lastCrashAt = now;
|
|
224
|
+
if (listener != null) listener.onPossibleCrash(loc, dropKmh);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Sharp turn (bearing change rate) — requires meaningful speed to avoid GPS jitter.
|
|
231
|
+
if (cfg.sharpTurnDegPerSec > 0 && loc.hasBearing() && speed >= 5.0 && hasPrevBearing) {
|
|
232
|
+
long dtMs = now - prevBearingAt;
|
|
233
|
+
if (dtMs > 0 && dtMs <= 5_000) {
|
|
234
|
+
double bearing = loc.getBearing();
|
|
235
|
+
double diff = Math.abs(bearing - prevBearingDeg);
|
|
236
|
+
if (diff > 180) diff = 360 - diff;
|
|
237
|
+
double rate = diff * 1000.0 / dtMs;
|
|
238
|
+
if (rate >= cfg.sharpTurnDegPerSec
|
|
239
|
+
&& (now - lastSharpTurnAt) >= DRIVING_EVENT_COOLDOWN_MS) {
|
|
240
|
+
lastSharpTurnAt = now;
|
|
241
|
+
if (listener != null) listener.onSharpTurn(loc, rate);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
prevBearingDeg = loc.getBearing();
|
|
245
|
+
prevBearingAt = now;
|
|
246
|
+
} else if (loc.hasBearing()) {
|
|
247
|
+
prevBearingDeg = loc.getBearing();
|
|
248
|
+
prevBearingAt = now;
|
|
249
|
+
hasPrevBearing = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
prevSpeedMps = speed;
|
|
253
|
+
prevSpeedAt = now;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private static double haversineMeters(double lat1, double lon1, double lat2, double lon2) {
|
|
257
|
+
double R = 6371000.0;
|
|
258
|
+
double dLat = Math.toRadians(lat2 - lat1);
|
|
259
|
+
double dLon = Math.toRadians(lon2 - lon1);
|
|
260
|
+
double a = Math.sin(dLat/2) * Math.sin(dLat/2)
|
|
261
|
+
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
|
|
262
|
+
* Math.sin(dLon/2) * Math.sin(dLon/2);
|
|
263
|
+
return 2 * R * Math.asin(Math.sqrt(a));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.marianhello.bgloc.headless;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
|
|
5
|
+
public abstract class AbstractTaskRunner implements TaskRunner {
|
|
6
|
+
protected Context mContext;
|
|
7
|
+
|
|
8
|
+
public AbstractTaskRunner() {}
|
|
9
|
+
|
|
10
|
+
public abstract void runTask(Task task);
|
|
11
|
+
|
|
12
|
+
public void setContext(Context context) {
|
|
13
|
+
mContext = context;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package com.marianhello.bgloc.headless;
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle;
|
|
4
|
+
|
|
5
|
+
import com.marianhello.bgloc.data.BackgroundActivity;
|
|
6
|
+
|
|
7
|
+
import org.json.JSONException;
|
|
8
|
+
|
|
9
|
+
public abstract class ActivityTask extends Task {
|
|
10
|
+
private BackgroundActivity mActivity;
|
|
11
|
+
|
|
12
|
+
public ActivityTask(BackgroundActivity activity) {
|
|
13
|
+
mActivity = activity;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Override
|
|
17
|
+
public String getName() {
|
|
18
|
+
return "activity";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
public Bundle getBundle() {
|
|
23
|
+
Bundle bundle = new Bundle();
|
|
24
|
+
Bundle params = new Bundle();
|
|
25
|
+
|
|
26
|
+
params.putInt("confidence", mActivity.getConfidence());
|
|
27
|
+
params.putString("type", BackgroundActivity.getActivityString(mActivity.getType()));
|
|
28
|
+
|
|
29
|
+
bundle.putString("name", getName());
|
|
30
|
+
bundle.putBundle("params", params);
|
|
31
|
+
return bundle;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Override
|
|
35
|
+
public String toString() {
|
|
36
|
+
if (mActivity == null) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
return mActivity.toJSONObject().toString();
|
|
42
|
+
} catch (JSONException e) {
|
|
43
|
+
onError("Error processing params: " + e.getMessage());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package com.marianhello.bgloc.headless;
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle;
|
|
4
|
+
|
|
5
|
+
import com.marianhello.bgloc.data.BackgroundLocation;
|
|
6
|
+
|
|
7
|
+
import org.json.JSONException;
|
|
8
|
+
|
|
9
|
+
public abstract class LocationTask extends Task {
|
|
10
|
+
protected BackgroundLocation mLocation;
|
|
11
|
+
|
|
12
|
+
public LocationTask(BackgroundLocation location) {
|
|
13
|
+
mLocation = location;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Override
|
|
17
|
+
public String getName() {
|
|
18
|
+
return "location";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
public Bundle getBundle() {
|
|
23
|
+
Bundle bundle = new Bundle();
|
|
24
|
+
Bundle params = new Bundle();
|
|
25
|
+
|
|
26
|
+
params.putString("provider", mLocation.getProvider());
|
|
27
|
+
params.putInt("locationProvider", mLocation.getLocationProvider());
|
|
28
|
+
params.putLong("time", mLocation.getTime());
|
|
29
|
+
params.putDouble("latitude", mLocation.getLatitude());
|
|
30
|
+
params.putDouble("longitude", mLocation.getLongitude());
|
|
31
|
+
if (mLocation.hasAccuracy()) params.putFloat("accuracy", mLocation.getAccuracy());
|
|
32
|
+
if (mLocation.hasVerticalAccuracy()) params.putFloat("accuracy", mLocation.getVerticalAccuracy());
|
|
33
|
+
if (mLocation.hasSpeed()) params.putFloat("speed", mLocation.getSpeed());
|
|
34
|
+
if (mLocation.hasAltitude()) params.putDouble("altitude", mLocation.getAltitude());
|
|
35
|
+
if (mLocation.hasBearing()) params.putFloat("bearing", mLocation.getBearing());
|
|
36
|
+
if (mLocation.hasRadius()) params.putFloat("radius", mLocation.getRadius());
|
|
37
|
+
if (mLocation.hasIsFromMockProvider()) params.putBoolean("isFromMockProvider", mLocation.isFromMockProvider());
|
|
38
|
+
if (mLocation.hasMockLocationsEnabled()) params.putBoolean("mockLocationsEnabled", mLocation.areMockLocationsEnabled());
|
|
39
|
+
|
|
40
|
+
bundle.putString("name", getName());
|
|
41
|
+
bundle.putBundle("params", params);
|
|
42
|
+
|
|
43
|
+
return bundle;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Override
|
|
47
|
+
public String toString() {
|
|
48
|
+
if (mLocation == null) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return mLocation.toJSONObject().toString();
|
|
54
|
+
} catch (JSONException e) {
|
|
55
|
+
onError("Error processing params: " + e.getMessage());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package com.marianhello.bgloc.headless;
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle;
|
|
4
|
+
|
|
5
|
+
import com.marianhello.bgloc.data.BackgroundLocation;
|
|
6
|
+
|
|
7
|
+
import org.json.JSONException;
|
|
8
|
+
|
|
9
|
+
public abstract class StationaryTask extends LocationTask {
|
|
10
|
+
public StationaryTask(BackgroundLocation location) {
|
|
11
|
+
super(location);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Override
|
|
15
|
+
public String getName() {
|
|
16
|
+
return "stationary";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Override
|
|
20
|
+
public Bundle getBundle() {
|
|
21
|
+
Bundle bundle = super.getBundle();
|
|
22
|
+
|
|
23
|
+
return bundle;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
package com.marianhello.bgloc.headless;
|
|
2
|
+
|
|
3
|
+
public class TaskRunnerFactory {
|
|
4
|
+
|
|
5
|
+
public TaskRunner getTaskRunner(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
|
6
|
+
return (TaskRunner) Class.forName(className).newInstance();
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package com.marianhello.bgloc.http;
|
|
2
|
+
|
|
3
|
+
import com.marianhello.bgloc.data.BackgroundLocation;
|
|
4
|
+
|
|
5
|
+
import java.text.SimpleDateFormat;
|
|
6
|
+
import java.util.Date;
|
|
7
|
+
import java.util.HashMap;
|
|
8
|
+
import java.util.Locale;
|
|
9
|
+
import java.util.Map;
|
|
10
|
+
import java.util.TimeZone;
|
|
11
|
+
import java.util.regex.Matcher;
|
|
12
|
+
import java.util.regex.Pattern;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolves placeholders like {lat}, {lon}, {timestamp_iso}, {device_id}, ...
|
|
16
|
+
* in a URL template using a single BackgroundLocation and an optional queryParams map.
|
|
17
|
+
*
|
|
18
|
+
* Placeholders not found in the location/queryParams are left as-is so that
|
|
19
|
+
* partial templates (e.g. only static keys for batch mode) keep working.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* String url = UrlTemplateResolver.resolve(template, location, queryParams);
|
|
23
|
+
* // location may be null for batch mode (only queryParams keys are resolved).
|
|
24
|
+
*/
|
|
25
|
+
public final class UrlTemplateResolver {
|
|
26
|
+
|
|
27
|
+
private static final Pattern PLACEHOLDER = Pattern.compile("\\{([a-zA-Z0-9_]+)\\}");
|
|
28
|
+
|
|
29
|
+
private UrlTemplateResolver() { /* no instances */ }
|
|
30
|
+
|
|
31
|
+
public static String resolve(String template, BackgroundLocation location, Map<String, ?> queryParams) {
|
|
32
|
+
if (template == null || template.isEmpty()) return template;
|
|
33
|
+
Map<String, String> ctx = buildContext(location, queryParams);
|
|
34
|
+
Matcher m = PLACEHOLDER.matcher(template);
|
|
35
|
+
StringBuffer sb = new StringBuffer(template.length());
|
|
36
|
+
while (m.find()) {
|
|
37
|
+
String key = m.group(1);
|
|
38
|
+
String value = ctx.get(key);
|
|
39
|
+
if (value != null) {
|
|
40
|
+
m.appendReplacement(sb, Matcher.quoteReplacement(urlEncode(value)));
|
|
41
|
+
} else {
|
|
42
|
+
// Leave placeholder as-is if no value available.
|
|
43
|
+
m.appendReplacement(sb, Matcher.quoteReplacement(m.group(0)));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
m.appendTail(sb);
|
|
47
|
+
return sb.toString();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static boolean hasPlaceholders(String template) {
|
|
51
|
+
if (template == null) return false;
|
|
52
|
+
return PLACEHOLDER.matcher(template).find();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private static Map<String, String> buildContext(BackgroundLocation loc, Map<String, ?> queryParams) {
|
|
56
|
+
Map<String, String> ctx = new HashMap<String, String>();
|
|
57
|
+
|
|
58
|
+
// queryParams first so location-derived values can override if user wants
|
|
59
|
+
if (queryParams != null) {
|
|
60
|
+
for (Map.Entry<String, ?> e : queryParams.entrySet()) {
|
|
61
|
+
if (e.getValue() != null) {
|
|
62
|
+
ctx.put(e.getKey(), String.valueOf(e.getValue()));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (loc != null) {
|
|
68
|
+
ctx.put("latitude", String.valueOf(loc.getLatitude()));
|
|
69
|
+
ctx.put("longitude", String.valueOf(loc.getLongitude()));
|
|
70
|
+
ctx.put("lat", String.valueOf(loc.getLatitude()));
|
|
71
|
+
ctx.put("lon", String.valueOf(loc.getLongitude()));
|
|
72
|
+
|
|
73
|
+
long timeMs = loc.getTime();
|
|
74
|
+
ctx.put("time", String.valueOf(timeMs));
|
|
75
|
+
ctx.put("timestamp", String.valueOf(timeMs));
|
|
76
|
+
ctx.put("timestamp_iso", isoUtc(timeMs));
|
|
77
|
+
|
|
78
|
+
if (loc.hasSpeed()) ctx.put("speed", String.valueOf(loc.getSpeed()));
|
|
79
|
+
if (loc.hasAltitude()) ctx.put("altitude", String.valueOf(loc.getAltitude()));
|
|
80
|
+
if (loc.hasBearing()) ctx.put("bearing", String.valueOf(loc.getBearing()));
|
|
81
|
+
if (loc.hasAccuracy()) ctx.put("accuracy", String.valueOf(loc.getAccuracy()));
|
|
82
|
+
|
|
83
|
+
if (loc.getProvider() != null) ctx.put("provider", loc.getProvider());
|
|
84
|
+
// is_moving derived from speed when available (>0.5 m/s ~ walking pace).
|
|
85
|
+
if (loc.hasSpeed()) {
|
|
86
|
+
ctx.put("is_moving", loc.getSpeed() > 0.5f ? "true" : "false");
|
|
87
|
+
}
|
|
88
|
+
// {activity} is not produced by BackgroundLocation by default; the user can supply
|
|
89
|
+
// a value via queryParams (already populated above).
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return ctx;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* URL-encode a placeholder value for use in URL paths and query strings.
|
|
97
|
+
* Spaces become %20, not + (which is form-encoding).
|
|
98
|
+
*/
|
|
99
|
+
private static String urlEncode(String s) {
|
|
100
|
+
if (s == null) return "";
|
|
101
|
+
try {
|
|
102
|
+
String enc = java.net.URLEncoder.encode(s, "UTF-8");
|
|
103
|
+
// URLEncoder uses application/x-www-form-urlencoded; convert "+" back to "%20" for URL safety.
|
|
104
|
+
return enc.replace("+", "%20");
|
|
105
|
+
} catch (Exception e) {
|
|
106
|
+
return s;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private static String isoUtc(long ms) {
|
|
111
|
+
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
|
112
|
+
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
113
|
+
return sdf.format(new Date(ms));
|
|
114
|
+
}
|
|
115
|
+
}
|