@josuelmm/cordova-background-geolocation 3.1.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.npmignore +4 -0
  2. package/CHANGELOG.md +313 -0
  3. package/CLAUDE.md +56 -0
  4. package/HISTORY.md +124 -0
  5. package/README.md +198 -6
  6. package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +90 -0
  7. package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +362 -1
  8. package/android/common/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +153 -0
  9. package/android/common/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +27 -11
  10. package/android/common/src/main/java/com/marianhello/bgloc/Config.java +268 -0
  11. package/android/common/src/main/java/com/marianhello/bgloc/HttpPostService.java +86 -26
  12. package/android/common/src/main/java/com/marianhello/bgloc/PluginDelegate.java +26 -0
  13. package/android/common/src/main/java/com/marianhello/bgloc/PostLocationTask.java +48 -5
  14. package/android/common/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
  15. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +8 -1
  16. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
  17. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
  18. package/android/common/src/main/java/com/marianhello/bgloc/driving/DrivingEventsDetector.java +265 -0
  19. package/android/common/src/main/java/com/marianhello/bgloc/http/UrlTemplateResolver.java +115 -0
  20. package/android/common/src/main/java/com/marianhello/bgloc/oem/BatteryOemHelper.java +214 -0
  21. package/android/common/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +13 -9
  22. package/android/common/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +29 -40
  23. package/android/common/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +14 -34
  24. package/android/common/src/main/java/com/marianhello/bgloc/sensor/SensorFusionDetector.java +199 -0
  25. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +310 -7
  26. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +14 -2
  27. package/android/common/src/main/java/com/marianhello/bgloc/sync/SyncAdapter.java +50 -3
  28. package/android/dependencies.gradle +0 -3
  29. package/angular/background-geolocation-events.ts +21 -0
  30. package/angular/background-geolocation.service.ts +91 -0
  31. package/angular/dist/background-geolocation-events.d.ts +18 -1
  32. package/angular/dist/background-geolocation.service.d.ts +40 -0
  33. package/angular/dist/esm2022/background-geolocation-events.mjs +22 -1
  34. package/angular/dist/esm2022/background-geolocation.service.mjs +47 -1
  35. package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs +67 -0
  36. package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs.map +1 -1
  37. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.h +4 -0
  38. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +352 -1
  39. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +26 -0
  40. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +421 -15
  41. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.h +12 -0
  42. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +83 -5
  43. package/ios/common/BackgroundGeolocation/MAURConfig.h +15 -0
  44. package/ios/common/BackgroundGeolocation/MAURConfig.m +100 -3
  45. package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +29 -2
  46. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +12 -3
  47. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +4 -0
  48. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +102 -44
  49. package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.h +41 -0
  50. package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.m +137 -0
  51. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.h +29 -0
  52. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.m +31 -0
  53. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.h +25 -0
  54. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.m +153 -0
  55. package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.h +31 -0
  56. package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
  57. package/package.json +36 -1
  58. package/plugin.xml +26 -8
  59. package/www/BackgroundGeolocation.d.ts +559 -3
  60. package/www/BackgroundGeolocation.js +78 -1
  61. package/RELEASE.MD +0 -16
