@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
|
@@ -24,6 +24,7 @@ import org.json.JSONObject;
|
|
|
24
24
|
|
|
25
25
|
import java.util.HashMap;
|
|
26
26
|
import java.util.Iterator;
|
|
27
|
+
import java.util.Locale;
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Config class
|
|
@@ -71,6 +72,39 @@ public class Config implements Parcelable
|
|
|
71
72
|
private Boolean enableWatchdog;
|
|
72
73
|
private Boolean showTime;
|
|
73
74
|
private Boolean showDistance;
|
|
75
|
+
// v3.3 (Phase 2): backend-agnostic HTTP transport
|
|
76
|
+
private String httpMethod; // POST | GET | PUT | PATCH (default POST)
|
|
77
|
+
private String syncHttpMethod; // POST | GET | PUT | PATCH (default POST)
|
|
78
|
+
private String httpMode; // batch | single (default batch)
|
|
79
|
+
private String syncMode; // batch | single (default batch)
|
|
80
|
+
private HashMap queryParams; // static placeholder values for URL templating
|
|
81
|
+
// v3.5 (Phase 4): diagnostics
|
|
82
|
+
private Integer heartbeatInterval; // ms; 0 disables heartbeat events
|
|
83
|
+
private String mockLocationPolicy; // allow | flag | drop (default allow)
|
|
84
|
+
// v4.0 (Phase 6): driver insights
|
|
85
|
+
private DrivingEventsOptions drivingEvents;
|
|
86
|
+
|
|
87
|
+
/** v4.0 Phase 6 + v4.1: driver-insights configuration. Plain holder; no Parcelable to keep this class diff small. */
|
|
88
|
+
public static class DrivingEventsOptions {
|
|
89
|
+
public boolean enabled = false;
|
|
90
|
+
public double speedLimitKmh = 0;
|
|
91
|
+
public double minMovingSpeedMps = 1.0;
|
|
92
|
+
public long stoppedDurationMs = 60_000L;
|
|
93
|
+
public double minTripSpeedMps = 3.0;
|
|
94
|
+
public long minTripDurationMs = 30_000L;
|
|
95
|
+
// v4.1 GPS-derived sensor-like events. 0 disables each one.
|
|
96
|
+
public double hardBrakeMps2 = 3.5;
|
|
97
|
+
public double rapidAccelMps2 = 3.5;
|
|
98
|
+
public double sharpTurnDegPerSec = 30;
|
|
99
|
+
public double crashImpactKmh = 25;
|
|
100
|
+
public long crashWindowMs = 2_000L;
|
|
101
|
+
// v4.2 sensor fusion (real accelerometer + gyroscope).
|
|
102
|
+
public boolean sensorFusion = false;
|
|
103
|
+
public double crashImpactG = 3.0; // |a| threshold for sensor crash, in g
|
|
104
|
+
public long sensorCrashCooldownMs = 10_000L;
|
|
105
|
+
public long phoneUsageWindowMs = 4_000L;
|
|
106
|
+
public long phoneUsageCooldownMs = 60_000L;
|
|
107
|
+
}
|
|
74
108
|
|
|
75
109
|
public Config () {
|
|
76
110
|
}
|
|
@@ -108,6 +142,33 @@ public class Config implements Parcelable
|
|
|
108
142
|
this.enableWatchdog = config.enableWatchdog;
|
|
109
143
|
this.showTime = config.showTime;
|
|
110
144
|
this.showDistance = config.showDistance;
|
|
145
|
+
this.httpMethod = config.httpMethod;
|
|
146
|
+
this.syncHttpMethod = config.syncHttpMethod;
|
|
147
|
+
this.httpMode = config.httpMode;
|
|
148
|
+
this.syncMode = config.syncMode;
|
|
149
|
+
this.queryParams = CloneHelper.deepCopy(config.queryParams);
|
|
150
|
+
this.heartbeatInterval = config.heartbeatInterval;
|
|
151
|
+
this.mockLocationPolicy = config.mockLocationPolicy;
|
|
152
|
+
if (config.drivingEvents != null) {
|
|
153
|
+
DrivingEventsOptions de = new DrivingEventsOptions();
|
|
154
|
+
de.enabled = config.drivingEvents.enabled;
|
|
155
|
+
de.speedLimitKmh = config.drivingEvents.speedLimitKmh;
|
|
156
|
+
de.minMovingSpeedMps = config.drivingEvents.minMovingSpeedMps;
|
|
157
|
+
de.stoppedDurationMs = config.drivingEvents.stoppedDurationMs;
|
|
158
|
+
de.minTripSpeedMps = config.drivingEvents.minTripSpeedMps;
|
|
159
|
+
de.minTripDurationMs = config.drivingEvents.minTripDurationMs;
|
|
160
|
+
de.hardBrakeMps2 = config.drivingEvents.hardBrakeMps2;
|
|
161
|
+
de.rapidAccelMps2 = config.drivingEvents.rapidAccelMps2;
|
|
162
|
+
de.sharpTurnDegPerSec = config.drivingEvents.sharpTurnDegPerSec;
|
|
163
|
+
de.crashImpactKmh = config.drivingEvents.crashImpactKmh;
|
|
164
|
+
de.crashWindowMs = config.drivingEvents.crashWindowMs;
|
|
165
|
+
de.sensorFusion = config.drivingEvents.sensorFusion;
|
|
166
|
+
de.crashImpactG = config.drivingEvents.crashImpactG;
|
|
167
|
+
de.sensorCrashCooldownMs = config.drivingEvents.sensorCrashCooldownMs;
|
|
168
|
+
de.phoneUsageWindowMs = config.drivingEvents.phoneUsageWindowMs;
|
|
169
|
+
de.phoneUsageCooldownMs = config.drivingEvents.phoneUsageCooldownMs;
|
|
170
|
+
this.drivingEvents = de;
|
|
171
|
+
}
|
|
111
172
|
if (config.template instanceof AbstractLocationTemplate) {
|
|
112
173
|
this.template = ((AbstractLocationTemplate)config.template).clone();
|
|
113
174
|
}
|
|
@@ -144,8 +205,55 @@ public class Config implements Parcelable
|
|
|
144
205
|
setEnableWatchdog((Boolean) in.readValue(null));
|
|
145
206
|
setShowTime((Boolean) in.readValue(null));
|
|
146
207
|
setShowDistance((Boolean) in.readValue(null));
|
|
208
|
+
setHttpMethod(in.readString());
|
|
209
|
+
setSyncHttpMethod(in.readString());
|
|
210
|
+
setHttpMode(in.readString());
|
|
211
|
+
setSyncMode(in.readString());
|
|
212
|
+
setHeartbeatInterval((Integer) in.readValue(null));
|
|
213
|
+
setMockLocationPolicy(in.readString());
|
|
214
|
+
// v4.0 + v4.1: driver-insights options serialised as primitives.
|
|
215
|
+
boolean deEnabled = in.readInt() != 0;
|
|
216
|
+
double deSpeedLimit = in.readDouble();
|
|
217
|
+
double deMinMove = in.readDouble();
|
|
218
|
+
long deStoppedDur = in.readLong();
|
|
219
|
+
double deMinTrip = in.readDouble();
|
|
220
|
+
long deMinTripDur = in.readLong();
|
|
221
|
+
// v4.1
|
|
222
|
+
double deHardBrake = in.readDouble();
|
|
223
|
+
double deRapidAccel = in.readDouble();
|
|
224
|
+
double deSharpTurn = in.readDouble();
|
|
225
|
+
double deCrashKmh = in.readDouble();
|
|
226
|
+
long deCrashWin = in.readLong();
|
|
227
|
+
// v4.2 sensor fusion
|
|
228
|
+
boolean deSensorFusion = in.readInt() != 0;
|
|
229
|
+
double deCrashImpactG = in.readDouble();
|
|
230
|
+
long deSensorCrashCooldown = in.readLong();
|
|
231
|
+
long dePhoneUsageWindow = in.readLong();
|
|
232
|
+
long dePhoneUsageCooldown = in.readLong();
|
|
233
|
+
boolean deHasOptions = in.readInt() != 0;
|
|
234
|
+
if (deHasOptions) {
|
|
235
|
+
DrivingEventsOptions de = new DrivingEventsOptions();
|
|
236
|
+
de.enabled = deEnabled;
|
|
237
|
+
de.speedLimitKmh = deSpeedLimit;
|
|
238
|
+
de.minMovingSpeedMps = deMinMove;
|
|
239
|
+
de.stoppedDurationMs = deStoppedDur;
|
|
240
|
+
de.minTripSpeedMps = deMinTrip;
|
|
241
|
+
de.minTripDurationMs = deMinTripDur;
|
|
242
|
+
de.hardBrakeMps2 = deHardBrake;
|
|
243
|
+
de.rapidAccelMps2 = deRapidAccel;
|
|
244
|
+
de.sharpTurnDegPerSec = deSharpTurn;
|
|
245
|
+
de.crashImpactKmh = deCrashKmh;
|
|
246
|
+
de.crashWindowMs = deCrashWin;
|
|
247
|
+
de.sensorFusion = deSensorFusion;
|
|
248
|
+
de.crashImpactG = deCrashImpactG;
|
|
249
|
+
de.sensorCrashCooldownMs = deSensorCrashCooldown;
|
|
250
|
+
de.phoneUsageWindowMs = dePhoneUsageWindow;
|
|
251
|
+
de.phoneUsageCooldownMs = dePhoneUsageCooldown;
|
|
252
|
+
this.drivingEvents = de;
|
|
253
|
+
}
|
|
147
254
|
Bundle bundle = in.readBundle();
|
|
148
255
|
setHttpHeaders((HashMap<String, String>) bundle.getSerializable("httpHeaders"));
|
|
256
|
+
setQueryParams((HashMap<String, String>) bundle.getSerializable("queryParams"));
|
|
149
257
|
setTemplate((LocationTemplate) bundle.getSerializable(AbstractLocationTemplate.BUNDLE_KEY));
|
|
150
258
|
}
|
|
151
259
|
|
|
@@ -183,6 +291,13 @@ public class Config implements Parcelable
|
|
|
183
291
|
config.enableWatchdog = false;
|
|
184
292
|
config.showTime = false;
|
|
185
293
|
config.showDistance = false;
|
|
294
|
+
config.httpMethod = "POST";
|
|
295
|
+
config.syncHttpMethod = "POST";
|
|
296
|
+
config.httpMode = "batch";
|
|
297
|
+
config.syncMode = "batch";
|
|
298
|
+
config.queryParams = null;
|
|
299
|
+
config.heartbeatInterval = 0;
|
|
300
|
+
config.mockLocationPolicy = "allow";
|
|
186
301
|
|
|
187
302
|
return config;
|
|
188
303
|
}
|
|
@@ -223,8 +338,36 @@ public class Config implements Parcelable
|
|
|
223
338
|
out.writeValue(getEnableWatchdog());
|
|
224
339
|
out.writeValue(getShowTime());
|
|
225
340
|
out.writeValue(getShowDistance());
|
|
341
|
+
out.writeString(getHttpMethod());
|
|
342
|
+
out.writeString(getSyncHttpMethod());
|
|
343
|
+
out.writeString(getHttpMode());
|
|
344
|
+
out.writeString(getSyncMode());
|
|
345
|
+
out.writeValue(getHeartbeatInterval());
|
|
346
|
+
out.writeString(getMockLocationPolicy());
|
|
347
|
+
// v4.0 + v4.1: drivingEvents primitives (always written; "hasOptions" flag at end).
|
|
348
|
+
DrivingEventsOptions de = drivingEvents;
|
|
349
|
+
out.writeInt(de != null && de.enabled ? 1 : 0);
|
|
350
|
+
out.writeDouble(de != null ? de.speedLimitKmh : 0.0);
|
|
351
|
+
out.writeDouble(de != null ? de.minMovingSpeedMps : 1.0);
|
|
352
|
+
out.writeLong (de != null ? de.stoppedDurationMs : 60_000L);
|
|
353
|
+
out.writeDouble(de != null ? de.minTripSpeedMps : 3.0);
|
|
354
|
+
out.writeLong (de != null ? de.minTripDurationMs : 30_000L);
|
|
355
|
+
// v4.1
|
|
356
|
+
out.writeDouble(de != null ? de.hardBrakeMps2 : 3.5);
|
|
357
|
+
out.writeDouble(de != null ? de.rapidAccelMps2 : 3.5);
|
|
358
|
+
out.writeDouble(de != null ? de.sharpTurnDegPerSec: 30.0);
|
|
359
|
+
out.writeDouble(de != null ? de.crashImpactKmh : 25.0);
|
|
360
|
+
out.writeLong (de != null ? de.crashWindowMs : 2_000L);
|
|
361
|
+
// v4.2 sensor fusion
|
|
362
|
+
out.writeInt (de != null && de.sensorFusion ? 1 : 0);
|
|
363
|
+
out.writeDouble(de != null ? de.crashImpactG : 3.0);
|
|
364
|
+
out.writeLong (de != null ? de.sensorCrashCooldownMs : 10_000L);
|
|
365
|
+
out.writeLong (de != null ? de.phoneUsageWindowMs : 4_000L);
|
|
366
|
+
out.writeLong (de != null ? de.phoneUsageCooldownMs : 60_000L);
|
|
367
|
+
out.writeInt (de != null ? 1 : 0);
|
|
226
368
|
Bundle bundle = new Bundle();
|
|
227
369
|
bundle.putSerializable("httpHeaders", getHttpHeaders());
|
|
370
|
+
bundle.putSerializable("queryParams", getQueryParams());
|
|
228
371
|
bundle.putSerializable(AbstractLocationTemplate.BUNDLE_KEY, (AbstractLocationTemplate) getTemplate());
|
|
229
372
|
out.writeBundle(bundle);
|
|
230
373
|
}
|
|
@@ -645,6 +788,102 @@ public class Config implements Parcelable
|
|
|
645
788
|
this.showDistance = showDistance;
|
|
646
789
|
}
|
|
647
790
|
|
|
791
|
+
/** HTTP method for the main `url`. Default POST. */
|
|
792
|
+
@Nullable
|
|
793
|
+
public String getHttpMethod() {
|
|
794
|
+
return httpMethod != null ? httpMethod : "POST";
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
public void setHttpMethod(@Nullable String httpMethod) {
|
|
798
|
+
this.httpMethod = (httpMethod == null || httpMethod.isEmpty()) ? null : httpMethod.toUpperCase(Locale.US);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** HTTP method for the `syncUrl`. Default POST. */
|
|
802
|
+
@Nullable
|
|
803
|
+
public String getSyncHttpMethod() {
|
|
804
|
+
return syncHttpMethod != null ? syncHttpMethod : "POST";
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
public void setSyncHttpMethod(@Nullable String syncHttpMethod) {
|
|
808
|
+
this.syncHttpMethod = (syncHttpMethod == null || syncHttpMethod.isEmpty()) ? null : syncHttpMethod.toUpperCase(Locale.US);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/** Real-time post mode. "batch" (default) or "single". */
|
|
812
|
+
@Nullable
|
|
813
|
+
public String getHttpMode() {
|
|
814
|
+
return httpMode != null ? httpMode : "batch";
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
public void setHttpMode(@Nullable String httpMode) {
|
|
818
|
+
this.httpMode = (httpMode == null || httpMode.isEmpty()) ? null : httpMode.toLowerCase(Locale.US);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/** Sync queue mode. "batch" (default) or "single". */
|
|
822
|
+
@Nullable
|
|
823
|
+
public String getSyncMode() {
|
|
824
|
+
return syncMode != null ? syncMode : "batch";
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
public void setSyncMode(@Nullable String syncMode) {
|
|
828
|
+
this.syncMode = (syncMode == null || syncMode.isEmpty()) ? null : syncMode.toLowerCase(Locale.US);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
public boolean hasQueryParams() {
|
|
832
|
+
return queryParams != null && !queryParams.isEmpty();
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
public HashMap<String, String> getQueryParams() {
|
|
836
|
+
return queryParams;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
public void setQueryParams(HashMap queryParams) {
|
|
840
|
+
this.queryParams = queryParams;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
public void setQueryParams(JSONObject queryParams) throws JSONException {
|
|
844
|
+
this.queryParams = new HashMap<String, String>();
|
|
845
|
+
if (queryParams == null) return;
|
|
846
|
+
Iterator<?> it = queryParams.keys();
|
|
847
|
+
while (it.hasNext()) {
|
|
848
|
+
String key = (String) it.next();
|
|
849
|
+
// queryParams accepts string | number (per d.ts). Convert numbers to string.
|
|
850
|
+
Object value = queryParams.get(key);
|
|
851
|
+
this.queryParams.put(key, value == null || value == JSONObject.NULL ? "" : String.valueOf(value));
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/** Heartbeat emit interval in ms. 0 disables. */
|
|
856
|
+
@Nullable
|
|
857
|
+
public Integer getHeartbeatInterval() {
|
|
858
|
+
return heartbeatInterval != null ? heartbeatInterval : 0;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
public void setHeartbeatInterval(Integer heartbeatInterval) {
|
|
862
|
+
this.heartbeatInterval = heartbeatInterval;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/** Mock location policy: "allow" | "flag" | "drop". Default "allow". */
|
|
866
|
+
@Nullable
|
|
867
|
+
public String getMockLocationPolicy() {
|
|
868
|
+
return mockLocationPolicy != null ? mockLocationPolicy : "allow";
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
public void setMockLocationPolicy(@Nullable String mockLocationPolicy) {
|
|
872
|
+
this.mockLocationPolicy = (mockLocationPolicy == null || mockLocationPolicy.isEmpty())
|
|
873
|
+
? null
|
|
874
|
+
: mockLocationPolicy.toLowerCase(Locale.US);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/** v4.0 Phase 6: driver-insights options. */
|
|
878
|
+
@Nullable
|
|
879
|
+
public DrivingEventsOptions getDrivingEvents() {
|
|
880
|
+
return drivingEvents;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
public void setDrivingEvents(@Nullable DrivingEventsOptions drivingEvents) {
|
|
884
|
+
this.drivingEvents = drivingEvents;
|
|
885
|
+
}
|
|
886
|
+
|
|
648
887
|
@Override
|
|
649
888
|
public String toString () {
|
|
650
889
|
return new StringBuffer()
|
|
@@ -675,6 +914,11 @@ public class Config implements Parcelable
|
|
|
675
914
|
.append(" postTemplate=").append(hasTemplate() ? getTemplate().toString() : null)
|
|
676
915
|
.append(" showTime=").append(getShowTime())
|
|
677
916
|
.append(" showDistance=").append(getShowDistance())
|
|
917
|
+
.append(" httpMethod=").append(getHttpMethod())
|
|
918
|
+
.append(" syncHttpMethod=").append(getSyncHttpMethod())
|
|
919
|
+
.append(" httpMode=").append(getHttpMode())
|
|
920
|
+
.append(" syncMode=").append(getSyncMode())
|
|
921
|
+
.append(" queryParams=").append(hasQueryParams() ? getQueryParams().toString() : null)
|
|
678
922
|
.append("]")
|
|
679
923
|
.toString();
|
|
680
924
|
}
|
|
@@ -788,6 +1032,30 @@ public class Config implements Parcelable
|
|
|
788
1032
|
if (config2.hasShowDistance()) {
|
|
789
1033
|
merger.setShowDistance(config2.getShowDistance());
|
|
790
1034
|
}
|
|
1035
|
+
if (config2.httpMethod != null) {
|
|
1036
|
+
merger.setHttpMethod(config2.getHttpMethod());
|
|
1037
|
+
}
|
|
1038
|
+
if (config2.syncHttpMethod != null) {
|
|
1039
|
+
merger.setSyncHttpMethod(config2.getSyncHttpMethod());
|
|
1040
|
+
}
|
|
1041
|
+
if (config2.httpMode != null) {
|
|
1042
|
+
merger.setHttpMode(config2.getHttpMode());
|
|
1043
|
+
}
|
|
1044
|
+
if (config2.syncMode != null) {
|
|
1045
|
+
merger.setSyncMode(config2.getSyncMode());
|
|
1046
|
+
}
|
|
1047
|
+
if (config2.hasQueryParams()) {
|
|
1048
|
+
merger.setQueryParams(config2.getQueryParams());
|
|
1049
|
+
}
|
|
1050
|
+
if (config2.heartbeatInterval != null) {
|
|
1051
|
+
merger.setHeartbeatInterval(config2.getHeartbeatInterval());
|
|
1052
|
+
}
|
|
1053
|
+
if (config2.mockLocationPolicy != null) {
|
|
1054
|
+
merger.setMockLocationPolicy(config2.getMockLocationPolicy());
|
|
1055
|
+
}
|
|
1056
|
+
if (config2.drivingEvents != null) {
|
|
1057
|
+
merger.setDrivingEvents(config2.drivingEvents);
|
|
1058
|
+
}
|
|
791
1059
|
|
|
792
1060
|
return merger;
|
|
793
1061
|
}
|
|
@@ -21,7 +21,6 @@ import org.json.JSONTokener;
|
|
|
21
21
|
|
|
22
22
|
import java.net.URL;
|
|
23
23
|
import java.net.HttpURLConnection;
|
|
24
|
-
import java.io.OutputStreamWriter;
|
|
25
24
|
import java.net.URLEncoder;
|
|
26
25
|
|
|
27
26
|
public class HttpPostService {
|
|
@@ -32,6 +31,7 @@ public class HttpPostService {
|
|
|
32
31
|
private static final int READ_TIMEOUT_MS = 120_000;
|
|
33
32
|
|
|
34
33
|
private String mUrl;
|
|
34
|
+
private String mMethod = "POST";
|
|
35
35
|
private HttpURLConnection mHttpURLConnection;
|
|
36
36
|
|
|
37
37
|
public interface UploadingProgressListener {
|
|
@@ -42,10 +42,31 @@ public class HttpPostService {
|
|
|
42
42
|
mUrl = url;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
public HttpPostService(String url, String method) {
|
|
46
|
+
mUrl = url;
|
|
47
|
+
mMethod = normalizeMethod(method);
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
public HttpPostService(final HttpURLConnection httpURLConnection) {
|
|
46
51
|
mHttpURLConnection = httpURLConnection;
|
|
47
52
|
}
|
|
48
53
|
|
|
54
|
+
public void setMethod(String method) {
|
|
55
|
+
mMethod = normalizeMethod(method);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private static String normalizeMethod(String method) {
|
|
59
|
+
if (method == null || method.isEmpty()) return "POST";
|
|
60
|
+
String m = method.trim().toUpperCase();
|
|
61
|
+
if (m.equals("POST") || m.equals("GET") || m.equals("PUT") || m.equals("PATCH")) return m;
|
|
62
|
+
return "POST";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Returns true when the HTTP method has no request body (GET). */
|
|
66
|
+
private boolean isBodyless() {
|
|
67
|
+
return "GET".equals(mMethod);
|
|
68
|
+
}
|
|
69
|
+
|
|
49
70
|
private HttpURLConnection openConnection() throws IOException {
|
|
50
71
|
if (mHttpURLConnection == null) {
|
|
51
72
|
mHttpURLConnection = (HttpURLConnection) new URL(mUrl).openConnection();
|
|
@@ -84,7 +105,7 @@ public class HttpPostService {
|
|
|
84
105
|
if (headers == null) {
|
|
85
106
|
headers = new HashMap();
|
|
86
107
|
}
|
|
87
|
-
|
|
108
|
+
|
|
88
109
|
String contentType = null;
|
|
89
110
|
for (Object keyObj : headers.keySet()) {
|
|
90
111
|
String key = (String) keyObj;
|
|
@@ -96,7 +117,29 @@ public class HttpPostService {
|
|
|
96
117
|
if (contentType == null) {
|
|
97
118
|
contentType = "application/json";
|
|
98
119
|
}
|
|
99
|
-
|
|
120
|
+
|
|
121
|
+
HttpURLConnection conn = this.openConnection();
|
|
122
|
+
conn.setRequestMethod(mMethod);
|
|
123
|
+
|
|
124
|
+
// Set headers (including Content-Type) up-front; needed for both bodyless and body requests.
|
|
125
|
+
if (!isBodyless()) {
|
|
126
|
+
conn.setRequestProperty("Content-Type", contentType);
|
|
127
|
+
}
|
|
128
|
+
Iterator<Map.Entry<String, String>> it = headers.entrySet().iterator();
|
|
129
|
+
while (it.hasNext()) {
|
|
130
|
+
Map.Entry<String, String> pair = it.next();
|
|
131
|
+
if (!pair.getKey().equalsIgnoreCase("Content-Type")) {
|
|
132
|
+
conn.setRequestProperty(pair.getKey(), pair.getValue());
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// GET: no body; data is expected to live in the URL (URL templating).
|
|
137
|
+
if (isBodyless()) {
|
|
138
|
+
conn.setDoOutput(false);
|
|
139
|
+
return conn.getResponseCode();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Prepare body according to Content-Type so header and body always match.
|
|
100
143
|
String finalBody = body;
|
|
101
144
|
if (contentType.equalsIgnoreCase("application/x-www-form-urlencoded")) {
|
|
102
145
|
try {
|
|
@@ -105,25 +148,17 @@ public class HttpPostService {
|
|
|
105
148
|
finalBody = body;
|
|
106
149
|
}
|
|
107
150
|
}
|
|
108
|
-
|
|
109
|
-
|
|
151
|
+
|
|
152
|
+
// Use byte length, not String.length(), so multi-byte UTF-8 characters
|
|
153
|
+
// (ñ, é, emoji, ...) match the Content-Length the server expects.
|
|
154
|
+
byte[] outputBytes = finalBody.getBytes(StandardCharsets.UTF_8);
|
|
110
155
|
conn.setDoOutput(true);
|
|
111
|
-
conn.setFixedLengthStreamingMode(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
Iterator<Map.Entry<String, String>> it = headers.entrySet().iterator();
|
|
116
|
-
while (it.hasNext()) {
|
|
117
|
-
Map.Entry<String, String> pair = it.next();
|
|
118
|
-
if (!pair.getKey().equalsIgnoreCase("Content-Type")) {
|
|
119
|
-
conn.setRequestProperty(pair.getKey(), pair.getValue());
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
OutputStreamWriter os = null;
|
|
156
|
+
conn.setFixedLengthStreamingMode(outputBytes.length);
|
|
157
|
+
|
|
158
|
+
java.io.OutputStream os = null;
|
|
124
159
|
try {
|
|
125
|
-
os =
|
|
126
|
-
os.write(
|
|
160
|
+
os = conn.getOutputStream();
|
|
161
|
+
os.write(outputBytes);
|
|
127
162
|
} finally {
|
|
128
163
|
if (os != null) {
|
|
129
164
|
os.flush();
|
|
@@ -251,6 +286,20 @@ public class HttpPostService {
|
|
|
251
286
|
}
|
|
252
287
|
|
|
253
288
|
HttpURLConnection conn = this.openConnection();
|
|
289
|
+
conn.setRequestMethod(mMethod);
|
|
290
|
+
if (isBodyless()) {
|
|
291
|
+
conn.setDoOutput(false);
|
|
292
|
+
// No headers loop / body for GET; we just consume the response.
|
|
293
|
+
Iterator<Map.Entry<String, String>> hit = headers.entrySet().iterator();
|
|
294
|
+
while (hit.hasNext()) {
|
|
295
|
+
Map.Entry<String, String> pair = hit.next();
|
|
296
|
+
if (!pair.getKey().equalsIgnoreCase("Content-Type")) {
|
|
297
|
+
conn.setRequestProperty(pair.getKey(), pair.getValue());
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (listener != null) listener.onProgress(100);
|
|
301
|
+
return conn.getResponseCode();
|
|
302
|
+
}
|
|
254
303
|
conn.setDoInput(false);
|
|
255
304
|
conn.setDoOutput(true);
|
|
256
305
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
@@ -258,7 +307,6 @@ public class HttpPostService {
|
|
|
258
307
|
} else {
|
|
259
308
|
conn.setChunkedStreamingMode(0);
|
|
260
309
|
}
|
|
261
|
-
conn.setRequestMethod("POST");
|
|
262
310
|
conn.setRequestProperty("Content-Type", contentType);
|
|
263
311
|
Iterator<Map.Entry<String, String>> it = headers.entrySet().iterator();
|
|
264
312
|
while (it.hasNext()) {
|
|
@@ -286,17 +334,29 @@ public class HttpPostService {
|
|
|
286
334
|
}
|
|
287
335
|
|
|
288
336
|
public static int postJSON(String url, JSONObject json, Map headers) throws IOException {
|
|
289
|
-
|
|
290
|
-
return service.postJSON(json, headers);
|
|
337
|
+
return postJSON(url, json, headers, "POST");
|
|
291
338
|
}
|
|
292
339
|
|
|
293
340
|
public static int postJSON(String url, JSONArray json, Map headers) throws IOException {
|
|
294
|
-
|
|
295
|
-
return service.postJSON(json, headers);
|
|
341
|
+
return postJSON(url, json, headers, "POST");
|
|
296
342
|
}
|
|
297
343
|
|
|
298
344
|
public static int postJSONFile(String url, File file, Map headers, UploadingProgressListener listener) throws IOException {
|
|
299
|
-
|
|
345
|
+
return postJSONFile(url, file, headers, listener, "POST");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
public static int postJSON(String url, JSONObject json, Map headers, String method) throws IOException {
|
|
349
|
+
HttpPostService service = new HttpPostService(url, method);
|
|
350
|
+
return service.postJSON(json, headers);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
public static int postJSON(String url, JSONArray json, Map headers, String method) throws IOException {
|
|
354
|
+
HttpPostService service = new HttpPostService(url, method);
|
|
355
|
+
return service.postJSON(json, headers);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
public static int postJSONFile(String url, File file, Map headers, UploadingProgressListener listener, String method) throws IOException {
|
|
359
|
+
HttpPostService service = new HttpPostService(url, method);
|
|
300
360
|
return service.postJSONFile(file, headers, listener);
|
|
301
361
|
}
|
|
302
362
|
}
|
|
@@ -16,4 +16,30 @@ public interface PluginDelegate {
|
|
|
16
16
|
void onAbortRequested();
|
|
17
17
|
void onHttpAuthorization();
|
|
18
18
|
void onError(PluginException error);
|
|
19
|
+
/** v3.5 Phase 4: sync queue events. Default no-op so existing implementations keep compiling. */
|
|
20
|
+
default void onSyncStart() {}
|
|
21
|
+
default void onSyncSuccess(int locationsSent) {}
|
|
22
|
+
default void onSyncError(int httpStatus, String message) {}
|
|
23
|
+
default void onSyncProgress(int progress) {}
|
|
24
|
+
default void onHeartbeat(BackgroundLocation location) {}
|
|
25
|
+
// v4.0 Phase 6: driver insights
|
|
26
|
+
default void onTripStart(BackgroundLocation location) {}
|
|
27
|
+
default void onTripEnd(BackgroundLocation location, double distance, long durationMs) {}
|
|
28
|
+
default void onMoving(BackgroundLocation location) {}
|
|
29
|
+
default void onStopped(BackgroundLocation location) {}
|
|
30
|
+
default void onSpeeding(BackgroundLocation location, double speedKmh, double limitKmh) {}
|
|
31
|
+
default void onProviderChange(String provider) {}
|
|
32
|
+
default void onSOS(BackgroundLocation location, org.json.JSONObject payload) {}
|
|
33
|
+
// v4.1 GPS-derived sensor-like events
|
|
34
|
+
default void onHardBrake(BackgroundLocation location, double decelMps2) {}
|
|
35
|
+
default void onRapidAcceleration(BackgroundLocation location, double accelMps2) {}
|
|
36
|
+
default void onSharpTurn(BackgroundLocation location, double degPerSec) {}
|
|
37
|
+
default void onPossibleCrash(BackgroundLocation location, double velocityDropKmh) {}
|
|
38
|
+
/** v4.2: same event, but enriched with the source ("gps" | "sensor") and impact value. */
|
|
39
|
+
default void onPossibleCrash(BackgroundLocation location, double value, String source) {
|
|
40
|
+
// Backward-compat default: forward to the legacy 2-arg overload.
|
|
41
|
+
onPossibleCrash(location, value);
|
|
42
|
+
}
|
|
43
|
+
/** v4.2 sensor fusion: emitted when device interaction is detected during an active trip. */
|
|
44
|
+
default void onPhoneUsageWhileDriving(BackgroundLocation location) {}
|
|
19
45
|
}
|
|
@@ -3,10 +3,12 @@ package com.marianhello.bgloc;
|
|
|
3
3
|
import com.marianhello.bgloc.data.BackgroundLocation;
|
|
4
4
|
import com.marianhello.bgloc.data.LocationDAO;
|
|
5
5
|
import com.marianhello.bgloc.data.SessionLocationDAO;
|
|
6
|
+
import com.marianhello.bgloc.http.UrlTemplateResolver;
|
|
6
7
|
import com.marianhello.logging.LoggerManager;
|
|
7
8
|
|
|
8
9
|
import org.json.JSONArray;
|
|
9
10
|
import org.json.JSONException;
|
|
11
|
+
import org.json.JSONObject;
|
|
10
12
|
|
|
11
13
|
import java.util.concurrent.ExecutorService;
|
|
12
14
|
import java.util.concurrent.Executors;
|
|
@@ -90,6 +92,18 @@ public class PostLocationTask {
|
|
|
90
92
|
return;
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
// v3.5 Phase 4: mock location policy. Detection is already in BackgroundLocation
|
|
96
|
+
// (isFromMockProvider). Here we apply the policy.
|
|
97
|
+
if (location != null && location.isFromMockProvider()) {
|
|
98
|
+
String policy = mConfig.getMockLocationPolicy(); // "allow" | "flag" | "drop"
|
|
99
|
+
if ("drop".equals(policy)) {
|
|
100
|
+
logger.info("Mock location dropped (mockLocationPolicy=drop)");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// "flag": leave it but caller can read isFromMockProvider() / mocked field.
|
|
104
|
+
// "allow": no-op.
|
|
105
|
+
}
|
|
106
|
+
|
|
93
107
|
long locationId = mLocationDAO.persistLocation(location);
|
|
94
108
|
location.setLocationId(locationId);
|
|
95
109
|
|
|
@@ -153,21 +167,44 @@ public class PostLocationTask {
|
|
|
153
167
|
|
|
154
168
|
private boolean postLocation(BackgroundLocation location) {
|
|
155
169
|
logger.debug("Executing PostLocationTask#postLocation");
|
|
156
|
-
JSONArray jsonLocations = new JSONArray();
|
|
157
170
|
|
|
171
|
+
// LocationTemplate.locationToJson returns Object (JSONObject for HashMapLocationTemplate,
|
|
172
|
+
// JSONArray for ArrayListLocationTemplate). Resolve to the concrete type before calling
|
|
173
|
+
// the matching HttpPostService.postJSON overload.
|
|
174
|
+
Object jsonLocation;
|
|
158
175
|
try {
|
|
159
|
-
|
|
176
|
+
jsonLocation = mConfig.getTemplate().locationToJson(location);
|
|
160
177
|
} catch (JSONException e) {
|
|
161
178
|
logger.warn("Location to json failed: {}", location.toString());
|
|
162
179
|
return false;
|
|
163
180
|
}
|
|
164
181
|
|
|
165
|
-
String
|
|
166
|
-
|
|
182
|
+
String urlTemplate = mConfig.getUrl();
|
|
183
|
+
// URL templating: substitute {lat}, {lon}, {timestamp_iso}, {device_id}, ... using the
|
|
184
|
+
// current location plus any static queryParams. For "single" mode this is per-location;
|
|
185
|
+
// for "batch" mode only static queryParams placeholders apply (location-derived ones
|
|
186
|
+
// would not make sense for an array).
|
|
187
|
+
String resolvedUrl = UrlTemplateResolver.resolve(urlTemplate, location, mConfig.getQueryParams());
|
|
188
|
+
|
|
189
|
+
String method = mConfig.getHttpMethod();
|
|
190
|
+
String mode = mConfig.getHttpMode();
|
|
191
|
+
logger.debug("Posting to url: {} method: {} mode: {} headers: {}",
|
|
192
|
+
resolvedUrl, method, mConfig.getHttpHeaders());
|
|
167
193
|
int responseCode;
|
|
168
194
|
|
|
169
195
|
try {
|
|
170
|
-
|
|
196
|
+
if ("single".equals(mode) || "GET".equals(method)) {
|
|
197
|
+
// GET cannot carry a JSON array body; force per-location request.
|
|
198
|
+
if (jsonLocation instanceof JSONArray) {
|
|
199
|
+
responseCode = HttpPostService.postJSON(resolvedUrl, (JSONArray) jsonLocation, mConfig.getHttpHeaders(), method);
|
|
200
|
+
} else {
|
|
201
|
+
responseCode = HttpPostService.postJSON(resolvedUrl, (JSONObject) jsonLocation, mConfig.getHttpHeaders(), method);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
JSONArray jsonLocations = new JSONArray();
|
|
205
|
+
jsonLocations.put(jsonLocation);
|
|
206
|
+
responseCode = HttpPostService.postJSON(resolvedUrl, jsonLocations, mConfig.getHttpHeaders(), method);
|
|
207
|
+
}
|
|
171
208
|
} catch (Exception e) {
|
|
172
209
|
mHasConnectivity = mConnectivityListener.hasConnectivity();
|
|
173
210
|
logger.warn("Error while posting locations: {}", e.getMessage());
|