Commit add9a85c authored by Chris Scott's avatar Chris Scott

Implement Android auto-boot on device startup. Implemented Android setConfig...

Implement Android auto-boot on device startup.  Implemented Android setConfig method.  Implement Android onStationary method.  Refactoring and cleanup.
parent 5710fba8
......@@ -16,7 +16,7 @@ Cordova Background Geolocation - Terms and conditions
- Allowing 3rd Parties to run Software on Licensee's Website[s] and Server[s];
- Publishing Software’s output to Licensee and 3rd Parties;
- Distribute verbatim copies of Software's output (including compiled binaries);
- Modify Software to suit Licensee’s needs and specifications.
- Modify Software to suit Licensee's needs and specifications.
2.2 Binary Restricted: Licensee may sublicense Software as a part of a larger work containing more than Software, distributed solely in Object or Binary form under a personal, non-sublicensable, limited license. Such redistribution shall be limited to unlimited codebases.</li><li>
......
/**
* cordova-background-geolocation
* Copyright (c) 2015, Transistor Software (9224-2932 Quebec Inc)
* All rights reserved.
* sales@transistorsoft.com
* http://transistorsoft.com
* @see LICENSE
*/
var ENV = (function() {
var localStorage = window.localStorage;
......@@ -133,7 +141,7 @@ var app = {
* This callback will be executed every time a geolocation is recorded in the background.
*/
var callbackFn = function(location) {
console.log('[js] BackgroundGeoLocation callback: ' + location.latitude + ',' + location.longitude);
console.log('[js] BackgroundGeoLocation callback: ' + JSON.stringify(location));
// Update our current-position marker.
app.setCurrentLocation(location);
......@@ -148,6 +156,7 @@ var app = {
// Only ios emits this stationary event
bgGeo.onStationary(function(location) {
console.log('[js] BackgroundGeoLocation onStationary ' + JSON.stringify(location));
if (!app.stationaryRadius) {
app.stationaryRadius = new google.maps.Circle({
fillColor: '#cc0000',
......@@ -165,14 +174,14 @@ var app = {
// BackgroundGeoLocation is highly configurable.
bgGeo.configure(callbackFn, failureFn, {
desiredAccuracy: 0, // <-- 0: highest power, highest accuracy; 1000: lowest power, lowest accuracy.
desiredAccuracy: 0,
stationaryRadius: 50,
distanceFilter: 50, // <-- minimum distance between location events
activityType: 'AutomotiveNavigation', // <-- [ios]
locationUpdateInterval: 30000, // <-- [android] minimum time between location updates, used in conjunction with #distanceFilter
activityRecognitionInterval: 10000, // <-- [android] sampling-rate activity-recognition system for movement/stationary detection
debug: true, // <-- enable this hear sounds, see notifications during life-cycle events.
stopOnTerminate: false // <-- enable this to clear background location settings when the app terminates
distanceFilter: 30,
locationUpdateInterval: 30000,
activityRecognitionInterval: 10000,
activityType: 'AutomotiveNavigation',
debug: true, // <-- enable this hear sounds for background-geolocation life-cycle.
stopOnTerminate: false // <-- enable this to clear background location settings when the app terminates
});
// Turn ON the background-geolocation system. The user will be tracked whenever they suspend the app.
......@@ -347,4 +356,4 @@ var app = {
}
};
app.initialize();
\ No newline at end of file
app.initialize();
......@@ -30,6 +30,13 @@
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<service android:name="com.transistorsoft.cordova.bggeo.BackgroundGeolocationService" />
<!-- autorun on boot receiver -->
<receiver android:name="com.transistorsoft.cordova.bggeo.BootReceiver" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest">
......@@ -40,6 +47,10 @@
<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. REMOVE THIS LINE TO DISABLE AUTO-BOOT -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</config-file>
<config-file target="res/xml/config.xml" parent="/*">
<feature name="BackgroundGeoLocation">
......
......@@ -38,7 +38,8 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
public static final String ACTION_ON_PACE_CHANGE = "onPaceChange";
public static final String ACTION_CONFIGURE = "configure";
public static final String ACTION_SET_CONFIG = "setConfig";
public static final String ACTION_ON_STATIONARY = "addStationaryRegionListener";
private PendingIntent locationUpdateService;
private Boolean isEnabled = false;
......@@ -46,18 +47,21 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
// Common config
private Integer desiredAccuracy = 10;
private Integer stationaryRadius = 50;
private Float distanceFilter = (float) 50;
private Boolean isDebugging = false;
private Boolean stopOnTerminate = false;
// Android-only config
private Integer locationUpdateInterval = 60000;
private Integer activityRecognitionInterval = 60000;
private Integer locationUpdateInterval = 60000;
private Integer activityRecognitionInterval = 60000;
// Geolocation callback
private CallbackContext callback;
// Called when DetectedActivity is STILL
private CallbackContext stationaryCallback;
private Location stationaryLocation;
private GoogleApiClient googleApiClient;
private DetectedActivity currentActivity;
......@@ -65,9 +69,7 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
private ToneGenerator toneGenerator;
@Override
protected void pluginInitialize() {
Log.d("BUS","registering");
protected void pluginInitialize() {
gWebView = this.webView;
Activity activity = this.cordova.getActivity();
......@@ -103,7 +105,6 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
if (ACTION_START.equalsIgnoreCase(action) && !isEnabled) {
result = true;
isEnabled = true;
isMoving = false;
requestActivityUpdates();
......@@ -116,26 +117,13 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
removeActivityUpdates();
callbackContext.success();
} else if (ACTION_CONFIGURE.equalsIgnoreCase(action)) {
result = true;
try {
JSONObject config = data.getJSONObject(0);
Log.i(TAG, "- configure: " + config.toString());
stationaryRadius = config.getInt("stationaryRadius");
distanceFilter = (float) config.getInt("distanceFilter");
desiredAccuracy = config.getInt("desiredAccuracy");
locationUpdateInterval = config.getInt("locationUpdateInterval");
activityRecognitionInterval = config.getInt("activityRecognitionInterval");
isDebugging = config.getBoolean("debug");
stopOnTerminate = config.getBoolean("stopOnTerminate");
result = applyConfig(data);
if (result) {
if (isDebugging) {
toneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
}
this.callback = callbackContext;
} catch (JSONException e) {
callbackContext.error("Configuration error " + e.getMessage());
} else {
callbackContext.error("- Configuration error!");
}
} else if (ACTION_ON_PACE_CHANGE.equalsIgnoreCase(action)) {
if (!isEnabled) {
......@@ -148,14 +136,48 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
callbackContext.success();
}
} else if (ACTION_SET_CONFIG.equalsIgnoreCase(action)) {
result = true;
result = applyConfig(data);
// TODO reconfigure Service
callbackContext.success();
if (result) {
callbackContext.success();
} else {
callbackContext.error("- Configuration error!");
}
} else if (ACTION_ON_STATIONARY.equalsIgnoreCase(action)) {
result = true;
this.stationaryCallback = callbackContext;
}
return result;
}
private boolean applyConfig(JSONArray data) {
try {
JSONObject config = data.getJSONObject(0);
Log.i(TAG, "- configure: " + config.toString());
if (config.has("distanceFilter")) {
distanceFilter = (float) config.getInt("distanceFilter");
}
if (config.has("desiredAccuracy")) {
desiredAccuracy = config.getInt("desiredAccuracy");
}
if (config.has("locationUpdateInterval")) {
locationUpdateInterval = config.getInt("locationUpdateInterval");
}
if (config.has("activityRecognitionInterval")) {
activityRecognitionInterval = config.getInt("activityRecognitionInterval");
}
if (config.has("debug")) {
isDebugging = config.getBoolean("debug");
}
if (config.has("stopOnTerminate")) {
stopOnTerminate = config.getBoolean("stopOnTerminate");
}
return true;
} catch (JSONException e) {
return false;
}
}
/**
* Translates a number representing desired accuracy of GeoLocation system from set [0, 10, 100, 1000].
* 0: most aggressive, most accurate, worst battery drain
......@@ -200,8 +222,15 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
private void setPace(Boolean moving) {
Log.i(TAG, "- setPace: " + moving);
boolean wasMoving = isMoving;
isMoving = moving;
if (moving && isEnabled) {
if (!wasMoving) {
startTone("doodly_doo");
}
stationaryLocation = null;
// Here's where the FusedLocationProvider is controlled.
LocationRequest request = LocationRequest.create()
.setPriority(translateDesiredAccuracy(desiredAccuracy))
.setInterval(this.locationUpdateInterval)
......@@ -211,21 +240,34 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
requestLocationUpdates(request);
} else {
removeLocationUpdates();
if (stationaryLocation == null) {
startTone("long_beep");
// Re-set our stationaryLocation
stationaryLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
// Inform Javascript of our stationaryLocation
fireStationaryListener();
}
}
}
public void onEventMainThread(DetectedActivity probableActivity) {
currentActivity = probableActivity;
Boolean wasMoving = isMoving;
boolean wasMoving = isMoving;
boolean nowMoving = false;
switch (probableActivity.getType()) {
case DetectedActivity.IN_VEHICLE:
case DetectedActivity.ON_BICYCLE:
case DetectedActivity.ON_FOOT:
isMoving = true;
case DetectedActivity.RUNNING:
case DetectedActivity.WALKING:
nowMoving = true;
break;
case DetectedActivity.STILL:
isMoving = false;
nowMoving = false;
break;
case DetectedActivity.UNKNOWN:
break;
......@@ -233,38 +275,73 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
break;
}
if (!wasMoving && isMoving) {
startTone("doodly_doo");
setPace(isMoving);
} else if (wasMoving && !isMoving) {
startTone("long_beep");
setPace(isMoving);
boolean startedMoving = !wasMoving && nowMoving;
boolean justStopped = wasMoving && !nowMoving;
boolean initialState = !nowMoving && (stationaryLocation == null);
if ( startedMoving || justStopped || initialState ) {
setPace(nowMoving);
}
String probableActivityName = getActivityName(probableActivity.getType());
Log.w(TAG, "- DetectedActivity: " + probableActivityName + ", confidence: " + probableActivity.getConfidence());
}
public void onEventMainThread(Location location) {
Log.i(TAG, "BUS Rx:" + location.toString());
startTone("beep");
try {
JSONObject loc = new JSONObject();
loc.put("latitude", location.getLatitude());
loc.put("longitude", location.getLongitude());
loc.put("accuracy", location.getAccuracy());
loc.put("speed", location.getSpeed());
loc.put("bearing", location.getBearing());
loc.put("altitude", location.getAltitude());
loc.put("timestamp", location.getTime());
PluginResult result = new PluginResult(PluginResult.Status.OK, loc);
PluginResult result = new PluginResult(PluginResult.Status.OK, locationToJson(location));
result.setKeepCallback(true);
runInBackground(callback, result);
}
/**
* Execute onStationary javascript callback when device is determined to have just stopped
*/
private void fireStationaryListener() {
Log.i(TAG, "- fire stationary listener");
if ( (stationaryCallback != null) && (stationaryLocation != null) ) {
final PluginResult result = new PluginResult(PluginResult.Status.OK, locationToJson(stationaryLocation));
result.setKeepCallback(true);
if(callback != null){
callback.sendPluginResult(result);
}
runInBackground(stationaryCallback, result);
}
}
/**
* Convert a Location instance to JSONObject
* @param Location
* @return JSONObject
*/
private JSONObject locationToJson(Location l) {
try {
JSONObject data = new JSONObject();
data.put("latitude", l.getLatitude());
data.put("longitude", l.getLongitude());
data.put("accuracy", l.getAccuracy());
data.put("speed", l.getSpeed());
data.put("bearing", l.getBearing());
data.put("altitude", l.getAltitude());
data.put("timestamp", l.getTime());
return data;
} catch (JSONException e) {
Log.e(TAG, "could not parse location");
return null;
}
}
/**
* Run a javascript callback in Background
* @param cb
* @param result
*/
private void runInBackground(final CallbackContext cb, final PluginResult result) {
if(cb != null){
cordova.getThreadPool().execute(new Runnable() {
public void run() {
cb.sendPluginResult(result);
}
});
}
}
......@@ -276,6 +353,10 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
return "on_bicycle";
case DetectedActivity.ON_FOOT:
return "on_foot";
case DetectedActivity.RUNNING:
return "running";
case DetectedActivity.WALKING:
return "walking";
case DetectedActivity.STILL:
return "still";
case DetectedActivity.UNKNOWN:
......@@ -302,8 +383,6 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, locationUpdateService);
}
/**
* Plays debug sound
* @param name
......@@ -357,6 +436,10 @@ public class BackgroundGeolocationPlugin extends CordovaPlugin implements Locati
* Checks to see if it should turn off
*/
public void onDestroy() {
Log.i(TAG, "- onDestroy");
Log.i(TAG, " stopOnTerminate: " + stopOnTerminate);
Log.i(TAG, " isEnabled: " + isEnabled);
if(isEnabled && stopOnTerminate || !isEnabled) {
removeActivityUpdates();
removeLocationUpdates();
......
......@@ -21,6 +21,9 @@ public class BackgroundGeolocationService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
// Determine whether the fore-ground Activity is running. If it's not, we'll reboot it.
boolean isPluginActive = BackgroundGeolocationPlugin.isActive();
if (ActivityRecognitionResult.hasResult(intent)) {
ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent);
DetectedActivity probableActivity = result.getMostProbableActivity();
......@@ -35,6 +38,7 @@ public class BackgroundGeolocationService extends IntentService {
case DetectedActivity.IN_VEHICLE:
case DetectedActivity.ON_BICYCLE:
case DetectedActivity.ON_FOOT:
case DetectedActivity.WALKING:
case DetectedActivity.RUNNING:
isMoving = true;
break;
......@@ -46,17 +50,20 @@ public class BackgroundGeolocationService extends IntentService {
break;
}
boolean isPushPluginActive = BackgroundGeolocationPlugin.isActive();
if (isMoving && !isPushPluginActive) {
// Force main-activity reload (if not running) if we're detected to be moving.
if (isMoving && !isPluginActive) {
forceMainActivityReload();
}
// Post activity to the bus.
EventBus.getDefault().post(probableActivity);
} else {
final Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
if (location != null) {
Log.i(TAG, "Location received: " + location.toString());
boolean isPushPluginActive = BackgroundGeolocationPlugin.isActive();
if (!isPushPluginActive) {
// Force main-activity reload when a location comes in.
if (!isPluginActive) {
forceMainActivityReload();
}
EventBus.getDefault().post(location);
......@@ -64,26 +71,34 @@ public class BackgroundGeolocationService extends IntentService {
}
}
/**
* This method has no other purpose than formatting the Activity for log-messages
*/
private String getActivityName(int activityType) {
switch (activityType) {
case DetectedActivity.IN_VEHICLE:
return "in_vehicle";
case DetectedActivity.ON_BICYCLE:
return "on_bicycle";
case DetectedActivity.ON_FOOT:
return "on_foot";
case DetectedActivity.STILL:
return "still";
case DetectedActivity.UNKNOWN:
return "unknown";
case DetectedActivity.TILTING:
return "tilting";
}
return "unknown";
}
switch (activityType) {
case DetectedActivity.IN_VEHICLE:
return "in_vehicle";
case DetectedActivity.ON_BICYCLE:
return "on_bicycle";
case DetectedActivity.ON_FOOT:
return "on_foot";
case DetectedActivity.RUNNING:
return "running";
case DetectedActivity.WALKING:
return "walking";
case DetectedActivity.STILL:
return "still";
case DetectedActivity.UNKNOWN:
return "unknown";
case DetectedActivity.TILTING:
return "tilting";
}
return "unknown";
}
/**
* Forces the main activity to re-launch if it's unloaded.
* Forces the main activity to re-launch if it's unloaded. This is how we're able to rely upon Javascript
* running always, since we for the app to boot.
*/
private void forceMainActivityReload() {
Log.w(TAG, "- Forcing main-activity reload");
......
package com.transistorsoft.cordova.bggeo;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.ActivityRecognition;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
/**
* This boot receiver is meant to handle the case where device is first booted after power up. This will initiate
* Google Play's ActivityRecognition API, whose events will be sent to BackgroundGeolocationService as usual.
* @author chris
*
*/
public class BootReceiver extends BroadcastReceiver implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private static final String TAG = "BackgroundGeolocation";
private GoogleApiClient googleApiClient;
private PendingIntent locationUpdateService;
private Integer activityRecognitionInterval = 10000;
private PendingResult pendingResult;
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "- BootReceiver auto-running ActivityRecognition system");
// GoogleApiClient connection is asynchronous process. @see #onConnected
pendingResult = goAsync();
// Connect to google-play services.
if (ConnectionResult.SUCCESS == GooglePlayServicesUtil.isGooglePlayServicesAvailable(context)) {
Log.i(TAG, "- Connecting to GooglePlayServices...");
googleApiClient = new GoogleApiClient.Builder(context)
.addApi(ActivityRecognition.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
googleApiClient.connect();
} else {
Log.e(TAG, "- GooglePlayServices unavailable");
}
// This is the IntentService we'll provide to google-play API.
locationUpdateService = PendingIntent.getService(context, 0, new Intent(context, BackgroundGeolocationService.class), PendingIntent.FLAG_UPDATE_CURRENT);
}
private void requestActivityUpdates() {
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(googleApiClient, activityRecognitionInterval, locationUpdateService);
}
@Override
public void onConnectionFailed(ConnectionResult arg0) {
// TODO Auto-generated method stub
pendingResult.finish();
}
@Override
public void onConnected(Bundle arg0) {
requestActivityUpdates();
pendingResult.finish();
}
@Override
public void onConnectionSuspended(int arg0) {
// TODO Auto-generated method stub
pendingResult.finish();
}
}
\ No newline at end of file
/**
* cordova-background-geolocation
* Copyright (c) 2015, Transistor Software (9224-2932 Quebec Inc)
* All rights reserved.
* sales@transistorsoft.com
* http://transistorsoft.com
* @see LICENSE
*/
var exec = require("cordova/exec");
module.exports = {
/**
......
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