@capgo/background-geolocation 7.0.6 → 7.0.7
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/README.md
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
<h2><a href="https://capgo.app/consulting/?ref=plugin"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
A Capacitor plugin that lets you receive geolocation updates even while the app is backgrounded.
|
|
9
|
+
A Capacitor plugin that lets you receive geolocation updates even while the app is backgrounded.
|
|
10
|
+
It has a web API to facilitate for a similar usage, but background geolocation is not supported in a regular browser, only in an app environment.
|
|
10
11
|
|
|
11
12
|
## Usage
|
|
12
13
|
|
|
@@ -121,12 +122,6 @@ function guess_location(callback, timeout) {
|
|
|
121
122
|
}
|
|
122
123
|
```
|
|
123
124
|
|
|
124
|
-
### Typescript support
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
import { BackgroundGeolocation } from "@capgo/background-geolocation";
|
|
128
|
-
```
|
|
129
|
-
|
|
130
125
|
## Installation
|
|
131
126
|
|
|
132
127
|
This plugin supports Capacitor v7:
|
|
@@ -162,7 +157,7 @@ Add the following keys to `Info.plist.`:
|
|
|
162
157
|
|
|
163
158
|
Set the the `android.useLegacyBridge` option to `true` in your Capacitor configuration. This prevents location updates halting after 5 minutes in the background. See https://capacitorjs.com/docs/config and https://github.com/capacitor-community/background-geolocation/issues/89.
|
|
164
159
|
|
|
165
|
-
On Android 13+, the app needs the `POST_NOTIFICATIONS` runtime permission to show the persistent notification informing the user that their location is being used in the background.
|
|
160
|
+
On Android 13+, the app needs the `POST_NOTIFICATIONS` runtime permission to show the persistent notification informing the user that their location is being used in the background. This runtime permission is requested after the location permission is granted.
|
|
166
161
|
|
|
167
162
|
If your app forwards location updates to a server in real time, be aware that after 5 minutes in the background Android will throttle HTTP requests initiated from the WebView. The solution is to use a native HTTP plugin such as [CapacitorHttp](https://capacitorjs.com/docs/apis/http). See https://github.com/capacitor-community/background-geolocation/issues/14.
|
|
168
163
|
|
package/android/src/main/java/com/capgo/capacitor_background_geolocation/BackgroundGeolocation.java
CHANGED
|
@@ -11,7 +11,6 @@ import android.content.Context;
|
|
|
11
11
|
import android.content.Intent;
|
|
12
12
|
import android.content.IntentFilter;
|
|
13
13
|
import android.content.ServiceConnection;
|
|
14
|
-
import android.content.pm.PackageManager;
|
|
15
14
|
import android.graphics.Color;
|
|
16
15
|
import android.location.Location;
|
|
17
16
|
import android.location.LocationManager;
|
|
@@ -30,7 +29,7 @@ import com.getcapacitor.annotation.CapacitorPlugin;
|
|
|
30
29
|
import com.getcapacitor.annotation.Permission;
|
|
31
30
|
import com.getcapacitor.annotation.PermissionCallback;
|
|
32
31
|
import com.google.android.gms.location.LocationServices;
|
|
33
|
-
import
|
|
32
|
+
import java.util.concurrent.CompletableFuture;
|
|
34
33
|
import org.json.JSONObject;
|
|
35
34
|
|
|
36
35
|
@CapacitorPlugin(
|
|
@@ -43,140 +42,178 @@ import org.json.JSONObject;
|
|
|
43
42
|
},
|
|
44
43
|
alias = "location"
|
|
45
44
|
),
|
|
45
|
+
@Permission(
|
|
46
|
+
strings = { Manifest.permission.POST_NOTIFICATIONS },
|
|
47
|
+
alias = "notification"
|
|
48
|
+
),
|
|
46
49
|
}
|
|
47
50
|
)
|
|
48
51
|
public class BackgroundGeolocation extends Plugin {
|
|
49
52
|
|
|
50
|
-
private
|
|
51
|
-
|
|
53
|
+
private CompletableFuture<
|
|
54
|
+
BackgroundGeolocationService.LocalBinder
|
|
55
|
+
> serviceConnectionFuture;
|
|
56
|
+
private CompletableFuture<Void> locationPermissionFuture;
|
|
52
57
|
|
|
53
58
|
private void fetchLastLocation(PluginCall call) {
|
|
54
59
|
try {
|
|
55
60
|
LocationServices.getFusedLocationProviderClient(getContext())
|
|
56
61
|
.getLastLocation()
|
|
57
|
-
.addOnSuccessListener(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@Override
|
|
61
|
-
public void onSuccess(Location location) {
|
|
62
|
-
if (location != null) {
|
|
63
|
-
call.resolve(formatLocation(location));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
62
|
+
.addOnSuccessListener(getActivity(), location -> {
|
|
63
|
+
if (location != null) {
|
|
64
|
+
call.resolve(formatLocation(location));
|
|
66
65
|
}
|
|
67
|
-
);
|
|
66
|
+
});
|
|
68
67
|
} catch (SecurityException ignore) {}
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
@PluginMethod(returnType = PluginMethod.RETURN_CALLBACK)
|
|
72
71
|
public void addWatcher(final PluginCall call) {
|
|
73
|
-
if (
|
|
74
|
-
|
|
72
|
+
if (
|
|
73
|
+
getPermissionState("location") != PermissionState.GRANTED &&
|
|
74
|
+
!call.getBoolean("requestPermissions", true)
|
|
75
|
+
) {
|
|
76
|
+
call.reject("User denied location permission", "NOT_AUTHORIZED");
|
|
75
77
|
return;
|
|
76
78
|
}
|
|
77
|
-
call.setKeepAlive(true);
|
|
78
79
|
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
if (
|
|
81
|
+
getPermissionState("location") != PermissionState.GRANTED &&
|
|
82
|
+
call.getBoolean("requestPermissions", true)
|
|
83
|
+
) {
|
|
84
|
+
call.setKeepAlive(true);
|
|
85
|
+
requestLocationPermissions(call)
|
|
86
|
+
.thenRun(() -> {
|
|
87
|
+
proceedWithWatcher(call);
|
|
88
|
+
})
|
|
89
|
+
.exceptionally(throwable -> {
|
|
90
|
+
call.reject("User denied location permission", "NOT_AUTHORIZED");
|
|
91
|
+
return null;
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// location permission granted.
|
|
97
|
+
if (!isLocationEnabled(getContext())) {
|
|
90
98
|
call.reject("Location services disabled.", "NOT_AUTHORIZED");
|
|
99
|
+
return;
|
|
91
100
|
}
|
|
101
|
+
|
|
102
|
+
// Everything is OK, continuing to adding a watcher
|
|
103
|
+
call.setKeepAlive(true);
|
|
104
|
+
proceedWithWatcher(call);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private void proceedWithWatcher(PluginCall call) {
|
|
92
108
|
if (call.getBoolean("stale", false)) {
|
|
93
109
|
fetchLastLocation(call);
|
|
94
110
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.setContentTitle(
|
|
101
|
-
call.getString("backgroundTitle", "Using your location")
|
|
102
|
-
)
|
|
103
|
-
.setContentText(backgroundMessage)
|
|
104
|
-
.setOngoing(true)
|
|
105
|
-
.setPriority(Notification.PRIORITY_HIGH)
|
|
106
|
-
.setWhen(System.currentTimeMillis());
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
String name = getAppString(
|
|
110
|
-
"capacitor_background_geolocation_notification_icon",
|
|
111
|
-
"mipmap/ic_launcher"
|
|
111
|
+
getServiceConnection().thenAccept(serviceBinder -> {
|
|
112
|
+
serviceBinder.addWatcher(
|
|
113
|
+
call.getCallbackId(),
|
|
114
|
+
createBackgroundNotification(call),
|
|
115
|
+
call.getFloat("distanceFilter", 0f)
|
|
112
116
|
);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// correctly when tapped. If there is no icon specified, tapping it will open the
|
|
116
|
-
// app's settings, rather than bringing the application to the foreground.
|
|
117
|
-
builder.setSmallIcon(getAppResourceIdentifier(parts[1], parts[0]));
|
|
118
|
-
} catch (Exception e) {
|
|
119
|
-
Logger.error("Could not set notification icon", e);
|
|
120
|
-
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
} catch (Exception e) {
|
|
131
|
-
Logger.error("Could not set notification color", e);
|
|
132
|
-
}
|
|
120
|
+
private CompletableFuture<Void> requestLocationPermissions(PluginCall call) {
|
|
121
|
+
if (locationPermissionFuture != null) {
|
|
122
|
+
return locationPermissionFuture;
|
|
123
|
+
}
|
|
124
|
+
locationPermissionFuture = new CompletableFuture<>();
|
|
125
|
+
requestPermissionForAlias("location", call, "locationPermissionsCallback");
|
|
126
|
+
return locationPermissionFuture;
|
|
127
|
+
}
|
|
133
128
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.getLaunchIntentForPackage(getContext().getPackageName());
|
|
137
|
-
if (launchIntent != null) {
|
|
138
|
-
launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
|
139
|
-
builder.setContentIntent(
|
|
140
|
-
PendingIntent.getActivity(
|
|
141
|
-
getContext(),
|
|
142
|
-
0,
|
|
143
|
-
launchIntent,
|
|
144
|
-
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
145
|
-
)
|
|
146
|
-
);
|
|
147
|
-
}
|
|
129
|
+
private Notification createBackgroundNotification(PluginCall call) {
|
|
130
|
+
String backgroundMessage = call.getString("backgroundMessage", "");
|
|
148
131
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
132
|
+
Notification.Builder builder = new Notification.Builder(getContext())
|
|
133
|
+
.setContentTitle(call.getString("backgroundTitle", "Using your location"))
|
|
134
|
+
.setContentText(backgroundMessage)
|
|
135
|
+
.setOngoing(true)
|
|
136
|
+
.setPriority(Notification.PRIORITY_HIGH)
|
|
137
|
+
.setWhen(System.currentTimeMillis());
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
String name = getAppString(
|
|
141
|
+
"capacitor_background_geolocation_notification_icon",
|
|
142
|
+
"mipmap/ic_launcher"
|
|
143
|
+
);
|
|
144
|
+
String[] parts = name.split("/");
|
|
145
|
+
// It is actually necessary to set a valid icon for the notification to behave
|
|
146
|
+
// correctly when tapped. If there is no icon specified, tapping it will open the
|
|
147
|
+
// app's settings, rather than bringing the application to the foreground.
|
|
148
|
+
builder.setSmallIcon(getAppResourceIdentifier(parts[1], parts[0]));
|
|
149
|
+
} catch (Exception e) {
|
|
150
|
+
Logger.error("Could not set notification icon", e);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
String color = getAppString(
|
|
155
|
+
"capacitor_background_geolocation_notification_color",
|
|
156
|
+
null
|
|
157
|
+
);
|
|
158
|
+
if (color != null) {
|
|
159
|
+
builder.setColor(Color.parseColor(color));
|
|
154
160
|
}
|
|
161
|
+
} catch (Exception e) {
|
|
162
|
+
Logger.error("Could not set notification color", e);
|
|
163
|
+
}
|
|
155
164
|
|
|
156
|
-
|
|
165
|
+
Intent launchIntent = getContext()
|
|
166
|
+
.getPackageManager()
|
|
167
|
+
.getLaunchIntentForPackage(getContext().getPackageName());
|
|
168
|
+
if (launchIntent != null) {
|
|
169
|
+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
|
170
|
+
builder.setContentIntent(
|
|
171
|
+
PendingIntent.getActivity(
|
|
172
|
+
getContext(),
|
|
173
|
+
0,
|
|
174
|
+
launchIntent,
|
|
175
|
+
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
176
|
+
)
|
|
177
|
+
);
|
|
157
178
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
179
|
+
|
|
180
|
+
// Set the Channel ID for Android O.
|
|
181
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
182
|
+
builder.setChannelId(
|
|
183
|
+
BackgroundGeolocationService.class.getPackage().getName()
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return builder.build();
|
|
163
188
|
}
|
|
164
189
|
|
|
165
190
|
@PermissionCallback
|
|
166
191
|
private void locationPermissionsCallback(PluginCall call) {
|
|
167
|
-
if (
|
|
168
|
-
call.reject("User denied location permission", "NOT_AUTHORIZED");
|
|
192
|
+
if (locationPermissionFuture == null) {
|
|
169
193
|
return;
|
|
170
194
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
195
|
+
|
|
196
|
+
requestPermissionForAlias(
|
|
197
|
+
"notification",
|
|
198
|
+
call,
|
|
199
|
+
"notificationPermissionsCallback"
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
if (getPermissionState("location") != PermissionState.GRANTED) {
|
|
203
|
+
locationPermissionFuture.completeExceptionally(
|
|
204
|
+
new SecurityException("User denied location permission")
|
|
205
|
+
);
|
|
206
|
+
locationPermissionFuture = null;
|
|
207
|
+
return;
|
|
179
208
|
}
|
|
209
|
+
|
|
210
|
+
locationPermissionFuture.complete(null);
|
|
211
|
+
locationPermissionFuture = null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@PermissionCallback
|
|
215
|
+
private void notificationPermissionsCallback(PluginCall call) {
|
|
216
|
+
Logger.debug("notification permission callback");
|
|
180
217
|
}
|
|
181
218
|
|
|
182
219
|
@PluginMethod
|
|
@@ -186,12 +223,20 @@ public class BackgroundGeolocation extends Plugin {
|
|
|
186
223
|
call.reject("Missing id.");
|
|
187
224
|
return;
|
|
188
225
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
226
|
+
|
|
227
|
+
getServiceConnection()
|
|
228
|
+
.thenAccept(service -> {
|
|
229
|
+
service.removeWatcher(callbackId);
|
|
230
|
+
PluginCall savedCall = getBridge().getSavedCall(callbackId);
|
|
231
|
+
if (savedCall != null) {
|
|
232
|
+
savedCall.release(getBridge());
|
|
233
|
+
}
|
|
234
|
+
call.resolve();
|
|
235
|
+
})
|
|
236
|
+
.exceptionally(throwable -> {
|
|
237
|
+
call.reject("Service connection failed: " + throwable.getMessage());
|
|
238
|
+
return null;
|
|
239
|
+
});
|
|
195
240
|
}
|
|
196
241
|
|
|
197
242
|
@PluginMethod
|
|
@@ -313,51 +358,77 @@ public class BackgroundGeolocation extends Plugin {
|
|
|
313
358
|
manager.createNotificationChannel(channel);
|
|
314
359
|
}
|
|
315
360
|
|
|
361
|
+
LocalBroadcastManager.getInstance(this.getContext()).registerReceiver(
|
|
362
|
+
new ServiceReceiver(),
|
|
363
|
+
new IntentFilter(BackgroundGeolocationService.ACTION_BROADCAST)
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private CompletableFuture<
|
|
368
|
+
BackgroundGeolocationService.LocalBinder
|
|
369
|
+
> getServiceConnection() {
|
|
370
|
+
if (
|
|
371
|
+
serviceConnectionFuture != null &&
|
|
372
|
+
!serviceConnectionFuture.isCompletedExceptionally()
|
|
373
|
+
) {
|
|
374
|
+
return serviceConnectionFuture;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
serviceConnectionFuture = new CompletableFuture<>();
|
|
378
|
+
|
|
379
|
+
Intent serviceIntent = new Intent(
|
|
380
|
+
this.getContext(),
|
|
381
|
+
BackgroundGeolocationService.class
|
|
382
|
+
);
|
|
383
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
384
|
+
this.getContext().startForegroundService(serviceIntent);
|
|
385
|
+
} else {
|
|
386
|
+
this.getContext().startService(serviceIntent);
|
|
387
|
+
}
|
|
388
|
+
|
|
316
389
|
this.getContext().bindService(
|
|
317
|
-
|
|
390
|
+
serviceIntent,
|
|
318
391
|
new ServiceConnection() {
|
|
319
392
|
@Override
|
|
320
393
|
public void onServiceConnected(ComponentName name, IBinder binder) {
|
|
321
|
-
|
|
322
|
-
(BackgroundGeolocationService.LocalBinder) binder
|
|
394
|
+
serviceConnectionFuture.complete(
|
|
395
|
+
(BackgroundGeolocationService.LocalBinder) binder
|
|
396
|
+
);
|
|
323
397
|
}
|
|
324
398
|
|
|
325
399
|
@Override
|
|
326
|
-
public void onServiceDisconnected(ComponentName name) {
|
|
400
|
+
public void onServiceDisconnected(ComponentName name) {
|
|
401
|
+
serviceConnectionFuture = null;
|
|
402
|
+
}
|
|
327
403
|
},
|
|
328
404
|
Context.BIND_AUTO_CREATE
|
|
329
405
|
);
|
|
330
406
|
|
|
331
|
-
|
|
332
|
-
new ServiceReceiver(),
|
|
333
|
-
new IntentFilter(BackgroundGeolocationService.ACTION_BROADCAST)
|
|
334
|
-
);
|
|
407
|
+
return serviceConnectionFuture;
|
|
335
408
|
}
|
|
336
409
|
|
|
337
410
|
@Override
|
|
338
411
|
protected void handleOnResume() {
|
|
339
|
-
if (service != null) {
|
|
340
|
-
if (
|
|
341
|
-
stoppedWithoutPermissions &&
|
|
342
|
-
getPermissionState("location") == PermissionState.GRANTED
|
|
343
|
-
) {
|
|
344
|
-
service.onPermissionsGranted();
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
412
|
super.handleOnResume();
|
|
348
413
|
}
|
|
349
414
|
|
|
350
415
|
@Override
|
|
351
416
|
protected void handleOnPause() {
|
|
352
|
-
stoppedWithoutPermissions =
|
|
353
|
-
getPermissionState("location") != PermissionState.GRANTED;
|
|
354
417
|
super.handleOnPause();
|
|
355
418
|
}
|
|
356
419
|
|
|
357
420
|
@Override
|
|
358
421
|
protected void handleOnDestroy() {
|
|
359
|
-
if (
|
|
360
|
-
|
|
422
|
+
if (serviceConnectionFuture != null) {
|
|
423
|
+
serviceConnectionFuture.thenAccept(
|
|
424
|
+
BackgroundGeolocationService.LocalBinder::stopService
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (
|
|
429
|
+
locationPermissionFuture != null && !locationPermissionFuture.isDone()
|
|
430
|
+
) {
|
|
431
|
+
locationPermissionFuture.cancel(true);
|
|
361
432
|
}
|
|
362
433
|
super.handleOnDestroy();
|
|
363
434
|
}
|
|
@@ -2,20 +2,14 @@ package com.capgo.capacitor_background_geolocation;
|
|
|
2
2
|
|
|
3
3
|
import android.app.Notification;
|
|
4
4
|
import android.app.Service;
|
|
5
|
+
import android.content.Context;
|
|
5
6
|
import android.content.Intent;
|
|
6
|
-
import android.
|
|
7
|
-
import android.location.
|
|
7
|
+
import android.location.LocationListener;
|
|
8
|
+
import android.location.LocationManager;
|
|
8
9
|
import android.os.Binder;
|
|
9
|
-
import android.os.Build;
|
|
10
10
|
import android.os.IBinder;
|
|
11
11
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
12
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
13
|
import java.util.HashSet;
|
|
20
14
|
|
|
21
15
|
// A bound and started service that is promoted to a foreground service
|
|
@@ -30,16 +24,16 @@ public class BackgroundGeolocationService extends Service {
|
|
|
30
24
|
// Must be unique for this application.
|
|
31
25
|
private static final int NOTIFICATION_ID = 28351;
|
|
32
26
|
|
|
33
|
-
private class Watcher {
|
|
27
|
+
private static class Watcher {
|
|
34
28
|
|
|
35
29
|
public String id;
|
|
36
|
-
public
|
|
37
|
-
public
|
|
38
|
-
public
|
|
30
|
+
public LocationManager client;
|
|
31
|
+
public float distanceFilter;
|
|
32
|
+
public LocationListener locationCallback;
|
|
39
33
|
public Notification backgroundNotification;
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
private HashSet<Watcher> watchers = new HashSet
|
|
36
|
+
private HashSet<Watcher> watchers = new HashSet<>();
|
|
43
37
|
|
|
44
38
|
@Override
|
|
45
39
|
public IBinder onBind(Intent intent) {
|
|
@@ -53,20 +47,26 @@ public class BackgroundGeolocationService extends Service {
|
|
|
53
47
|
@Override
|
|
54
48
|
public boolean onUnbind(Intent intent) {
|
|
55
49
|
for (Watcher watcher : watchers) {
|
|
56
|
-
watcher.client.
|
|
50
|
+
watcher.client.removeUpdates(watcher.locationCallback);
|
|
57
51
|
}
|
|
58
|
-
watchers = new HashSet
|
|
52
|
+
watchers = new HashSet<>();
|
|
59
53
|
stopSelf();
|
|
60
54
|
return false;
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
private void requestLocationUpdates(Watcher watcher) {
|
|
58
|
+
try {
|
|
59
|
+
watcher.client.requestLocationUpdates(
|
|
60
|
+
LocationManager.GPS_PROVIDER,
|
|
61
|
+
1000,
|
|
62
|
+
watcher.distanceFilter,
|
|
63
|
+
watcher.locationCallback
|
|
64
|
+
);
|
|
65
|
+
} catch (SecurityException ignore) {
|
|
66
|
+
// According to Android Studio, this method can throw a Security Exception if
|
|
67
|
+
// permissions are not yet granted. Rather than check the permissions, which is fiddly,
|
|
68
|
+
// we simply ignore the exception.
|
|
68
69
|
}
|
|
69
|
-
return null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// Handles requests from the activity.
|
|
@@ -77,54 +77,28 @@ public class BackgroundGeolocationService extends Service {
|
|
|
77
77
|
Notification backgroundNotification,
|
|
78
78
|
float distanceFilter
|
|
79
79
|
) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@Override
|
|
92
|
-
public void onLocationResult(LocationResult locationResult) {
|
|
93
|
-
Location location = locationResult.getLastLocation();
|
|
94
|
-
Intent intent = new Intent(ACTION_BROADCAST);
|
|
95
|
-
intent.putExtra("location", location);
|
|
96
|
-
intent.putExtra("id", id);
|
|
97
|
-
LocalBroadcastManager.getInstance(
|
|
98
|
-
getApplicationContext()
|
|
99
|
-
).sendBroadcast(intent);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
@Override
|
|
103
|
-
public void onLocationAvailability(LocationAvailability availability) {
|
|
104
|
-
if (!availability.isLocationAvailable()) {
|
|
105
|
-
Logger.debug("Location not available");
|
|
106
|
-
}
|
|
107
|
-
}
|
|
80
|
+
LocationManager locationManager = (LocationManager) getSystemService(
|
|
81
|
+
Context.LOCATION_SERVICE
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
LocationListener listener = location -> {
|
|
85
|
+
Intent intent = new Intent(ACTION_BROADCAST);
|
|
86
|
+
intent.putExtra("location", location);
|
|
87
|
+
intent.putExtra("id", id);
|
|
88
|
+
LocalBroadcastManager.getInstance(
|
|
89
|
+
getApplicationContext()
|
|
90
|
+
).sendBroadcast(intent);
|
|
108
91
|
};
|
|
109
92
|
|
|
110
93
|
Watcher watcher = new Watcher();
|
|
111
94
|
watcher.id = id;
|
|
112
|
-
watcher.client =
|
|
113
|
-
watcher.
|
|
114
|
-
watcher.locationCallback =
|
|
95
|
+
watcher.client = locationManager;
|
|
96
|
+
watcher.distanceFilter = distanceFilter;
|
|
97
|
+
watcher.locationCallback = listener;
|
|
115
98
|
watcher.backgroundNotification = backgroundNotification;
|
|
116
99
|
watchers.add(watcher);
|
|
117
100
|
|
|
118
|
-
|
|
119
|
-
// permissions are not yet granted. Rather than check the permissions, which is fiddly,
|
|
120
|
-
// we simply ignore the exception.
|
|
121
|
-
try {
|
|
122
|
-
watcher.client.requestLocationUpdates(
|
|
123
|
-
watcher.locationRequest,
|
|
124
|
-
watcher.locationCallback,
|
|
125
|
-
null
|
|
126
|
-
);
|
|
127
|
-
} catch (SecurityException ignore) {}
|
|
101
|
+
requestLocationUpdates(watcher);
|
|
128
102
|
|
|
129
103
|
// Promote the service to the foreground if necessary.
|
|
130
104
|
// Ideally we would only call 'startForeground' if the service is not already
|
|
@@ -132,46 +106,32 @@ public class BackgroundGeolocationService extends Service {
|
|
|
132
106
|
// in API level 29 and seems to behave weirdly, as reported in #120. However,
|
|
133
107
|
// it appears that 'startForeground' is idempotent, so we just call it repeatedly
|
|
134
108
|
// each time a background watcher is added.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Logger.error("Failed to foreground service", exception);
|
|
143
|
-
}
|
|
109
|
+
try {
|
|
110
|
+
// This method has been known to fail due to weird
|
|
111
|
+
// permission bugs, so we prevent any exceptions from
|
|
112
|
+
// crashing the app. See issue #86.
|
|
113
|
+
startForeground(NOTIFICATION_ID, backgroundNotification);
|
|
114
|
+
} catch (Exception exception) {
|
|
115
|
+
Logger.error("Failed to foreground service", exception);
|
|
144
116
|
}
|
|
145
117
|
}
|
|
146
118
|
|
|
147
119
|
void removeWatcher(String id) {
|
|
148
120
|
for (Watcher watcher : watchers) {
|
|
149
121
|
if (watcher.id.equals(id)) {
|
|
150
|
-
watcher.client.
|
|
122
|
+
watcher.client.removeUpdates(watcher.locationCallback);
|
|
151
123
|
watchers.remove(watcher);
|
|
152
|
-
|
|
153
|
-
stopForeground(true);
|
|
154
|
-
}
|
|
155
|
-
return;
|
|
124
|
+
break;
|
|
156
125
|
}
|
|
157
126
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
void onPermissionsGranted() {
|
|
161
|
-
// If permissions were granted while the app was in the background, for example in
|
|
162
|
-
// the Settings app, the watchers need restarting.
|
|
163
|
-
for (Watcher watcher : watchers) {
|
|
164
|
-
watcher.client.removeLocationUpdates(watcher.locationCallback);
|
|
165
|
-
watcher.client.requestLocationUpdates(
|
|
166
|
-
watcher.locationRequest,
|
|
167
|
-
watcher.locationCallback,
|
|
168
|
-
null
|
|
169
|
-
);
|
|
127
|
+
if (watchers.isEmpty()) {
|
|
128
|
+
stopService();
|
|
170
129
|
}
|
|
171
130
|
}
|
|
172
131
|
|
|
173
132
|
void stopService() {
|
|
174
|
-
|
|
133
|
+
stopForeground(true);
|
|
134
|
+
stopSelf();
|
|
175
135
|
}
|
|
176
136
|
}
|
|
177
137
|
}
|
package/package.json
CHANGED