@@ -0,0 +1,214 @@
1
+ package com.marianhello.bgloc.oem;
2
+
3
+ import android.app.Activity;
4
+ import android.content.ComponentName;
5
+ import android.content.Context;
6
+ import android.content.Intent;
7
+ import android.net.Uri;
8
+ import android.os.Build;
9
+ import android.os.PowerManager;
10
+ import android.provider.Settings;
11
+
12
+ import org.json.JSONArray;
13
+ import org.json.JSONException;
14
+ import org.json.JSONObject;
15
+
16
+ /**
17
+ * v3.6 Phase 5: Battery / OEM helpers.
18
+ *
19
+ * Standard Android Doze whitelist:
20
+ * - {@link #isIgnoringBatteryOptimizations(Context)}
21
+ * - {@link #requestIgnoreBatteryOptimizations(Activity)}
22
+ * - {@link #openBatterySettings(Activity)}
23
+ *
24
+ * OEM-specific "auto-start" / "background activity" screens (Xiaomi MIUI, Huawei
25
+ * EMUI, Oppo ColorOS, Vivo FunTouch, Samsung One UI). These cannot be granted
26
+ * programmatically — the user must toggle them in Settings.
27
+ */
28
+ public final class BatteryOemHelper {
29
+
30
+ private BatteryOemHelper() { /* no instances */ }
31
+
32
+ public static boolean isIgnoringBatteryOptimizations(Context ctx) {
33
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;
34
+ try {
35
+ PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE);
36
+ return pm != null && pm.isIgnoringBatteryOptimizations(ctx.getPackageName());
37
+ } catch (Exception e) {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Opens the system battery-optimisation prompt for the app.
44
+ * Does not throw; logs and silently returns if the system dialog is missing.
45
+ */
46
+ public static void requestIgnoreBatteryOptimizations(Activity activity) {
47
+ if (activity == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
48
+ try {
49
+ Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
50
+ intent.setData(Uri.parse("package:" + activity.getPackageName()));
51
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
52
+ activity.startActivity(intent);
53
+ } catch (Exception ignored) {
54
+ openBatterySettings(activity);
55
+ }
56
+ }
57
+
58
+ public static void openBatterySettings(Activity activity) {
59
+ if (activity == null) return;
60
+ // Try the per-app battery usage screen first; fall back to app-info.
61
+ try {
62
+ Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
63
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
64
+ activity.startActivity(intent);
65
+ } catch (Exception e) {
66
+ try {
67
+ Intent fallback = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
68
+ fallback.setData(Uri.parse("package:" + activity.getPackageName()));
69
+ fallback.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
70
+ activity.startActivity(fallback);
71
+ } catch (Exception ignored) {}
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Open the OEM-specific "auto-start" / "background activity" screen.
77
+ * Returns a JSON object describing what was opened so the JS layer can show
78
+ * appropriate copy: { opened, manufacturer, screen }.
79
+ */
80
+ public static JSONObject openAutoStartSettings(Activity activity) throws JSONException {
81
+ JSONObject out = new JSONObject();
82
+ String manufacturer = Build.MANUFACTURER != null ? Build.MANUFACTURER.toLowerCase() : "";
83
+ out.put("manufacturer", manufacturer);
84
+ out.put("opened", false);
85
+ out.put("screen", "");
86
+
87
+ if (activity == null) return out;
88
+
89
+ ComponentName component = autoStartComponent(manufacturer);
90
+ if (component != null) {
91
+ try {
92
+ Intent intent = new Intent();
93
+ intent.setComponent(component);
94
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
95
+ activity.startActivity(intent);
96
+ out.put("opened", true);
97
+ out.put("screen", component.flattenToShortString());
98
+ return out;
99
+ } catch (Exception ignored) { /* fall through to app-info */ }
100
+ }
101
+
102
+ // Fallback: standard application details page so the user can toggle background restrictions.
103
+ try {
104
+ Intent fallback = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
105
+ fallback.setData(Uri.parse("package:" + activity.getPackageName()));
106
+ fallback.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
107
+ activity.startActivity(fallback);
108
+ out.put("opened", true);
109
+ out.put("screen", "android.settings.APPLICATION_DETAILS_SETTINGS");
110
+ } catch (Exception ignored) {}
111
+ return out;
112
+ }
113
+
114
+ /** Known OEM auto-start screen components. Verified against AOSP / OEM forks; may vary by ROM. */
115
+ private static ComponentName autoStartComponent(String manufacturer) {
116
+ if (manufacturer == null) return null;
117
+ if (manufacturer.contains("xiaomi") || manufacturer.contains("redmi") || manufacturer.contains("poco")) {
118
+ return new ComponentName("com.miui.securitycenter",
119
+ "com.miui.permcenter.autostart.AutoStartManagementActivity");
120
+ }
121
+ if (manufacturer.contains("huawei") || manufacturer.contains("honor")) {
122
+ return new ComponentName("com.huawei.systemmanager",
123
+ "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
124
+ }
125
+ if (manufacturer.contains("oppo")) {
126
+ return new ComponentName("com.coloros.safecenter",
127
+ "com.coloros.safecenter.permission.startup.StartupAppListActivity");
128
+ }
129
+ if (manufacturer.contains("vivo")) {
130
+ return new ComponentName("com.vivo.permissionmanager",
131
+ "com.vivo.permissionmanager.activity.BgStartUpManagerActivity");
132
+ }
133
+ if (manufacturer.contains("samsung")) {
134
+ // Samsung does not expose a stable component for "Sleeping apps"; return null and
135
+ // let the caller fall back to app-info, where the user can disable battery optimisation.
136
+ return null;
137
+ }
138
+ if (manufacturer.contains("oneplus")) {
139
+ return new ComponentName("com.oneplus.security",
140
+ "com.oneplus.security.chainlaunch.view.ChainLaunchAppListActivity");
141
+ }
142
+ if (manufacturer.contains("asus")) {
143
+ return new ComponentName("com.asus.mobilemanager",
144
+ "com.asus.mobilemanager.entry.FunctionActivity");
145
+ }
146
+ return null;
147
+ }
148
+
149
+ /**
150
+ * Returns OEM-specific guidance steps. The caller renders them as a help screen.
151
+ */
152
+ public static JSONObject getManufacturerHelp() throws JSONException {
153
+ JSONObject out = new JSONObject();
154
+ String m = Build.MANUFACTURER != null ? Build.MANUFACTURER.toLowerCase() : "";
155
+ out.put("manufacturer", m);
156
+ out.put("steps", new JSONArray(stepsFor(m)));
157
+ return out;
158
+ }
159
+
160
+ private static String[] stepsFor(String manufacturer) {
161
+ if (manufacturer.contains("xiaomi") || manufacturer.contains("redmi") || manufacturer.contains("poco")) {
162
+ return new String[] {
163
+ "Settings → Apps → Manage apps → [your app] → Autostart → enable.",
164
+ "Settings → Apps → Manage apps → [your app] → Battery saver → No restrictions.",
165
+ "Settings → Apps → Manage apps → [your app] → Other permissions → Display pop-up windows while running in the background → Allow.",
166
+ "Lock the app in Recents (drag it down to keep it in memory)."
167
+ };
168
+ }
169
+ if (manufacturer.contains("huawei") || manufacturer.contains("honor")) {
170
+ return new String[] {
171
+ "Settings → Apps → [your app] → Battery → App launch → switch off Manage automatically and enable Auto-launch + Run in background.",
172
+ "Settings → Battery → App launch → [your app] → manage manually."
173
+ };
174
+ }
175
+ if (manufacturer.contains("oppo")) {
176
+ return new String[] {
177
+ "Settings → Battery → Power Consumption Protection → [your app] → Allow.",
178
+ "Settings → Apps → App management → [your app] → Permissions → Auto-start → Allow.",
179
+ "Settings → Privacy permissions → Startup manager → [your app] → enable."
180
+ };
181
+ }
182
+ if (manufacturer.contains("vivo")) {
183
+ return new String[] {
184
+ "Settings → Battery → High background power consumption → [your app] → Allow.",
185
+ "Settings → More settings → Permission management → Auto-start → [your app] → enable."
186
+ };
187
+ }
188
+ if (manufacturer.contains("samsung")) {
189
+ return new String[] {
190
+ "Settings → Apps → [your app] → Battery → Unrestricted.",
191
+ "Settings → Battery and device care → Battery → Background usage limits → Sleeping apps → make sure [your app] is NOT listed.",
192
+ "Settings → Battery and device care → Battery → Background usage limits → Never sleeping apps → add [your app]."
193
+ };
194
+ }
195
+ if (manufacturer.contains("oneplus")) {
196
+ return new String[] {
197
+ "Settings → Battery → Battery optimisation → [your app] → Don't optimise.",
198
+ "Settings → Apps → [your app] → Battery → Background activity → Allow."
199
+ };
200
+ }
201
+ if (manufacturer.contains("asus")) {
202
+ return new String[] {
203
+ "Settings → Apps → [your app] → Battery → Battery saver → Off.",
204
+ "Mobile Manager → Auto-start manager → [your app] → enable."
205
+ };
206
+ }
207
+ // Generic Android.
208
+ return new String[] {
209
+ "Settings → Apps → [your app] → Battery → Unrestricted.",
210
+ "Settings → Apps → [your app] → Permissions → Location → Allow all the time.",
211
+ "Disable battery optimisation for [your app] in Settings → Battery."
212
+ };
213
+ }
214
+ }
@@ -21,6 +21,7 @@ import com.google.android.gms.location.LocationCallback;
21
21
  import com.google.android.gms.location.LocationRequest;
22
22
  import com.google.android.gms.location.LocationResult;
23
23
  import com.google.android.gms.location.LocationServices;
24
+ import com.google.android.gms.location.Priority;
24
25
  import com.marianhello.bgloc.Config;
25
26
  import com.marianhello.bgloc.data.BackgroundActivity;
26
27
 
@@ -136,10 +137,12 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
136
137
  if (fusedLocationClient == null || mConfig == null) { return; }
137
138
 
138
139
  int priority = translateDesiredAccuracy(mConfig.getDesiredAccuracy());
139
- LocationRequest locationRequest = LocationRequest.create()
140
- .setPriority(priority)
141
- .setFastestInterval(mConfig.getFastestInterval())
142
- .setInterval(mConfig.getInterval());
140
+ // v3.4: LocationRequest.Builder (play-services-location 21.0.0+) replaces deprecated
141
+ // LocationRequest.create() + setPriority/setInterval/setFastestInterval.
142
+ LocationRequest locationRequest = new LocationRequest.Builder(priority, mConfig.getInterval())
143
+ .setMinUpdateIntervalMillis(mConfig.getFastestInterval())
144
+ .setWaitForAccurateLocation(false)
145
+ .build();
143
146
 
144
147
  try {
145
148
  fusedLocationClient.requestLocationUpdates(
@@ -213,19 +216,20 @@ public class ActivityRecognitionLocationProvider extends AbstractLocationProvide
213
216
  * 1000: least aggressive, least accurate, best for battery.
214
217
  */
215
218
  private int translateDesiredAccuracy(Integer accuracy) {
219
+ // v3.4: Priority.* (play-services-location 21.0.0+) replaces deprecated LocationRequest.PRIORITY_*.
216
220
  if (accuracy == null) {
217
- return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
221
+ return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
218
222
  }
219
223
  if (accuracy >= 10000) {
220
- return LocationRequest.PRIORITY_NO_POWER;
224
+ return Priority.PRIORITY_PASSIVE;
221
225
  }
222
226
  if (accuracy >= 1000) {
223
- return LocationRequest.PRIORITY_LOW_POWER;
227
+ return Priority.PRIORITY_LOW_POWER;
224
228
  }
225
229
  if (accuracy >= 100) {
226
- return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
230
+ return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
227
231
  }
228
- return LocationRequest.PRIORITY_HIGH_ACCURACY;
232
+ return Priority.PRIORITY_HIGH_ACCURACY;
229
233
  }
230
234
 
231
235
  public static DetectedActivity getProbableActivity(List<DetectedActivity> detectedActivities) {
@@ -6,7 +6,6 @@ import android.content.BroadcastReceiver;
6
6
  import android.content.Context;
7
7
  import android.content.Intent;
8
8
  import android.content.IntentFilter;
9
- import android.location.Criteria;
10
9
  import android.location.Location;
11
10
  import android.location.LocationListener;
12
11
  import android.location.LocationManager;
@@ -55,8 +54,6 @@ public class DistanceFilterLocationProvider extends AbstractLocationProvider imp
55
54
  private PendingIntent singleUpdatePI;
56
55
  private Integer scaledDistanceFilter;
57
56
 
58
- private Criteria criteria;
59
-
60
57
  private LocationManager locationManager;
61
58
  private AlarmManager alarmManager;
62
59
 
@@ -110,12 +107,20 @@ public class DistanceFilterLocationProvider extends AbstractLocationProvider imp
110
107
  singleUpdatePI = PendingIntent.getBroadcast(mContext, 9003, singleLocationUpdateIntent, cancelCurrentFlag);
111
108
  registerReceiver(singleUpdateReceiver, new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION));
112
109
 
113
- // Location criteria
114
- criteria = new Criteria();
115
- criteria.setAltitudeRequired(false);
116
- criteria.setBearingRequired(false);
117
- criteria.setSpeedRequired(true);
118
- criteria.setCostAllowed(true);
110
+ // v3.4 Phase 3: Criteria API removed (deprecated since Android 12 / API 31).
111
+ // Provider selection is now explicit (GPS-first / Network-fallback) via pickProvider().
112
+ }
113
+
114
+ /** v3.4 Phase 3: replaces getBestProvider(criteria, true). */
115
+ private String pickProvider() {
116
+ if (locationManager == null) return null;
117
+ if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
118
+ return LocationManager.GPS_PROVIDER;
119
+ }
120
+ if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
121
+ return LocationManager.NETWORK_PROVIDER;
122
+ }
123
+ return null;
119
124
  }
120
125
 
121
126
  @Override
@@ -209,9 +214,6 @@ public class DistanceFilterLocationProvider extends AbstractLocationProvider imp
209
214
 
210
215
  try {
211
216
  locationManager.removeUpdates(this);
212
- criteria.setAccuracy(Criteria.ACCURACY_FINE);
213
- criteria.setHorizontalAccuracy(translateDesiredAccuracy(mConfig.getDesiredAccuracy()));
214
- criteria.setPowerRequirement(Criteria.POWER_HIGH);
215
217
 
216
218
  if (isMoving) {
217
219
  // setPace can be called while moving, after distanceFilter has been recalculated. We don't want to re-acquire velocity in this case.
@@ -228,18 +230,15 @@ public class DistanceFilterLocationProvider extends AbstractLocationProvider imp
228
230
  // Turn on each provider aggressively for a short period of time
229
231
  List<String> matchingProviders = locationManager.getAllProviders();
230
232
  for (String provider: matchingProviders) {
231
- if (provider != LocationManager.PASSIVE_PROVIDER) {
233
+ if (!LocationManager.PASSIVE_PROVIDER.equals(provider)) {
232
234
  logger.info("Requesting location updates from provider {}", provider);
233
235
  locationManager.requestLocationUpdates(provider, 0, 0, this);
234
236
  }
235
237
  }
236
238
  } else {
237
- String provider = locationManager.getBestProvider(criteria, true);
238
- if (Build.VERSION.SDK_INT > 30 && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
239
- provider = LocationManager.GPS_PROVIDER;
240
- }
239
+ String provider = pickProvider();
241
240
  if (provider == null) {
242
- logger.warn("No best provider available");
241
+ logger.warn("No location provider available (GPS and Network disabled)");
243
242
  return;
244
243
  }
245
244
  logger.info("Requesting location updates from provider {}", provider);
@@ -251,23 +250,8 @@ public class DistanceFilterLocationProvider extends AbstractLocationProvider imp
251
250
  }
252
251
  }
253
252
 
254
- /**
255
- * Translates a number representing desired accuracy of Geolocation system from set [0, 10, 100, 1000].
256
- * 0: most aggressive, most accurate, worst battery drain
257
- * 1000: least aggressive, least accurate, best for battery.
258
- */
259
- private int translateDesiredAccuracy(Integer accuracy) {
260
- if (accuracy == null) {
261
- return Criteria.ACCURACY_MEDIUM;
262
- }
263
- if (accuracy >= 1000) {
264
- return Criteria.ACCURACY_LOW;
265
- }
266
- if (accuracy >= 100) {
267
- return Criteria.ACCURACY_MEDIUM;
268
- }
269
- return Criteria.ACCURACY_HIGH;
270
- }
253
+ // v3.4 Phase 3: translateDesiredAccuracy(...) returning Criteria.ACCURACY_* removed.
254
+ // Provider selection no longer depends on Criteria; pickProvider() chooses GPS-first.
271
255
 
272
256
  /**
273
257
  * Returns the most accurate and timely previously detected location.
@@ -533,14 +517,19 @@ public class DistanceFilterLocationProvider extends AbstractLocationProvider imp
533
517
  logger.info("Stationary location monitor fired");
534
518
  playDebugTone(Tone.DIALTONE);
535
519
 
536
- criteria.setAccuracy(Criteria.ACCURACY_FINE);
537
- criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
538
- criteria.setPowerRequirement(Criteria.POWER_HIGH);
539
-
520
+ // v3.4 Phase 3: Criteria-based requestSingleUpdate removed (deprecated since API 31).
521
+ // Use the provider-string overload, which is still supported and stable.
522
+ String provider = pickProvider();
523
+ if (provider == null) {
524
+ logger.warn("Stationary monitor: no provider available");
525
+ return;
526
+ }
540
527
  try {
541
- locationManager.requestSingleUpdate(criteria, singleUpdatePI);
528
+ locationManager.requestSingleUpdate(provider, singleUpdatePI);
542
529
  } catch (SecurityException e) {
543
530
  logger.error("Security exception: {}", e.getMessage());
531
+ } catch (IllegalArgumentException e) {
532
+ logger.warn("requestSingleUpdate failed: {}", e.getMessage());
544
533
  }
545
534
  }
546
535
  }
@@ -1,14 +1,12 @@
1
1
  package com.marianhello.bgloc.provider;
2
2
 
3
3
  import android.content.Context;
4
- import android.location.Criteria;
5
4
  import android.location.Location;
6
5
  import android.location.LocationListener;
7
6
  import android.location.LocationManager;
8
7
  import android.os.Bundle;
9
8
 
10
9
  import com.marianhello.bgloc.Config;
11
- import com.marianhello.logging.LoggerManager;
12
10
 
13
11
  public class RawLocationProvider extends AbstractLocationProvider implements LocationListener {
14
12
  private LocationManager locationManager;
@@ -39,21 +37,10 @@ public class RawLocationProvider extends AbstractLocationProvider implements Loc
39
37
  logger.warn("RawLocationProvider started without config");
40
38
  return;
41
39
  }
42
- String provider = LocationManager.GPS_PROVIDER;
43
- boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
44
- if (!gpsEnabled) {
45
- Criteria criteria = new Criteria();
46
- criteria.setAltitudeRequired(false);
47
- criteria.setBearingRequired(false);
48
- criteria.setSpeedRequired(true);
49
- criteria.setCostAllowed(true);
50
- criteria.setAccuracy(Criteria.ACCURACY_FINE);
51
- criteria.setHorizontalAccuracy(translateDesiredAccuracy(mConfig.getDesiredAccuracy()));
52
- criteria.setPowerRequirement(Criteria.POWER_HIGH);
53
- provider = locationManager.getBestProvider(criteria, true);
54
- }
40
+ // v3.4 Phase 3: explicit GPS/Network selection. Drops the deprecated Criteria + getBestProvider.
41
+ String provider = pickProvider();
55
42
  if (provider == null) {
56
- logger.warn("No location provider available (GPS disabled and getBestProvider returned null)");
43
+ logger.warn("No location provider available (GPS and Network disabled)");
57
44
  return;
58
45
  }
59
46
  try {
@@ -66,6 +53,17 @@ public class RawLocationProvider extends AbstractLocationProvider implements Loc
66
53
  }
67
54
  }
68
55
 
56
+ /** GPS first, fall back to Network. */
57
+ private String pickProvider() {
58
+ if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
59
+ return LocationManager.GPS_PROVIDER;
60
+ }
61
+ if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
62
+ return LocationManager.NETWORK_PROVIDER;
63
+ }
64
+ return null;
65
+ }
66
+
69
67
  @Override
70
68
  public void onStop() {
71
69
  if (!isStarted) {
@@ -118,24 +116,6 @@ public class RawLocationProvider extends AbstractLocationProvider implements Loc
118
116
  logger.debug("Provider {} was disabled", provider);
119
117
  }
120
118
 
121
- /**
122
- * Translates a number representing desired accuracy of Geolocation system from set [0, 10, 100, 1000].
123
- * 0: most aggressive, most accurate, worst battery drain
124
- * 1000: least aggressive, least accurate, best for battery.
125
- */
126
- private int translateDesiredAccuracy(Integer accuracy) {
127
- if (accuracy == null) {
128
- return Criteria.ACCURACY_MEDIUM;
129
- }
130
- if (accuracy >= 1000) {
131
- return Criteria.ACCURACY_LOW;
132
- }
133
- if (accuracy >= 100) {
134
- return Criteria.ACCURACY_MEDIUM;
135
- }
136
- return Criteria.ACCURACY_HIGH;
137
- }
138
-
139
119
  @Override
140
120
  public void onDestroy() {
141
121
  logger.debug("Destroying RawLocationProvider");
@@ -0,0 +1,199 @@
1
+ package com.marianhello.bgloc.sensor;
2
+
3
+ import android.content.Context;
4
+ import android.hardware.Sensor;
5
+ import android.hardware.SensorEvent;
6
+ import android.hardware.SensorEventListener;
7
+ import android.hardware.SensorManager;
8
+ import android.os.Handler;
9
+ import android.os.Looper;
10
+ import android.os.PowerManager;
11
+
12
+ import com.marianhello.bgloc.data.BackgroundLocation;
13
+
14
+ /**
15
+ * v4.2 Phase 8 — Real sensor fusion detector.
16
+ *
17
+ * Samples linear acceleration (TYPE_LINEAR_ACCELERATION, gravity removed) and
18
+ * gyroscope (TYPE_GYROSCOPE) at SENSOR_DELAY_GAME (~50 Hz). Used to refine
19
+ * possibleCrash and to detect phoneUsageWhileDriving. Pure-Android, no JNI.
20
+ *
21
+ * Crash detection: |a| (m/s²) above {@code crashImpactG} g during a tripActive
22
+ * window emits {@link Listener#onPossibleCrash}. Combined with the GPS-derived
23
+ * heuristic in {@link com.marianhello.bgloc.driving.DrivingEventsDetector},
24
+ * this gives a far higher-confidence signal at low speeds (parking-lot impact)
25
+ * where GPS alone misses.
26
+ *
27
+ * phoneUsageWhileDriving: while {@code tripActive} is true, if the screen turns
28
+ * on and the user produces touch-shaped jitter on the device for a sustained
29
+ * window, fires {@link Listener#onPhoneUsageWhileDriving}. Conservative — designed
30
+ * to avoid false-positives from passenger usage by tying to {@code tripActive}.
31
+ */
32
+ public class SensorFusionDetector implements SensorEventListener {
33
+
34
+ public interface Listener {
35
+ /** Triggered when |a| exceeds crashImpactG while tripActive. */
36
+ void onSensorCrash(BackgroundLocation lastLocation, double impactG);
37
+ /** Screen on + sustained device interaction during tripActive. */
38
+ void onPhoneUsageWhileDriving(BackgroundLocation lastLocation);
39
+ }
40
+
41
+ public static class Config {
42
+ public boolean enabled = false;
43
+ /** Crash threshold in g. 1g = 9.81 m/s². Default 3g. */
44
+ public double crashImpactG = 3.0;
45
+ /** Cooldown between repeated crash detections. */
46
+ public long crashCooldownMs = 10_000;
47
+ /** Min sustained gyro+accel jitter window for phoneUsage. */
48
+ public long phoneUsageWindowMs = 4_000;
49
+ /** Cooldown between repeated phone-usage events. */
50
+ public long phoneUsageCooldownMs = 60_000;
51
+ }
52
+
53
+ private static final float G = 9.80665f;
54
+
55
+ private final Context appContext;
56
+ private final Listener listener;
57
+ private final SensorManager sensorManager;
58
+ private final Sensor linearAccel;
59
+ private final Sensor gyroscope;
60
+ private final PowerManager powerManager;
61
+ private final Handler handler;
62
+
63
+ private Config cfg = new Config();
64
+ private boolean started = false;
65
+ private boolean tripActive = false;
66
+ private BackgroundLocation lastLocation;
67
+
68
+ private long lastCrashAt = 0L;
69
+ private long lastPhoneUsageAt = 0L;
70
+
71
+ // phoneUsage state
72
+ private long jitterAboveSince = 0L;
73
+ private static final double JITTER_GYRO_RAD_S = 0.7; // ~40 deg/s
74
+ private static final double JITTER_ACCEL_MPS2 = 0.5; // small accel, hand movement
75
+
76
+ public SensorFusionDetector(Context context, Listener listener) {
77
+ this.appContext = context.getApplicationContext();
78
+ this.listener = listener;
79
+ this.sensorManager = (SensorManager) appContext.getSystemService(Context.SENSOR_SERVICE);
80
+ this.linearAccel = sensorManager != null ? sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION) : null;
81
+ this.gyroscope = sensorManager != null ? sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) : null;
82
+ this.powerManager = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
83
+ this.handler = new Handler(Looper.getMainLooper());
84
+ }
85
+
86
+ public synchronized void setConfig(Config c) {
87
+ if (c != null) this.cfg = c;
88
+ }
89
+
90
+ public synchronized boolean isAvailable() {
91
+ return sensorManager != null && linearAccel != null;
92
+ }
93
+
94
+ /** Start sampling sensors. Idempotent. */
95
+ public synchronized void start() {
96
+ if (started || !cfg.enabled || sensorManager == null) return;
97
+ if (linearAccel != null) {
98
+ sensorManager.registerListener(this, linearAccel, SensorManager.SENSOR_DELAY_GAME, handler);
99
+ }
100
+ if (gyroscope != null) {
101
+ sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_GAME, handler);
102
+ }
103
+ started = true;
104
+ }
105
+
106
+ /** Stop sampling. Idempotent. */
107
+ public synchronized void stop() {
108
+ if (!started) return;
109
+ if (sensorManager != null) sensorManager.unregisterListener(this);
110
+ started = false;
111
+ jitterAboveSince = 0L;
112
+ }
113
+
114
+ /** Called by detector host whenever the GPS layer marks tripActive on/off. */
115
+ public synchronized void setTripActive(boolean active) {
116
+ this.tripActive = active;
117
+ if (!active) jitterAboveSince = 0L;
118
+ }
119
+
120
+ /** Last known location for event payload. Updated by host. */
121
+ public synchronized void setLastLocation(BackgroundLocation loc) {
122
+ this.lastLocation = loc;
123
+ }
124
+
125
+ @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { /* ignore */ }
126
+
127
+ @Override
128
+ public void onSensorChanged(SensorEvent event) {
129
+ Listener l;
130
+ Config c;
131
+ boolean tripActiveNow;
132
+ BackgroundLocation loc;
133
+ synchronized (this) {
134
+ l = this.listener;
135
+ c = this.cfg;
136
+ tripActiveNow = this.tripActive;
137
+ loc = this.lastLocation;
138
+ }
139
+ if (l == null || c == null || !c.enabled) return;
140
+
141
+ long now = System.currentTimeMillis();
142
+
143
+ if (event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
144
+ float ax = event.values[0], ay = event.values[1], az = event.values[2];
145
+ double mag = Math.sqrt(ax * ax + ay * ay + az * az);
146
+ double gMag = mag / G;
147
+
148
+ // Crash: high impact during a trip.
149
+ if (tripActiveNow && c.crashImpactG > 0 && gMag >= c.crashImpactG
150
+ && (now - lastCrashAt) >= c.crashCooldownMs) {
151
+ lastCrashAt = now;
152
+ l.onSensorCrash(loc, gMag);
153
+ }
154
+
155
+ // Phone usage signal: small accel jitter during trip + screen on.
156
+ evaluatePhoneUsage(now, mag, /*gyroMag*/ -1, c, l, tripActiveNow, loc);
157
+ } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
158
+ float gx = event.values[0], gy = event.values[1], gz = event.values[2];
159
+ double gyroMag = Math.sqrt(gx * gx + gy * gy + gz * gz);
160
+ evaluatePhoneUsage(now, /*accelMag*/ -1, gyroMag, c, l, tripActiveNow, loc);
161
+ }
162
+ }
163
+
164
+ private void evaluatePhoneUsage(long now,
165
+ double accelMag,
166
+ double gyroMag,
167
+ Config c,
168
+ Listener l,
169
+ boolean tripActiveNow,
170
+ BackgroundLocation loc) {
171
+ if (!tripActiveNow) { jitterAboveSince = 0L; return; }
172
+ if (powerManager == null || !isScreenOn()) { jitterAboveSince = 0L; return; }
173
+
174
+ boolean above = (accelMag >= 0 && accelMag >= JITTER_ACCEL_MPS2)
175
+ || (gyroMag >= 0 && gyroMag >= JITTER_GYRO_RAD_S);
176
+ if (above) {
177
+ if (jitterAboveSince == 0L) jitterAboveSince = now;
178
+ if ((now - jitterAboveSince) >= c.phoneUsageWindowMs
179
+ && (now - lastPhoneUsageAt) >= c.phoneUsageCooldownMs) {
180
+ lastPhoneUsageAt = now;
181
+ jitterAboveSince = 0L;
182
+ l.onPhoneUsageWhileDriving(loc);
183
+ }
184
+ } else {
185
+ jitterAboveSince = 0L;
186
+ }
187
+ }
188
+
189
+ @SuppressWarnings("deprecation")
190
+ private boolean isScreenOn() {
191
+ if (powerManager == null) return false;
192
+ try {
193
+ // isInteractive added API 20; we target >= 21 elsewhere, but guard anyway.
194
+ return powerManager.isInteractive();
195
+ } catch (Throwable t) {
196
+ return powerManager.isScreenOn();
197
+ }
198
+ }
199
+ }