Commit a4b1ccc2 authored by Chris Scott's avatar Chris Scott

Merge pull request #19 from christocracy/implement_shared_settings

Implement Android SharedPreferences for configuration.  
parents 8ef8ff1d 88612c14
...@@ -286,8 +286,56 @@ The number of miutes to wait before turning off the GPS after the ActivityRecogn ...@@ -286,8 +286,56 @@ The number of miutes to wait before turning off the GPS after the ActivityRecogn
If the user closes the application while the background-tracking has been started, location-tracking will continue on if ```stopOnTerminate: false```. You may choose to force the foreground application to reload (since this is where your Javascript runs) by setting ```foreceReload: true```. This will guarantee that locations are always sent to your Javascript callback (**WARNING** possibly disruptive to user). If the user closes the application while the background-tracking has been started, location-tracking will continue on if ```stopOnTerminate: false```. You may choose to force the foreground application to reload (since this is where your Javascript runs) by setting ```foreceReload: true```. This will guarantee that locations are always sent to your Javascript callback (**WARNING** possibly disruptive to user).
#####`@param {Boolean} startOnBoot`
Set to ```true``` to start the background-service whenever the device boots. Unless you configure the plugin to ```forceReload``` (ie: boot your app), you should configure the plugin's HTTP features so it can POST to your server in "headless" mode.
#### HTTP Feature #### HTTP Feature
The Android plugin can run as a "headless" background service, sending the user's location to your server even after the user close the application (by configuring ```stopOnTerminate: false```). The Android plugin can run as a "headless" background service, sending the user's location to your server even after the user close the application (by configuring ```stopOnTerminate: false```). The plugin's HTTP request will arrive at your server as follows:
```
bgGeo.configure(callbackFn, failureFn, {
.
.
.
url: 'http://posttestserver.com/post.php?dir=cordova-background-geolocation',
headers: {
"X-FOO": "bar"
},
params: {
"auth_token": "maybe_your_server_authenticates_via_token_YES?"
}
});
...
Headers (Some may be inserted by server)
REQUEST_URI = /post.php?dir=cordova-background-geolocation
QUERY_STRING = dir=cordova-background-geolocation
REQUEST_METHOD = POST
GATEWAY_INTERFACE = CGI/1.1
REMOTE_PORT = 38380
REMOTE_ADDR = 198.84.250.106
HTTP_USER_AGENT = Apache-HttpClient/UNAVAILABLE (java 1.4)
HTTP_CONNECTION = close
HTTP_HOST = posttestserver.com
CONTENT_LENGTH = 243
CONTENT_TYPE = application/json
HTTP_ACCEPT = application/json
UNIQUE_ID = VS-YI9Bx6hIAABctKDoAAAAB
REQUEST_TIME_FLOAT = 1429198883.9584
REQUEST_TIME = 1429198883
No Post Params.
== Begin post body ==
{"auth_token":"maybe_your_server_authenticates_via_token_YES?","location":{"latitude":45.5192875,"longitude":-73.6169281,"accuracy":25.42799949645996,"speed":0,"bearing":0,"altitude":0,"timestamp":1429198882716},"android_id":"39dbac67e2c9d80"}
== End post body ==
Upload contains PUT data:
{"auth_token":"maybe_your_server_authenticates_via_token_YES?","location":{"latitude":45.5192875,"longitude":-73.6169281,"accuracy":25.42799949645996,"speed":0,"bearing":0,"altitude":0,"timestamp":1429198882716},"android_id":"39dbac67e2c9d80"}
```
#####`@param {String} url` #####`@param {String} url`
...@@ -301,17 +349,6 @@ Optional HTTP params sent along in HTTP request to above ```#url```. ...@@ -301,17 +349,6 @@ Optional HTTP params sent along in HTTP request to above ```#url```.
Optional HTTP params sent along in HTTP request to above ```#url```. Optional HTTP params sent along in HTTP request to above ```#url```.
#### Autorun on Boot Feature
The Android background-service can be configured to autorun whenever the device is booted. In ```plugin.xml```, you must first un-comment the Android permission ```android.permission.RECEIVE_BOOT_COMPLETED```
```
<!-- Autorun your app on device BOOT. UNCOMMENT TO ENABLE AUTO-BOOT -->
<!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
```
Next, since the plugin will have no access to your presumably logged-in user at boot-time (eg authentication_token, password, etc), you must manually configure the plugin's parameters within the Java file [src/android/BootReceiver.java](https://github.com/christocracy/cordova-background-geolocation/blob/edge/src/android/BootReceiver.java). Since the plugin will be running in "headless" mode at boot-time (ie: no foreground application, thus no javascript) you should configure an ```#url``` so the plugin can automatically POST location to your server. Since the plugin has no access to any user-identifying information, the Android plugin will send along the device's UUID in the HTTP request params as ```#android_id```. It's up to you to map this Android UUID to a user on your server (at login time, for example). You may fetch the device UUID using standard cordova plugin [org.apache.cordova.device](http://plugins.cordova.io/#/package/org.apache.cordova.device).
### iOS Config ### iOS Config
#####`@param {String} activityType [AutomotiveNavigation, OtherNavigation, Fitness, Other]` #####`@param {String} activityType [AutomotiveNavigation, OtherNavigation, Fitness, Other]`
......
...@@ -179,8 +179,9 @@ var app = { ...@@ -179,8 +179,9 @@ var app = {
locationUpdateInterval: 5000, locationUpdateInterval: 5000,
activityRecognitionInterval: 10000, activityRecognitionInterval: 10000,
stopTimeout: 0, stopTimeout: 0,
forceReload: true, // <-- If the user closes the app **while location-tracking is started** , reboot app (WARNING: possibly distruptive to user) forceReload: true, // <-- [Android] If the user closes the app **while location-tracking is started** , reboot app (WARNING: possibly distruptive to user)
stopOnTerminate: false, // <-- Allow the background-service to run headless when user closes the app. stopOnTerminate: false, // <-- [Android] Allow the background-service to run headless when user closes the app.
startOnBoot: true, // <-- [Android] Auto start background-service in headless mode when device is powered-up.
activityType: 'AutomotiveNavigation' activityType: 'AutomotiveNavigation'
/** /**
* HTTP Feature: set an url to allow the native background service to POST locations to your server * HTTP Feature: set an url to allow the native background service to POST locations to your server
......
...@@ -58,9 +58,7 @@ ...@@ -58,9 +58,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Autorun your app on device BOOT. UNCOMMENT TO ENABLE AUTO-BOOT -->
<!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
</config-file> </config-file>
<config-file target="res/xml/config.xml" parent="/*"> <config-file target="res/xml/config.xml" parent="/*">
......
...@@ -15,6 +15,7 @@ import com.transistorsoft.cordova.bggeo.BackgroundGeolocationService.StationaryL ...@@ -15,6 +15,7 @@ import com.transistorsoft.cordova.bggeo.BackgroundGeolocationService.StationaryL
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location; import android.location.Location;
import android.util.Log; import android.util.Log;
...@@ -93,11 +94,9 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin { ...@@ -93,11 +94,9 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin {
callbackContext.success(); callbackContext.success();
} }
} else if (ACTION_SET_CONFIG.equalsIgnoreCase(action)) { } else if (ACTION_SET_CONFIG.equalsIgnoreCase(action)) {
activity.stopService(backgroundServiceIntent);
result = applyConfig(data); result = applyConfig(data);
// TODO reconfigure Service // TODO reconfigure Service
if (result) { if (result) {
activity.startService(backgroundServiceIntent);
callbackContext.success(); callbackContext.success();
} else { } else {
callbackContext.error("- Configuration error!"); callbackContext.error("- Configuration error!");
...@@ -110,47 +109,65 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin { ...@@ -110,47 +109,65 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin {
} }
private boolean applyConfig(JSONArray data) { private boolean applyConfig(JSONArray data) {
Activity activity = this.cordova.getActivity();
try { try {
JSONObject config = data.getJSONObject(0); JSONObject config = data.getJSONObject(0);
Log.i(TAG, "- configure: " + config.toString()); Log.i(TAG, "- configure: " + config.toString());
backgroundServiceIntent.putExtra("isMoving", isMoving); SharedPreferences settings = activity.getSharedPreferences(TAG, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("isMoving", isMoving);
if (config.has("distanceFilter")) { if (config.has("distanceFilter")) {
backgroundServiceIntent.putExtra("distanceFilter", (float) config.getInt("distanceFilter")); editor.putFloat("distanceFilter", config.getInt("distanceFilter"));
} }
if (config.has("desiredAccuracy")) { if (config.has("desiredAccuracy")) {
backgroundServiceIntent.putExtra("desiredAccuracy", config.getInt("desiredAccuracy")); editor.putInt("desiredAccuracy", config.getInt("desiredAccuracy"));
} }
if (config.has("locationUpdateInterval")) { if (config.has("locationUpdateInterval")) {
backgroundServiceIntent.putExtra("locationUpdateInterval", config.getInt("locationUpdateInterval")); editor.putInt("locationUpdateInterval", config.getInt("locationUpdateInterval"));
} }
if (config.has("activityRecognitionInterval")) { if (config.has("activityRecognitionInterval")) {
backgroundServiceIntent.putExtra("activityRecognitionInterval", config.getInt("activityRecognitionInterval")); editor.putInt("activityRecognitionInterval", config.getInt("activityRecognitionInterval"));
} }
if (config.has("stopTimeout")) { if (config.has("stopTimeout")) {
backgroundServiceIntent.putExtra("stopTimeout", config.getLong("stopTimeout")); editor.putLong("stopTimeout", config.getLong("stopTimeout"));
} }
if (config.has("debug")) { if (config.has("debug")) {
backgroundServiceIntent.putExtra("debug", config.getBoolean("debug")); editor.putBoolean("debug", config.getBoolean("debug"));
} }
if (config.has("stopOnTerminate")) { if (config.has("stopOnTerminate")) {
stopOnTerminate = config.getBoolean("stopOnTerminate"); editor.putBoolean("stopOnTerminate", config.getBoolean("stopOnTerminate"));
backgroundServiceIntent.putExtra("stopOnTerminate", config.getBoolean("stopOnTerminate")); }
if (config.has("startOnBoot")) {
editor.putBoolean("startOnBoot", config.getBoolean("startOnBoot"));
} }
if (config.has("forceReload")) { if (config.has("forceReload")) {
backgroundServiceIntent.putExtra("forceReload", config.getBoolean("forceReload")); editor.putBoolean("forceReload", config.getBoolean("forceReload"));
} }
if (config.has("url")) { if (config.has("url")) {
backgroundServiceIntent.putExtra("url", config.getString("url")); editor.putString("url", config.getString("url"));
} }
if (config.has("params")) { if (config.has("params")) {
backgroundServiceIntent.putExtra("params", config.getString("params")); try {
editor.putString("params", config.getJSONObject("params").toString());
} catch (JSONException e) {
Log.w(TAG, "- Failed to parse #params to JSONObject. Ignored");
}
} }
if (config.has("headers")) { if (config.has("headers")) {
backgroundServiceIntent.putExtra("headers", config.getString("headers")); try {
editor.putString("headers", config.getJSONObject("headers").toString());
} catch (JSONException e) {
Log.w(TAG, "- Failed to parse #headers to JSONObject. Ignored");
}
} }
editor.commit();
return true; return true;
} catch (JSONException e) { } catch (JSONException e) {
Log.w(TAG, e);
return false; return false;
} }
} }
......
...@@ -25,6 +25,7 @@ import android.app.PendingIntent; ...@@ -25,6 +25,7 @@ import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.location.Location; import android.location.Location;
import android.media.AudioManager; import android.media.AudioManager;
...@@ -58,19 +59,19 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl ...@@ -58,19 +59,19 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
/** /**
* @config {Integer} desiredAccuracy * @config {Integer} desiredAccuracy
*/ */
private Integer desiredAccuracy = 10; private Integer desiredAccuracy = 10;
/** /**
* @config {Float} distanceFilter * @config {Float} distanceFilter
*/ */
private Float distanceFilter = (float) 50; private Float distanceFilter = 50f;
/** /**
* @config {Boolean} isDebugging * @config {Boolean} isDebugging
*/ */
private Boolean isDebugging = false; private Boolean isDebugging = false;
/** /**
* @config {Boolean} stopOnTerminate * @config {Boolean} stopOnTerminate
*/ */
private Boolean stopOnTerminate = false; private Boolean stopOnTerminate = false;
// Android-only config // Android-only config
/** /**
...@@ -92,21 +93,21 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl ...@@ -92,21 +93,21 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
/** /**
* @config {Integer} stopTimeout The time to wait after ARS STILL to turn of GPS * @config {Integer} stopTimeout The time to wait after ARS STILL to turn of GPS
*/ */
private long stopTimeout = 0; private long stopTimeout = 0;
// HTTP config // HTTP config
/** /**
* @config {String} url For sending location to your server * @config {String} url For sending location to your server
*/ */
private String url = null; private String url = null;
/** /**
* @config {JSONObject} params For sending location to your server * @config {JSONObject} params For sending location to your server
*/ */
private JSONObject params = new JSONObject(); private JSONObject params = new JSONObject();
/** /**
* @config {JSONObject} headers For sending location to your server * @config {JSONObject} headers For sending location to your server
*/ */
private JSONObject headers = new JSONObject(); private JSONObject headers = new JSONObject();
// Flags // Flags
private Boolean isEnabled = false; private Boolean isEnabled = false;
...@@ -123,31 +124,38 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl ...@@ -123,31 +124,38 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
instance = this; instance = this;
EventBus.getDefault().register(this); EventBus.getDefault().register(this);
// Load config settings
SharedPreferences settings = getSharedPreferences(TAG, 0);
isEnabled = true; isEnabled = true;
stopOnTerminate = intent.getBooleanExtra("stopOnTerminate", true);
isDebugging = intent.getBooleanExtra("debug", false); isDebugging = settings.getBoolean("debug", false);
distanceFilter = intent.getFloatExtra("distanceFilter", 50); distanceFilter = settings.getFloat("distanceFilter", 50);
desiredAccuracy = intent.getIntExtra("desiredAccuracy", 10); desiredAccuracy = settings.getInt("desiredAccuracy", 10);
locationUpdateInterval = intent.getIntExtra("locationUpdateInterval", 30000); locationUpdateInterval = settings.getInt("locationUpdateInterval", 30000);
activityRecognitionInterval = intent.getIntExtra("activityRecognitionInterval", 60000); activityRecognitionInterval = settings.getInt("activityRecognitionInterval", 60000);
stopTimeout = intent.getLongExtra("stopTimeout", 0); stopTimeout = settings.getLong("stopTimeout", 0);
forceReload = intent.getBooleanExtra("forceReload", false); stopOnTerminate = settings.getBoolean("stopOnTerminate", true);
isMoving = intent.getBooleanExtra("isMoving", false); forceReload = settings.getBoolean("forceReload", false);
isMoving = settings.getBoolean("isMoving", false);
// HTTP Configuration // HTTP Configuration
url = intent.getStringExtra("url"); url = settings.getString("url", null);
try { if (settings.contains("params")) {
if (intent.hasExtra("params")) { try {
params = new JSONObject(intent.getStringExtra("params")); params = new JSONObject(settings.getString("params", "{}"));
} catch (JSONException e) {
Log.w(TAG, "- Faile to parse #params to JSONObject");
} }
if (intent.hasExtra("headers")) { }
headers = new JSONObject(intent.getStringExtra("headers")); if (settings.contains("headers")) {
try {
headers = new JSONObject(settings.getString("headers", "{}"));
} catch (JSONException e) {
Log.w(TAG, "- Failed to parse #headers to JSONObject");
} }
} catch (JSONException e) {
e.printStackTrace();
} }
Log.i(TAG, "----------------------------------------"); Log.i(TAG, "----------------------------------------");
Log.i(TAG, "- Start BackgroundGeolocationService"); Log.i(TAG, "- Start BackgroundGeolocationService");
Log.i(TAG, " debug: " + isDebugging); Log.i(TAG, " debug: " + isDebugging);
...@@ -159,6 +167,7 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl ...@@ -159,6 +167,7 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
Log.i(TAG, " stopOnTerminate: " + stopOnTerminate); Log.i(TAG, " stopOnTerminate: " + stopOnTerminate);
Log.i(TAG, " forceReload: " + forceReload); Log.i(TAG, " forceReload: " + forceReload);
Log.i(TAG, " isMoving: " + isMoving); Log.i(TAG, " isMoving: " + isMoving);
Log.i(TAG, "----------------------------------------"); Log.i(TAG, "----------------------------------------");
// For debug sounds, turn on ToneGenerator. // For debug sounds, turn on ToneGenerator.
...@@ -182,7 +191,7 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl ...@@ -182,7 +191,7 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
Log.e(TAG, "- GooglePlayServices unavailable"); Log.e(TAG, "- GooglePlayServices unavailable");
} }
return Service.START_REDELIVER_INTENT; return Service.START_STICKY;
} }
@Override @Override
...@@ -370,7 +379,8 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl ...@@ -370,7 +379,8 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
private Integer getLocationUpdateInterval() { private Integer getLocationUpdateInterval() {
// TODO Can add intelligence here based upon currentActivity. // TODO Can add intelligence here based upon currentActivity.
return locationUpdateInterval; SharedPreferences settings = getSharedPreferences(TAG, 0);
return settings.getInt("locationUpdateInterval", locationUpdateInterval);
} }
private Integer getFastestLocationUpdateInterval() { private Integer getFastestLocationUpdateInterval() {
...@@ -499,7 +509,8 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl ...@@ -499,7 +509,8 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
} }
private void requestActivityUpdates() { private void requestActivityUpdates() {
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(googleApiClient, activityRecognitionInterval, activityRecognitionPI); SharedPreferences settings = getSharedPreferences(TAG, 0);
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(googleApiClient, settings.getInt("activityRecognitionInterval", activityRecognitionInterval), activityRecognitionPI);
} }
private void removeActivityUpdates() { private void removeActivityUpdates() {
...@@ -509,12 +520,14 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl ...@@ -509,12 +520,14 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
private void requestLocationUpdates() { private void requestLocationUpdates() {
if (!isPaused || !isEnabled) { return; } // <-- Don't engage GPS when app is in foreground if (!isPaused || !isEnabled) { return; } // <-- Don't engage GPS when app is in foreground
SharedPreferences settings = getSharedPreferences(TAG, 0);
// Configure LocationRequest // Configure LocationRequest
locationRequest = LocationRequest.create() locationRequest = LocationRequest.create()
.setPriority(translateDesiredAccuracy(desiredAccuracy)) .setPriority(translateDesiredAccuracy(settings.getInt("desiredAccuracy", desiredAccuracy)))
.setInterval(getLocationUpdateInterval()) .setInterval(getLocationUpdateInterval())
.setFastestInterval(getFastestLocationUpdateInterval()) .setFastestInterval(getFastestLocationUpdateInterval())
.setSmallestDisplacement(distanceFilter); .setSmallestDisplacement(settings.getFloat("distanceFilter", distanceFilter));
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, locationUpdatePI); LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, locationUpdatePI);
} }
......
...@@ -3,6 +3,7 @@ package com.transistorsoft.cordova.bggeo; ...@@ -3,6 +3,7 @@ package com.transistorsoft.cordova.bggeo;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
/** /**
* This boot receiver is meant to handle the case where device is first booted after power up. * This boot receiver is meant to handle the case where device is first booted after power up.
...@@ -11,45 +12,18 @@ import android.util.Log; ...@@ -11,45 +12,18 @@ import android.util.Log;
* *
*/ */
public class BootReceiver extends BroadcastReceiver { public class BootReceiver extends BroadcastReceiver {
private static final String TAG = "BackgroundGeolocation"; private static final String TAG = "BackgroundGeolocation";
/**
* Background Geolocation Configuration params.
* If you're auto-running the service on BOOT, you need to manually configure the params here since the foreground app will not have been booted.
*/
private float distanceFilter = 50;
private Integer desiredAccuracy = 0;
private Integer locationUpdateInterval = 5000;
private Integer activityRecognitionInterval = 10000;
private long stopTimeout = 0;
private boolean debug = true;
private boolean stopOnTerminate = false;
private boolean forceReload = false;
private String url = "http://posttestserver.com/post.php?dir=cordova-background-geolocation";
private String params = "{'foo':'bar'}";
private String headers = "{'X-FOO':'BAR'}";
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.i(TAG, "- BootReceiver booting service"); SharedPreferences settings = context.getSharedPreferences(TAG, 0);
Intent backgroundServiceIntent = new Intent(context, BackgroundGeolocationService.class);
// Configure background geolocation service params.
backgroundServiceIntent.putExtra("distanceFilter", distanceFilter);
backgroundServiceIntent.putExtra("desiredAccuracy", desiredAccuracy);
backgroundServiceIntent.putExtra("locationUpdateInterval", locationUpdateInterval);
backgroundServiceIntent.putExtra("activityRecognitionInterval", activityRecognitionInterval);
backgroundServiceIntent.putExtra("stopTimeout", stopTimeout);
backgroundServiceIntent.putExtra("debug", debug);
backgroundServiceIntent.putExtra("stopOnTerminate", stopOnTerminate);
backgroundServiceIntent.putExtra("forceReload", forceReload);
backgroundServiceIntent.putExtra("url", url);
backgroundServiceIntent.putExtra("params", params);
backgroundServiceIntent.putExtra("headers", headers);
boolean startOnBoot = settings.getBoolean("startOnBoot", false);
if (!startOnBoot) {
return;
}
Log.i(TAG, "- BootReceiver booting service");
// Start the service. // Start the service.
context.startService(backgroundServiceIntent); context.startService(new Intent(context, BackgroundGeolocationService.class));
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment