Commit 2e639b14 authored by Chris Scott's avatar Chris Scott

Merge pull request #20 from christocracy/edge

Edge
parents b1b6b2f7 a9c1c12c
......@@ -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).
#####`@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
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`
......@@ -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```.
#### 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
#####`@param {String} activityType [AutomotiveNavigation, OtherNavigation, Fitness, Other]`
......
......@@ -179,8 +179,9 @@ var app = {
locationUpdateInterval: 5000,
activityRecognitionInterval: 10000,
stopTimeout: 0,
forceReload: true, // <-- 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.
forceReload: true, // <-- [Android] If the user closes the app **while location-tracking is started** , reboot app (WARNING: possibly distruptive to user)
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'
/**
* HTTP Feature: set an url to allow the native background service to POST locations to your server
......
......@@ -58,9 +58,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<!-- Autorun your app on device BOOT. UNCOMMENT TO ENABLE AUTO-BOOT -->
<!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</config-file>
<config-file target="res/xml/config.xml" parent="/*">
......
......@@ -15,6 +15,7 @@ import com.transistorsoft.cordova.bggeo.BackgroundGeolocationService.StationaryL
import de.greenrobot.event.EventBus;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.util.Log;
......@@ -58,20 +59,16 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin {
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "execute / action : " + action);
Activity activity = this.cordova.getActivity();
Boolean result = false;
Boolean result = false;
if (ACTION_START.equalsIgnoreCase(action) && !isEnabled) {
result = true;
isEnabled = true;
if (!BackgroundGeolocationService.isInstanceCreated()) {
activity.startService(backgroundServiceIntent);
}
this.setEnabled(true);
callbackContext.success();
} else if (ACTION_STOP.equalsIgnoreCase(action)) {
result = true;
isEnabled = false;
activity.stopService(backgroundServiceIntent);
this.setEnabled(false);
callbackContext.success();
} else if (ACTION_CONFIGURE.equalsIgnoreCase(action)) {
result = applyConfig(data);
......@@ -93,11 +90,9 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin {
callbackContext.success();
}
} else if (ACTION_SET_CONFIG.equalsIgnoreCase(action)) {
activity.stopService(backgroundServiceIntent);
result = applyConfig(data);
// TODO reconfigure Service
if (result) {
activity.startService(backgroundServiceIntent);
callbackContext.success();
} else {
callbackContext.error("- Configuration error!");
......@@ -109,48 +104,85 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin {
return result;
}
private void setEnabled(boolean value) {
isEnabled = value;
Activity activity = this.cordova.getActivity();
SharedPreferences settings = activity.getSharedPreferences(TAG, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("enabled", isEnabled);
editor.commit();
if (isEnabled) {
if (!BackgroundGeolocationService.isInstanceCreated()) {
activity.startService(backgroundServiceIntent);
}
} else {
activity.stopService(backgroundServiceIntent);
}
}
private boolean applyConfig(JSONArray data) {
Activity activity = this.cordova.getActivity();
try {
JSONObject config = data.getJSONObject(0);
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")) {
backgroundServiceIntent.putExtra("distanceFilter", (float) config.getInt("distanceFilter"));
editor.putFloat("distanceFilter", config.getInt("distanceFilter"));
}
if (config.has("desiredAccuracy")) {
backgroundServiceIntent.putExtra("desiredAccuracy", config.getInt("desiredAccuracy"));
editor.putInt("desiredAccuracy", config.getInt("desiredAccuracy"));
}
if (config.has("locationUpdateInterval")) {
backgroundServiceIntent.putExtra("locationUpdateInterval", config.getInt("locationUpdateInterval"));
editor.putInt("locationUpdateInterval", config.getInt("locationUpdateInterval"));
}
if (config.has("activityRecognitionInterval")) {
backgroundServiceIntent.putExtra("activityRecognitionInterval", config.getInt("activityRecognitionInterval"));
editor.putInt("activityRecognitionInterval", config.getInt("activityRecognitionInterval"));
}
if (config.has("stopTimeout")) {
backgroundServiceIntent.putExtra("stopTimeout", config.getLong("stopTimeout"));
editor.putLong("stopTimeout", config.getLong("stopTimeout"));
}
if (config.has("debug")) {
backgroundServiceIntent.putExtra("debug", config.getBoolean("debug"));
editor.putBoolean("debug", config.getBoolean("debug"));
}
if (config.has("stopOnTerminate")) {
stopOnTerminate = config.getBoolean("stopOnTerminate");
backgroundServiceIntent.putExtra("stopOnTerminate", config.getBoolean("stopOnTerminate"));
editor.putBoolean("stopOnTerminate", config.getBoolean("stopOnTerminate"));
}
if (config.has("startOnBoot")) {
editor.putBoolean("startOnBoot", config.getBoolean("startOnBoot"));
}
if (config.has("forceReload")) {
backgroundServiceIntent.putExtra("forceReload", config.getBoolean("forceReload"));
editor.putBoolean("forceReload", config.getBoolean("forceReload"));
}
if (config.has("url")) {
backgroundServiceIntent.putExtra("url", config.getString("url"));
editor.putString("url", config.getString("url"));
}
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")) {
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;
} catch (JSONException e) {
Log.w(TAG, e);
return false;
}
}
......
......@@ -25,6 +25,7 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.media.AudioManager;
......@@ -58,19 +59,19 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
/**
* @config {Integer} desiredAccuracy
*/
private Integer desiredAccuracy = 10;
private Integer desiredAccuracy = 10;
/**
* @config {Float} distanceFilter
*/
private Float distanceFilter = (float) 50;
private Float distanceFilter = 50f;
/**
* @config {Boolean} isDebugging
*/
private Boolean isDebugging = false;
private Boolean isDebugging = false;
/**
* @config {Boolean} stopOnTerminate
*/
private Boolean stopOnTerminate = false;
private Boolean stopOnTerminate = false;
// Android-only config
/**
......@@ -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
*/
private long stopTimeout = 0;
private long stopTimeout = 0;
// HTTP config
/**
* @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
*/
private JSONObject params = new JSONObject();
private JSONObject params = new JSONObject();
/**
* @config {JSONObject} headers For sending location to your server
*/
private JSONObject headers = new JSONObject();
private JSONObject headers = new JSONObject();
// Flags
private Boolean isEnabled = false;
......@@ -122,32 +123,42 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
public int onStartCommand(Intent intent, int flags, int startId) {
instance = this;
EventBus.getDefault().register(this);
EventBus eventBus = EventBus.getDefault();
if (!eventBus.isRegistered(this)) {
eventBus.register(this);
}
// Load config settings
SharedPreferences settings = getSharedPreferences(TAG, 0);
isEnabled = true;
stopOnTerminate = intent.getBooleanExtra("stopOnTerminate", true);
isDebugging = intent.getBooleanExtra("debug", false);
distanceFilter = intent.getFloatExtra("distanceFilter", 50);
desiredAccuracy = intent.getIntExtra("desiredAccuracy", 10);
locationUpdateInterval = intent.getIntExtra("locationUpdateInterval", 30000);
activityRecognitionInterval = intent.getIntExtra("activityRecognitionInterval", 60000);
stopTimeout = intent.getLongExtra("stopTimeout", 0);
forceReload = intent.getBooleanExtra("forceReload", false);
isMoving = intent.getBooleanExtra("isMoving", false);
isDebugging = settings.getBoolean("debug", false);
distanceFilter = settings.getFloat("distanceFilter", 50);
desiredAccuracy = settings.getInt("desiredAccuracy", 10);
locationUpdateInterval = settings.getInt("locationUpdateInterval", 30000);
activityRecognitionInterval = settings.getInt("activityRecognitionInterval", 60000);
stopTimeout = settings.getLong("stopTimeout", 0);
stopOnTerminate = settings.getBoolean("stopOnTerminate", true);
forceReload = settings.getBoolean("forceReload", false);
isMoving = settings.getBoolean("isMoving", false);
// HTTP Configuration
url = intent.getStringExtra("url");
try {
if (intent.hasExtra("params")) {
params = new JSONObject(intent.getStringExtra("params"));
url = settings.getString("url", null);
if (settings.contains("params")) {
try {
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, "- Start BackgroundGeolocationService");
Log.i(TAG, " debug: " + isDebugging);
......@@ -159,6 +170,7 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
Log.i(TAG, " stopOnTerminate: " + stopOnTerminate);
Log.i(TAG, " forceReload: " + forceReload);
Log.i(TAG, " isMoving: " + isMoving);
Log.i(TAG, "----------------------------------------");
// For debug sounds, turn on ToneGenerator.
......@@ -182,7 +194,7 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
Log.e(TAG, "- GooglePlayServices unavailable");
}
return Service.START_REDELIVER_INTENT;
return Service.START_STICKY;
}
@Override
......@@ -339,7 +351,9 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
startTone("long_beep");
// set our stationaryLocation
stationaryLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
EventBus.getDefault().post(new StationaryLocation(stationaryLocation));
if (stationaryLocation != null) {
EventBus.getDefault().post(new StationaryLocation(stationaryLocation));
}
}
}
}
......@@ -370,7 +384,8 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
private Integer getLocationUpdateInterval() {
// TODO Can add intelligence here based upon currentActivity.
return locationUpdateInterval;
SharedPreferences settings = getSharedPreferences(TAG, 0);
return settings.getInt("locationUpdateInterval", locationUpdateInterval);
}
private Integer getFastestLocationUpdateInterval() {
......@@ -499,7 +514,8 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
}
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() {
......@@ -509,12 +525,14 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
private void requestLocationUpdates() {
if (!isPaused || !isEnabled) { return; } // <-- Don't engage GPS when app is in foreground
SharedPreferences settings = getSharedPreferences(TAG, 0);
// Configure LocationRequest
locationRequest = LocationRequest.create()
.setPriority(translateDesiredAccuracy(desiredAccuracy))
.setPriority(translateDesiredAccuracy(settings.getInt("desiredAccuracy", desiredAccuracy)))
.setInterval(getLocationUpdateInterval())
.setFastestInterval(getFastestLocationUpdateInterval())
.setSmallestDisplacement(distanceFilter);
.setSmallestDisplacement(settings.getFloat("distanceFilter", distanceFilter));
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, locationUpdatePI);
}
......@@ -566,9 +584,12 @@ public class BackgroundGeolocationService extends Service implements GoogleApiCl
private void cleanUp() {
instance = null;
EventBus.getDefault().unregister(this);
removeActivityUpdates();
removeLocationUpdates();
googleApiClient.disconnect();
if (googleApiClient != null && googleApiClient.isConnected()) {
removeActivityUpdates();
removeLocationUpdates();
googleApiClient.disconnect();
}
}
/**
......
......@@ -3,6 +3,7 @@ package com.transistorsoft.cordova.bggeo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
/**
* This boot receiver is meant to handle the case where device is first booted after power up.
......@@ -11,45 +12,20 @@ import android.util.Log;
*
*/
public class BootReceiver extends BroadcastReceiver {
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'}";
private static final String TAG = "BackgroundGeolocation";
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "- BootReceiver booting service");
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);
SharedPreferences settings = context.getSharedPreferences(TAG, 0);
boolean startOnBoot = settings.getBoolean("startOnBoot", false);
boolean enabled = settings.getBoolean("enabled", false);
if (!startOnBoot || !enabled) {
return;
}
Log.i(TAG, "- BootReceiver booting 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