@capgo/background-geolocation 8.0.31 → 8.0.33
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 +297 -13
- package/android/build.gradle +2 -0
- package/android/src/main/AndroidManifest.xml +17 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/BackgroundGeolocation.java +281 -2
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/GeofenceBootReceiver.java +58 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/GeofenceBroadcastReceiver.java +83 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/GeofenceStore.java +255 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/GeofenceTransitionWorker.java +35 -0
- package/dist/docs.json +740 -4
- package/dist/esm/definitions.d.ts +317 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +17 -1
- package/dist/esm/web.js +108 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +108 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +108 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoBackgroundGeolocationPlugin/CapgoCapacitorBackgroundGeolocationPlugin.swift +425 -4
- package/ios/Tests/CapgoBackgroundGeolocationPluginTests/CapgoBackgroundGeolocationPluginTests.swift +5 -0
- package/package.json +17 -10
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
package com.capgo.capacitor_background_geolocation;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.content.SharedPreferences;
|
|
5
|
+
import androidx.work.Constraints;
|
|
6
|
+
import androidx.work.Data;
|
|
7
|
+
import androidx.work.NetworkType;
|
|
8
|
+
import androidx.work.OneTimeWorkRequest;
|
|
9
|
+
import androidx.work.WorkManager;
|
|
10
|
+
import com.getcapacitor.JSObject;
|
|
11
|
+
import com.getcapacitor.Logger;
|
|
12
|
+
import com.google.android.gms.location.Geofence;
|
|
13
|
+
import com.google.android.gms.location.GeofencingRequest;
|
|
14
|
+
import java.io.IOException;
|
|
15
|
+
import java.io.OutputStream;
|
|
16
|
+
import java.net.HttpURLConnection;
|
|
17
|
+
import java.net.URL;
|
|
18
|
+
import java.nio.charset.StandardCharsets;
|
|
19
|
+
import java.util.HashSet;
|
|
20
|
+
import java.util.Iterator;
|
|
21
|
+
import java.util.Set;
|
|
22
|
+
import org.json.JSONException;
|
|
23
|
+
import org.json.JSONObject;
|
|
24
|
+
|
|
25
|
+
final class GeofenceStore {
|
|
26
|
+
|
|
27
|
+
static final String ACTION_GEOFENCE_EVENT = GeofenceStore.class.getPackage().getName() + ".geofence";
|
|
28
|
+
static final String ACTION_GEOFENCE_ERROR = GeofenceStore.class.getPackage().getName() + ".geofence.error";
|
|
29
|
+
static final String EXTRA_GEOFENCE_PAYLOAD = "payload";
|
|
30
|
+
static final String EXTRA_GEOFENCE_ERROR = "error";
|
|
31
|
+
|
|
32
|
+
private static final String PREFS_NAME = "CapgoBackgroundGeolocationGeofences";
|
|
33
|
+
private static final String KEY_URL = "url";
|
|
34
|
+
private static final String KEY_NOTIFY_ON_ENTRY = "notifyOnEntry";
|
|
35
|
+
private static final String KEY_NOTIFY_ON_EXIT = "notifyOnExit";
|
|
36
|
+
private static final String KEY_PAYLOAD = "payload";
|
|
37
|
+
private static final String KEY_REGION_IDS = "regionIds";
|
|
38
|
+
private static final String KEY_REGION_PREFIX = "region.";
|
|
39
|
+
|
|
40
|
+
private GeofenceStore() {}
|
|
41
|
+
|
|
42
|
+
static void saveSetup(Context context, String url, boolean notifyOnEntry, boolean notifyOnExit, JSONObject payload) {
|
|
43
|
+
SharedPreferences.Editor editor = prefs(context).edit();
|
|
44
|
+
if (url == null || url.isEmpty()) {
|
|
45
|
+
editor.remove(KEY_URL);
|
|
46
|
+
} else {
|
|
47
|
+
editor.putString(KEY_URL, url);
|
|
48
|
+
}
|
|
49
|
+
editor.putBoolean(KEY_NOTIFY_ON_ENTRY, notifyOnEntry);
|
|
50
|
+
editor.putBoolean(KEY_NOTIFY_ON_EXIT, notifyOnExit);
|
|
51
|
+
editor.putString(KEY_PAYLOAD, payload == null ? new JSONObject().toString() : payload.toString());
|
|
52
|
+
editor.apply();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static String getUrl(Context context) {
|
|
56
|
+
return prefs(context).getString(KEY_URL, null);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static boolean getNotifyOnEntry(Context context) {
|
|
60
|
+
return prefs(context).getBoolean(KEY_NOTIFY_ON_ENTRY, true);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static boolean getNotifyOnExit(Context context) {
|
|
64
|
+
return prefs(context).getBoolean(KEY_NOTIFY_ON_EXIT, true);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static void saveRegion(
|
|
68
|
+
Context context,
|
|
69
|
+
String identifier,
|
|
70
|
+
double latitude,
|
|
71
|
+
double longitude,
|
|
72
|
+
float radius,
|
|
73
|
+
boolean notifyOnEntry,
|
|
74
|
+
boolean notifyOnExit,
|
|
75
|
+
JSONObject payload
|
|
76
|
+
) throws JSONException {
|
|
77
|
+
JSONObject region = new JSONObject();
|
|
78
|
+
region.put("identifier", identifier);
|
|
79
|
+
region.put("latitude", latitude);
|
|
80
|
+
region.put("longitude", longitude);
|
|
81
|
+
region.put("radius", radius);
|
|
82
|
+
region.put("notifyOnEntry", notifyOnEntry);
|
|
83
|
+
region.put("notifyOnExit", notifyOnExit);
|
|
84
|
+
region.put("payload", payload == null ? new JSONObject() : payload);
|
|
85
|
+
|
|
86
|
+
Set<String> regionIds = getRegionIds(context);
|
|
87
|
+
regionIds.add(identifier);
|
|
88
|
+
prefs(context).edit().putStringSet(KEY_REGION_IDS, regionIds).putString(KEY_REGION_PREFIX + identifier, region.toString()).apply();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static void removeRegion(Context context, String identifier) {
|
|
92
|
+
Set<String> regionIds = getRegionIds(context);
|
|
93
|
+
regionIds.remove(identifier);
|
|
94
|
+
prefs(context).edit().putStringSet(KEY_REGION_IDS, regionIds).remove(KEY_REGION_PREFIX + identifier).apply();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static void clearRegions(Context context) {
|
|
98
|
+
SharedPreferences preferences = prefs(context);
|
|
99
|
+
SharedPreferences.Editor editor = preferences.edit();
|
|
100
|
+
for (String identifier : getRegionIds(context)) {
|
|
101
|
+
editor.remove(KEY_REGION_PREFIX + identifier);
|
|
102
|
+
}
|
|
103
|
+
editor.remove(KEY_REGION_IDS).apply();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static Set<String> getRegionIds(Context context) {
|
|
107
|
+
return new HashSet<>(prefs(context).getStringSet(KEY_REGION_IDS, new HashSet<>()));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static JSONObject getRegion(Context context, String identifier) {
|
|
111
|
+
return jsonFromString(prefs(context).getString(KEY_REGION_PREFIX + identifier, null));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static GeofencingRequest buildGeofencingRequest(JSONObject region) throws JSONException {
|
|
115
|
+
String identifier = region.getString("identifier");
|
|
116
|
+
double latitude = region.getDouble("latitude");
|
|
117
|
+
double longitude = region.getDouble("longitude");
|
|
118
|
+
float radius = (float) region.getDouble("radius");
|
|
119
|
+
boolean notifyOnEntry = region.optBoolean("notifyOnEntry", true);
|
|
120
|
+
boolean notifyOnExit = region.optBoolean("notifyOnExit", true);
|
|
121
|
+
|
|
122
|
+
int transitionTypes = geofenceTransitionTypes(notifyOnEntry, notifyOnExit);
|
|
123
|
+
int initialTrigger = notifyOnEntry ? GeofencingRequest.INITIAL_TRIGGER_ENTER : 0;
|
|
124
|
+
Geofence geofence = new Geofence.Builder()
|
|
125
|
+
.setRequestId(identifier)
|
|
126
|
+
.setCircularRegion(latitude, longitude, radius)
|
|
127
|
+
.setTransitionTypes(transitionTypes)
|
|
128
|
+
.setExpirationDuration(Geofence.NEVER_EXPIRE)
|
|
129
|
+
.build();
|
|
130
|
+
return new GeofencingRequest.Builder().setInitialTrigger(initialTrigger).addGeofence(geofence).build();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static int geofenceTransitionTypes(boolean notifyOnEntry, boolean notifyOnExit) throws JSONException {
|
|
134
|
+
int transitionTypes = 0;
|
|
135
|
+
if (notifyOnEntry) {
|
|
136
|
+
transitionTypes |= Geofence.GEOFENCE_TRANSITION_ENTER;
|
|
137
|
+
}
|
|
138
|
+
if (notifyOnExit) {
|
|
139
|
+
transitionTypes |= Geofence.GEOFENCE_TRANSITION_EXIT;
|
|
140
|
+
}
|
|
141
|
+
if (transitionTypes == 0) {
|
|
142
|
+
throw new JSONException("At least one transition must be enabled");
|
|
143
|
+
}
|
|
144
|
+
return transitionTypes;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static JSONObject buildTransitionData(Context context, String identifier, boolean enter) throws JSONException {
|
|
148
|
+
JSONObject region = getRegion(context, identifier);
|
|
149
|
+
JSONObject payload = copy(jsonFromString(prefs(context).getString(KEY_PAYLOAD, null)));
|
|
150
|
+
JSONObject regionPayload = region.optJSONObject("payload");
|
|
151
|
+
if (regionPayload != null) {
|
|
152
|
+
merge(payload, regionPayload);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
JSONObject data = copy(payload);
|
|
156
|
+
data.put("identifier", identifier);
|
|
157
|
+
data.put("transition", enter ? "enter" : "exit");
|
|
158
|
+
data.put("enter", enter);
|
|
159
|
+
if (region.has("latitude")) {
|
|
160
|
+
data.put("latitude", region.optDouble("latitude"));
|
|
161
|
+
}
|
|
162
|
+
if (region.has("longitude")) {
|
|
163
|
+
data.put("longitude", region.optDouble("longitude"));
|
|
164
|
+
}
|
|
165
|
+
if (region.has("radius")) {
|
|
166
|
+
data.put("radius", region.optDouble("radius"));
|
|
167
|
+
}
|
|
168
|
+
data.put("payload", payload);
|
|
169
|
+
return data;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static void enqueueTransition(Context context, JSONObject data) {
|
|
173
|
+
if (getUrl(context) == null || getUrl(context).isEmpty()) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
Data inputData = new Data.Builder().putString(EXTRA_GEOFENCE_PAYLOAD, data.toString()).build();
|
|
177
|
+
Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
|
|
178
|
+
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(GeofenceTransitionWorker.class)
|
|
179
|
+
.setInputData(inputData)
|
|
180
|
+
.setConstraints(constraints)
|
|
181
|
+
.build();
|
|
182
|
+
WorkManager.getInstance(context).enqueue(request);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static void sendTransition(Context context, JSONObject data) throws IOException {
|
|
186
|
+
String urlString = getUrl(context);
|
|
187
|
+
if (urlString == null || urlString.isEmpty()) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
HttpURLConnection connection = null;
|
|
191
|
+
try {
|
|
192
|
+
URL url = new URL(urlString);
|
|
193
|
+
byte[] body = data.toString().getBytes(StandardCharsets.UTF_8);
|
|
194
|
+
connection = (HttpURLConnection) url.openConnection();
|
|
195
|
+
connection.setRequestMethod("POST");
|
|
196
|
+
connection.setConnectTimeout(15000);
|
|
197
|
+
connection.setReadTimeout(15000);
|
|
198
|
+
connection.setDoOutput(true);
|
|
199
|
+
connection.setRequestProperty("Accept", "application/json");
|
|
200
|
+
connection.setRequestProperty("Content-Type", "application/json");
|
|
201
|
+
connection.setRequestProperty("Content-Length", String.valueOf(body.length));
|
|
202
|
+
try (OutputStream outputStream = connection.getOutputStream()) {
|
|
203
|
+
outputStream.write(body);
|
|
204
|
+
}
|
|
205
|
+
int responseCode = connection.getResponseCode();
|
|
206
|
+
Logger.debug("Geofence transition POST finished with response code: " + responseCode);
|
|
207
|
+
if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
|
|
208
|
+
throw new IOException("Geofence transition POST failed with response code: " + responseCode);
|
|
209
|
+
}
|
|
210
|
+
} finally {
|
|
211
|
+
if (connection != null) {
|
|
212
|
+
connection.disconnect();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static JSObject toJSObject(JSONObject jsonObject) throws JSONException {
|
|
218
|
+
JSObject result = new JSObject();
|
|
219
|
+
Iterator<String> keys = jsonObject.keys();
|
|
220
|
+
while (keys.hasNext()) {
|
|
221
|
+
String key = keys.next();
|
|
222
|
+
result.put(key, jsonObject.get(key));
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private static SharedPreferences prefs(Context context) {
|
|
228
|
+
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private static JSONObject jsonFromString(String value) {
|
|
232
|
+
if (value == null || value.isEmpty()) {
|
|
233
|
+
return new JSONObject();
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
return new JSONObject(value);
|
|
237
|
+
} catch (JSONException exception) {
|
|
238
|
+
return new JSONObject();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private static JSONObject copy(JSONObject source) throws JSONException {
|
|
243
|
+
JSONObject target = new JSONObject();
|
|
244
|
+
merge(target, source);
|
|
245
|
+
return target;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private static void merge(JSONObject target, JSONObject source) throws JSONException {
|
|
249
|
+
Iterator<String> keys = source.keys();
|
|
250
|
+
while (keys.hasNext()) {
|
|
251
|
+
String key = keys.next();
|
|
252
|
+
target.put(key, source.get(key));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.capgo.capacitor_background_geolocation;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import androidx.annotation.NonNull;
|
|
5
|
+
import androidx.work.Worker;
|
|
6
|
+
import androidx.work.WorkerParameters;
|
|
7
|
+
import com.getcapacitor.Logger;
|
|
8
|
+
import org.json.JSONException;
|
|
9
|
+
import org.json.JSONObject;
|
|
10
|
+
|
|
11
|
+
public class GeofenceTransitionWorker extends Worker {
|
|
12
|
+
|
|
13
|
+
public GeofenceTransitionWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
|
14
|
+
super(context, workerParams);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@NonNull
|
|
18
|
+
@Override
|
|
19
|
+
public Result doWork() {
|
|
20
|
+
String payload = getInputData().getString(GeofenceStore.EXTRA_GEOFENCE_PAYLOAD);
|
|
21
|
+
if (payload == null || payload.isEmpty()) {
|
|
22
|
+
return Result.success();
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
GeofenceStore.sendTransition(getApplicationContext(), new JSONObject(payload));
|
|
26
|
+
return Result.success();
|
|
27
|
+
} catch (JSONException exception) {
|
|
28
|
+
Logger.error("Invalid geofence transition payload", exception);
|
|
29
|
+
return Result.failure();
|
|
30
|
+
} catch (Exception exception) {
|
|
31
|
+
Logger.error("Failed to send geofence transition", exception);
|
|
32
|
+
return Result.retry();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|