@azatek/background-geolocation 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorCommunityBackgroundGeolocation.podspec +16 -0
- package/LICENSE +18 -0
- package/Package.swift +29 -0
- package/README.md +242 -0
- package/android/build.gradle +51 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +6 -0
- package/android/gradle.properties +20 -0
- package/android/proguard-rules.pro +21 -0
- package/android/settings.gradle +2 -0
- package/android/src/main/AndroidManifest.xml +21 -0
- package/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocation.java +348 -0
- package/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocationService.java +204 -0
- package/definitions.d.ts +157 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/Plugin.h +10 -0
- package/ios/Plugin/Plugin.m +8 -0
- package/ios/Plugin/Swift/Plugin.swift +263 -0
- package/ios/Podfile +16 -0
- package/package.json +56 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
package com.equimaps.capacitor_background_geolocation;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.app.Notification;
|
|
5
|
+
import android.app.NotificationChannel;
|
|
6
|
+
import android.app.NotificationManager;
|
|
7
|
+
import android.graphics.Color;
|
|
8
|
+
import android.app.PendingIntent;
|
|
9
|
+
import android.content.BroadcastReceiver;
|
|
10
|
+
import android.content.ComponentName;
|
|
11
|
+
import android.content.Context;
|
|
12
|
+
import android.content.Intent;
|
|
13
|
+
import android.content.IntentFilter;
|
|
14
|
+
import android.content.ServiceConnection;
|
|
15
|
+
import android.content.pm.PackageManager;
|
|
16
|
+
import android.location.Location;
|
|
17
|
+
import android.location.LocationManager;
|
|
18
|
+
import android.net.Uri;
|
|
19
|
+
import android.os.Build;
|
|
20
|
+
import android.os.IBinder;
|
|
21
|
+
import android.provider.Settings;
|
|
22
|
+
|
|
23
|
+
import com.getcapacitor.JSObject;
|
|
24
|
+
import com.getcapacitor.Logger;
|
|
25
|
+
import com.getcapacitor.PermissionState;
|
|
26
|
+
import com.getcapacitor.Plugin;
|
|
27
|
+
import com.getcapacitor.PluginCall;
|
|
28
|
+
import com.getcapacitor.PluginMethod;
|
|
29
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
30
|
+
import com.getcapacitor.annotation.Permission;
|
|
31
|
+
import com.getcapacitor.annotation.PermissionCallback;
|
|
32
|
+
import com.google.android.gms.location.LocationServices;
|
|
33
|
+
import com.google.android.gms.tasks.OnSuccessListener;
|
|
34
|
+
|
|
35
|
+
import org.json.JSONObject;
|
|
36
|
+
|
|
37
|
+
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
38
|
+
|
|
39
|
+
@CapacitorPlugin(
|
|
40
|
+
name = "BackgroundGeolocation",
|
|
41
|
+
permissions = {
|
|
42
|
+
@Permission(
|
|
43
|
+
strings = {
|
|
44
|
+
Manifest.permission.ACCESS_COARSE_LOCATION,
|
|
45
|
+
Manifest.permission.ACCESS_FINE_LOCATION
|
|
46
|
+
},
|
|
47
|
+
alias = "location"
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
public class BackgroundGeolocation extends Plugin {
|
|
52
|
+
private BackgroundGeolocationService.LocalBinder service = null;
|
|
53
|
+
private Boolean stoppedWithoutPermissions = false;
|
|
54
|
+
|
|
55
|
+
private void fetchLastLocation(PluginCall call) {
|
|
56
|
+
try {
|
|
57
|
+
LocationServices.getFusedLocationProviderClient(
|
|
58
|
+
getContext()
|
|
59
|
+
).getLastLocation().addOnSuccessListener(
|
|
60
|
+
getActivity(),
|
|
61
|
+
new OnSuccessListener<Location>() {
|
|
62
|
+
@Override
|
|
63
|
+
public void onSuccess(Location location) {
|
|
64
|
+
if (location != null) {
|
|
65
|
+
call.resolve(formatLocation(location));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
} catch (SecurityException ignore) {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@PluginMethod(returnType = PluginMethod.RETURN_CALLBACK)
|
|
74
|
+
public void addWatcher(final PluginCall call) {
|
|
75
|
+
if (service == null) {
|
|
76
|
+
call.reject("Service not running.");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
call.setKeepAlive(true);
|
|
80
|
+
|
|
81
|
+
if (getPermissionState("location") != PermissionState.GRANTED) {
|
|
82
|
+
if (call.getBoolean("requestPermissions", true)) {
|
|
83
|
+
requestPermissionForAlias("location", call, "locationPermissionsCallback");
|
|
84
|
+
} else {
|
|
85
|
+
call.reject("Permission denied.", "NOT_AUTHORIZED");
|
|
86
|
+
}
|
|
87
|
+
} else if (!isLocationEnabled(getContext())) {
|
|
88
|
+
call.reject("Location services disabled.", "NOT_AUTHORIZED");
|
|
89
|
+
}
|
|
90
|
+
if (call.getBoolean("stale", false)) {
|
|
91
|
+
fetchLastLocation(call);
|
|
92
|
+
}
|
|
93
|
+
Notification backgroundNotification = null;
|
|
94
|
+
String backgroundMessage = call.getString("backgroundMessage");
|
|
95
|
+
|
|
96
|
+
if (backgroundMessage != null) {
|
|
97
|
+
Notification.Builder builder = new Notification.Builder(getContext())
|
|
98
|
+
.setContentTitle(
|
|
99
|
+
call.getString(
|
|
100
|
+
"backgroundTitle",
|
|
101
|
+
"Using your location"
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
.setContentText(backgroundMessage)
|
|
105
|
+
.setOngoing(true)
|
|
106
|
+
.setPriority(Notification.PRIORITY_HIGH)
|
|
107
|
+
.setWhen(System.currentTimeMillis());
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
String name = getAppString(
|
|
111
|
+
"capacitor_background_geolocation_notification_icon",
|
|
112
|
+
"mipmap/ic_launcher"
|
|
113
|
+
);
|
|
114
|
+
String[] parts = name.split("/");
|
|
115
|
+
// It is actually necessary to set a valid icon for the notification to behave
|
|
116
|
+
// correctly when tapped. If there is no icon specified, tapping it will open the
|
|
117
|
+
// app's settings, rather than bringing the application to the foreground.
|
|
118
|
+
builder.setSmallIcon(
|
|
119
|
+
getAppResourceIdentifier(parts[1], parts[0])
|
|
120
|
+
);
|
|
121
|
+
} catch (Exception e) {
|
|
122
|
+
Logger.error("Could not set notification icon", e);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
String color = getAppString(
|
|
127
|
+
"capacitor_background_geolocation_notification_color",
|
|
128
|
+
null
|
|
129
|
+
);
|
|
130
|
+
if (color != null) {
|
|
131
|
+
builder.setColor(Color.parseColor(color));
|
|
132
|
+
}
|
|
133
|
+
} catch (Exception e) {
|
|
134
|
+
Logger.error("Could not set notification color", e);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Intent launchIntent = getContext().getPackageManager().getLaunchIntentForPackage(
|
|
138
|
+
getContext().getPackageName()
|
|
139
|
+
);
|
|
140
|
+
if (launchIntent != null) {
|
|
141
|
+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
|
142
|
+
builder.setContentIntent(
|
|
143
|
+
PendingIntent.getActivity(
|
|
144
|
+
getContext(),
|
|
145
|
+
0,
|
|
146
|
+
launchIntent,
|
|
147
|
+
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Set the Channel ID for Android O.
|
|
153
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
154
|
+
builder.setChannelId(BackgroundGeolocationService.class.getPackage().getName());
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
backgroundNotification = builder.build();
|
|
158
|
+
}
|
|
159
|
+
service.addWatcher(
|
|
160
|
+
call.getCallbackId(),
|
|
161
|
+
backgroundNotification,
|
|
162
|
+
call.getFloat("distanceFilter", 0f),
|
|
163
|
+
call.getInt("accuracy", 102)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@PermissionCallback
|
|
168
|
+
private void locationPermissionsCallback(PluginCall call) {
|
|
169
|
+
|
|
170
|
+
if (getPermissionState("location") != PermissionState.GRANTED) {
|
|
171
|
+
call.reject("User denied location permission", "NOT_AUTHORIZED");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (call.getBoolean("stale", false)) {
|
|
175
|
+
fetchLastLocation(call);
|
|
176
|
+
}
|
|
177
|
+
if (service != null) {
|
|
178
|
+
service.onPermissionsGranted();
|
|
179
|
+
// The handleOnResume method will now be called, and we don't need it to call
|
|
180
|
+
// service.onPermissionsGranted again so we reset this flag.
|
|
181
|
+
stoppedWithoutPermissions = false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@PluginMethod()
|
|
186
|
+
public void removeWatcher(PluginCall call) {
|
|
187
|
+
String callbackId = call.getString("id");
|
|
188
|
+
if (callbackId == null) {
|
|
189
|
+
call.reject("Missing id.");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
service.removeWatcher(callbackId);
|
|
193
|
+
PluginCall savedCall = getBridge().getSavedCall(callbackId);
|
|
194
|
+
if (savedCall != null) {
|
|
195
|
+
savedCall.release(getBridge());
|
|
196
|
+
}
|
|
197
|
+
call.resolve();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@PluginMethod()
|
|
201
|
+
public void openSettings(PluginCall call) {
|
|
202
|
+
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
203
|
+
Uri uri = Uri.fromParts("package", getContext().getPackageName(), null);
|
|
204
|
+
intent.setData(uri);
|
|
205
|
+
getContext().startActivity(intent);
|
|
206
|
+
call.resolve();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Checks if device-wide location services are disabled
|
|
210
|
+
private static Boolean isLocationEnabled(Context context) {
|
|
211
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
212
|
+
LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
|
213
|
+
return lm != null && lm.isLocationEnabled();
|
|
214
|
+
} else {
|
|
215
|
+
return (
|
|
216
|
+
Settings.Secure.getInt(
|
|
217
|
+
context.getContentResolver(),
|
|
218
|
+
Settings.Secure.LOCATION_MODE,
|
|
219
|
+
Settings.Secure.LOCATION_MODE_OFF
|
|
220
|
+
) != Settings.Secure.LOCATION_MODE_OFF
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private static JSObject formatLocation(Location location) {
|
|
226
|
+
JSObject obj = new JSObject();
|
|
227
|
+
obj.put("latitude", location.getLatitude());
|
|
228
|
+
obj.put("longitude", location.getLongitude());
|
|
229
|
+
// The docs state that all Location objects have an accuracy, but then why is there a
|
|
230
|
+
// hasAccuracy method? Better safe than sorry.
|
|
231
|
+
obj.put("accuracy", location.hasAccuracy() ? location.getAccuracy() : JSONObject.NULL);
|
|
232
|
+
obj.put("altitude", location.hasAltitude() ? location.getAltitude() : JSONObject.NULL);
|
|
233
|
+
if (Build.VERSION.SDK_INT >= 26 && location.hasVerticalAccuracy()) {
|
|
234
|
+
obj.put("altitudeAccuracy", location.getVerticalAccuracyMeters());
|
|
235
|
+
} else {
|
|
236
|
+
obj.put("altitudeAccuracy", JSONObject.NULL);
|
|
237
|
+
}
|
|
238
|
+
// In addition to mocking locations in development, Android allows the
|
|
239
|
+
// installation of apps which have the power to simulate location
|
|
240
|
+
// readings in other apps.
|
|
241
|
+
obj.put("simulated", location.isFromMockProvider());
|
|
242
|
+
obj.put("speed", location.hasSpeed() ? location.getSpeed() : JSONObject.NULL);
|
|
243
|
+
obj.put("bearing", location.hasBearing() ? location.getBearing() : JSONObject.NULL);
|
|
244
|
+
obj.put("time", location.getTime());
|
|
245
|
+
return obj;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Receives messages from the service.
|
|
249
|
+
private class ServiceReceiver extends BroadcastReceiver {
|
|
250
|
+
@Override
|
|
251
|
+
public void onReceive(Context context, Intent intent) {
|
|
252
|
+
String id = intent.getStringExtra("id");
|
|
253
|
+
PluginCall call = getBridge().getSavedCall(id);
|
|
254
|
+
if (call == null) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
Location location = intent.getParcelableExtra("location");
|
|
258
|
+
if (location != null) {
|
|
259
|
+
call.resolve(formatLocation(location));
|
|
260
|
+
} else {
|
|
261
|
+
Logger.debug("No locations received");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Gets the identifier of the app's resource by name, returning 0 if not found.
|
|
267
|
+
private int getAppResourceIdentifier(String name, String defType) {
|
|
268
|
+
return getContext().getResources().getIdentifier(
|
|
269
|
+
name,
|
|
270
|
+
defType,
|
|
271
|
+
getContext().getPackageName()
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Gets a string from the app's strings.xml file, resorting to a fallback if it is not defined.
|
|
276
|
+
private String getAppString(String name, String fallback) {
|
|
277
|
+
int id = getAppResourceIdentifier(name, "string");
|
|
278
|
+
return id == 0 ? fallback : getContext().getString(id);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@Override
|
|
282
|
+
public void load() {
|
|
283
|
+
super.load();
|
|
284
|
+
|
|
285
|
+
// Android O requires a Notification Channel.
|
|
286
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
287
|
+
NotificationManager manager = (NotificationManager) getContext().getSystemService(
|
|
288
|
+
Context.NOTIFICATION_SERVICE
|
|
289
|
+
);
|
|
290
|
+
NotificationChannel channel = new NotificationChannel(
|
|
291
|
+
BackgroundGeolocationService.class.getPackage().getName(),
|
|
292
|
+
getAppString(
|
|
293
|
+
"capacitor_background_geolocation_notification_channel_name",
|
|
294
|
+
"Background Tracking"
|
|
295
|
+
),
|
|
296
|
+
NotificationManager.IMPORTANCE_DEFAULT
|
|
297
|
+
);
|
|
298
|
+
channel.enableLights(false);
|
|
299
|
+
channel.enableVibration(false);
|
|
300
|
+
channel.setSound(null, null);
|
|
301
|
+
manager.createNotificationChannel(channel);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
this.getContext().bindService(
|
|
305
|
+
new Intent(this.getContext(), BackgroundGeolocationService.class),
|
|
306
|
+
new ServiceConnection() {
|
|
307
|
+
@Override
|
|
308
|
+
public void onServiceConnected(ComponentName name, IBinder binder) {
|
|
309
|
+
BackgroundGeolocation.this.service = (BackgroundGeolocationService.LocalBinder) binder;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@Override
|
|
313
|
+
public void onServiceDisconnected(ComponentName name) {
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
Context.BIND_AUTO_CREATE
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
LocalBroadcastManager.getInstance(this.getContext()).registerReceiver(
|
|
320
|
+
new ServiceReceiver(),
|
|
321
|
+
new IntentFilter(BackgroundGeolocationService.ACTION_BROADCAST)
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@Override
|
|
326
|
+
protected void handleOnResume() {
|
|
327
|
+
if (service != null) {
|
|
328
|
+
if (stoppedWithoutPermissions && getPermissionState("location") == PermissionState.GRANTED) {
|
|
329
|
+
service.onPermissionsGranted();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
super.handleOnResume();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@Override
|
|
336
|
+
protected void handleOnPause() {
|
|
337
|
+
stoppedWithoutPermissions = getPermissionState("location") != PermissionState.GRANTED;
|
|
338
|
+
super.handleOnPause();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@Override
|
|
342
|
+
protected void handleOnDestroy() {
|
|
343
|
+
if (service != null) {
|
|
344
|
+
service.stopService();
|
|
345
|
+
}
|
|
346
|
+
super.handleOnDestroy();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
package com.equimaps.capacitor_background_geolocation;
|
|
2
|
+
|
|
3
|
+
import android.app.Notification;
|
|
4
|
+
import android.app.Service;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
import android.content.pm.ServiceInfo;
|
|
7
|
+
import android.location.Location;
|
|
8
|
+
import android.os.Binder;
|
|
9
|
+
import android.os.Build;
|
|
10
|
+
import android.os.IBinder;
|
|
11
|
+
|
|
12
|
+
import com.getcapacitor.Logger;
|
|
13
|
+
import com.google.android.gms.location.FusedLocationProviderClient;
|
|
14
|
+
import com.google.android.gms.location.LocationAvailability;
|
|
15
|
+
import com.google.android.gms.location.LocationCallback;
|
|
16
|
+
import com.google.android.gms.location.LocationRequest;
|
|
17
|
+
import com.google.android.gms.location.LocationResult;
|
|
18
|
+
import com.google.android.gms.location.LocationServices;
|
|
19
|
+
|
|
20
|
+
import java.util.HashSet;
|
|
21
|
+
|
|
22
|
+
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
23
|
+
|
|
24
|
+
// A bound and started service that is promoted to a foreground service
|
|
25
|
+
// (showing a persistent notification) when the first background watcher is
|
|
26
|
+
// added, and demoted when the last background watcher is removed.
|
|
27
|
+
public class BackgroundGeolocationService extends Service {
|
|
28
|
+
static final String ACTION_BROADCAST = (
|
|
29
|
+
BackgroundGeolocationService.class.getPackage().getName() + ".broadcast"
|
|
30
|
+
);
|
|
31
|
+
private final IBinder binder = new LocalBinder();
|
|
32
|
+
|
|
33
|
+
// Must be unique for this application.
|
|
34
|
+
private static final int NOTIFICATION_ID = 28351;
|
|
35
|
+
|
|
36
|
+
private class Watcher {
|
|
37
|
+
public String id;
|
|
38
|
+
public FusedLocationProviderClient client;
|
|
39
|
+
public LocationRequest locationRequest;
|
|
40
|
+
public LocationCallback locationCallback;
|
|
41
|
+
public Notification backgroundNotification;
|
|
42
|
+
}
|
|
43
|
+
private HashSet<Watcher> watchers = new HashSet<Watcher>();
|
|
44
|
+
|
|
45
|
+
@Override
|
|
46
|
+
public IBinder onBind(Intent intent) {
|
|
47
|
+
return binder;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Some devices allow a foreground service to outlive the application's main
|
|
51
|
+
// activity, leading to nasty crashes as reported in issue #59. If we learn
|
|
52
|
+
// that the application has been killed, all watchers are stopped and the
|
|
53
|
+
// service is terminated immediately.
|
|
54
|
+
@Override
|
|
55
|
+
public boolean onUnbind(Intent intent) {
|
|
56
|
+
for (Watcher watcher : watchers) {
|
|
57
|
+
watcher.client.removeLocationUpdates(watcher.locationCallback);
|
|
58
|
+
}
|
|
59
|
+
watchers = new HashSet<Watcher>();
|
|
60
|
+
stopSelf();
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Notification getNotification() {
|
|
65
|
+
for (Watcher watcher : watchers) {
|
|
66
|
+
if (watcher.backgroundNotification != null) {
|
|
67
|
+
return watcher.backgroundNotification;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Handles requests from the activity.
|
|
74
|
+
public class LocalBinder extends Binder {
|
|
75
|
+
void addWatcher(
|
|
76
|
+
final String id,
|
|
77
|
+
Notification backgroundNotification,
|
|
78
|
+
float distanceFilter,
|
|
79
|
+
int accuracy
|
|
80
|
+
) {
|
|
81
|
+
FusedLocationProviderClient client = LocationServices.getFusedLocationProviderClient(
|
|
82
|
+
BackgroundGeolocationService.this
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Map accuracy enum to Android Priority
|
|
86
|
+
int priority;
|
|
87
|
+
long interval;
|
|
88
|
+
|
|
89
|
+
switch (accuracy) {
|
|
90
|
+
case 100: // HIGH
|
|
91
|
+
priority = Priority.PRIORITY_HIGH_ACCURACY;
|
|
92
|
+
interval = 5000; // 5 seconds
|
|
93
|
+
break;
|
|
94
|
+
case 102: // BALANCED (Default)
|
|
95
|
+
priority = Priority.PRIORITY_BALANCED_POWER_ACCURACY;
|
|
96
|
+
interval = 10000; // 10 seconds
|
|
97
|
+
break;
|
|
98
|
+
case 104: // LOW
|
|
99
|
+
priority = Priority.PRIORITY_LOW_POWER;
|
|
100
|
+
interval = 30000; // 30 seconds
|
|
101
|
+
break;
|
|
102
|
+
case 105: // PASSIVE
|
|
103
|
+
priority = Priority.PRIORITY_PASSIVE;
|
|
104
|
+
interval = 60000; // 60 seconds
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
priority = Priority.PRIORITY_BALANCED_POWER_ACCURACY;
|
|
108
|
+
interval = 10000;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Use modern LocationRequest.Builder API
|
|
112
|
+
LocationRequest locationRequest = new LocationRequest.Builder(priority, interval)
|
|
113
|
+
.setMinUpdateDistanceMeters(distanceFilter)
|
|
114
|
+
.setWaitForAccurateLocation(false)
|
|
115
|
+
.setMaxUpdateDelayMillis(interval)
|
|
116
|
+
.build();
|
|
117
|
+
|
|
118
|
+
LocationCallback callback = new LocationCallback(){
|
|
119
|
+
@Override
|
|
120
|
+
public void onLocationResult(LocationResult locationResult) {
|
|
121
|
+
Location location = locationResult.getLastLocation();
|
|
122
|
+
Intent intent = new Intent(ACTION_BROADCAST);
|
|
123
|
+
intent.putExtra("location", location);
|
|
124
|
+
intent.putExtra("id", id);
|
|
125
|
+
LocalBroadcastManager.getInstance(
|
|
126
|
+
getApplicationContext()
|
|
127
|
+
).sendBroadcast(intent);
|
|
128
|
+
}
|
|
129
|
+
@Override
|
|
130
|
+
public void onLocationAvailability(LocationAvailability availability) {
|
|
131
|
+
if (!availability.isLocationAvailable()) {
|
|
132
|
+
Logger.debug("Location not available");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
Watcher watcher = new Watcher();
|
|
138
|
+
watcher.id = id;
|
|
139
|
+
watcher.client = client;
|
|
140
|
+
watcher.locationRequest = locationRequest;
|
|
141
|
+
watcher.locationCallback = callback;
|
|
142
|
+
watcher.backgroundNotification = backgroundNotification;
|
|
143
|
+
watchers.add(watcher);
|
|
144
|
+
|
|
145
|
+
// According to Android Studio, this method can throw a Security Exception if
|
|
146
|
+
// permissions are not yet granted. Rather than check the permissions, which is fiddly,
|
|
147
|
+
// we simply ignore the exception.
|
|
148
|
+
try {
|
|
149
|
+
watcher.client.requestLocationUpdates(
|
|
150
|
+
watcher.locationRequest,
|
|
151
|
+
watcher.locationCallback,
|
|
152
|
+
null
|
|
153
|
+
);
|
|
154
|
+
} catch (SecurityException ignore) {}
|
|
155
|
+
|
|
156
|
+
// Promote the service to the foreground if necessary.
|
|
157
|
+
// Ideally we would only call 'startForeground' if the service is not already
|
|
158
|
+
// foregrounded. Unfortunately, 'getForegroundServiceType' was only introduced
|
|
159
|
+
// in API level 29 and seems to behave weirdly, as reported in #120. However,
|
|
160
|
+
// it appears that 'startForeground' is idempotent, so we just call it repeatedly
|
|
161
|
+
// each time a background watcher is added.
|
|
162
|
+
if (backgroundNotification != null) {
|
|
163
|
+
try {
|
|
164
|
+
// This method has been known to fail due to weird
|
|
165
|
+
// permission bugs, so we prevent any exceptions from
|
|
166
|
+
// crashing the app. See issue #86.
|
|
167
|
+
startForeground(NOTIFICATION_ID, backgroundNotification);
|
|
168
|
+
} catch (Exception exception) {
|
|
169
|
+
Logger.error("Failed to foreground service", exception);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
void removeWatcher(String id) {
|
|
175
|
+
for (Watcher watcher : watchers) {
|
|
176
|
+
if (watcher.id.equals(id)) {
|
|
177
|
+
watcher.client.removeLocationUpdates(watcher.locationCallback);
|
|
178
|
+
watchers.remove(watcher);
|
|
179
|
+
if (getNotification() == null) {
|
|
180
|
+
stopForeground(true);
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
void onPermissionsGranted() {
|
|
188
|
+
// If permissions were granted while the app was in the background, for example in
|
|
189
|
+
// the Settings app, the watchers need restarting.
|
|
190
|
+
for (Watcher watcher : watchers) {
|
|
191
|
+
watcher.client.removeLocationUpdates(watcher.locationCallback);
|
|
192
|
+
watcher.client.requestLocationUpdates(
|
|
193
|
+
watcher.locationRequest,
|
|
194
|
+
watcher.locationCallback,
|
|
195
|
+
null
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
void stopService() {
|
|
201
|
+
BackgroundGeolocationService.this.stopSelf();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|