@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.
- package/.npmignore +4 -0
- package/CHANGELOG.md +313 -0
- package/CLAUDE.md +56 -0
- package/HISTORY.md +124 -0
- package/README.md +198 -6
- 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 +362 -1
- package/android/common/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +153 -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 +48 -5
- package/android/common/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +8 -1
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
- 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 +310 -7
- 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 +91 -0
- package/angular/dist/background-geolocation-events.d.ts +18 -1
- package/angular/dist/background-geolocation.service.d.ts +40 -0
- package/angular/dist/esm2022/background-geolocation-events.mjs +22 -1
- package/angular/dist/esm2022/background-geolocation.service.mjs +47 -1
- package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs +67 -0
- package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs.map +1 -1
- package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.h +4 -0
- package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +352 -1
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +26 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +421 -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/MAURGeolocationOpenHelper.m +12 -3
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +4 -0
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +102 -44
- 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/MAURUrlTemplateResolver.h +31 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
- package/package.json +36 -1
- package/plugin.xml +26 -8
- package/www/BackgroundGeolocation.d.ts +559 -3
- package/www/BackgroundGeolocation.js +78 -1
- 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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
.
|
|
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
|
|
221
|
+
return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
|
|
218
222
|
}
|
|
219
223
|
if (accuracy >= 10000) {
|
|
220
|
-
return
|
|
224
|
+
return Priority.PRIORITY_PASSIVE;
|
|
221
225
|
}
|
|
222
226
|
if (accuracy >= 1000) {
|
|
223
|
-
return
|
|
227
|
+
return Priority.PRIORITY_LOW_POWER;
|
|
224
228
|
}
|
|
225
229
|
if (accuracy >= 100) {
|
|
226
|
-
return
|
|
230
|
+
return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
|
|
227
231
|
}
|
|
228
|
-
return
|
|
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
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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 (
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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(
|
|
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
|
}
|
package/android/common/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java
CHANGED
|
@@ -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
|
-
|
|
43
|
-
|
|
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
|
|
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
|
+
}
|