Commit 3a7e7891 authored by Chris Scott's avatar Chris Scott

Implementing 3 new features requested by Lorin Metzger.

parent 88e618db
......@@ -20,8 +20,8 @@ The plugin creates the object `window.plugins.backgroundGeoLocation` with the me
`changePace(true) // engages aggressive monitoring immediately`
`onStationary(callback, fail)`
`onChangePace(callback, fail)`
`addGeofence(config, callback, fail)`
`removeGeofence(identifier, callback, fail)`
......@@ -276,7 +276,14 @@ bgGeo.changePace(true); // <-- Aggressive GPS monitoring immediately engaged.
bgGeo.changePace(false); // <-- Disable aggressive GPS monitoring. Engages stationary-mode.
```
####`onStationary(callbackFn, failureFn)`
####`onChangePace(callbackFn, failureFn)`
Your ```callbackFn``` will be executed each time the device has changed-state between **MOVING** or **STATIONARY. The ```callbackFn``` will be provided with a ```Location``` object as the 1st param, with the usual params (```latitude, longitude, accuracy, speed, bearing, altitude```), in addition to a ```taskId``` used to signal that your callback is finished.
######@param {Boolean} isMoving `false` if entered **STATIONARY** mode; `true` if entered **MOVING** mode.
######@param {Object} location The location at the state-change.
######@param {Integer} taskId The taskId used to send to bgGeo.finish(taskId) in order to signal completion of your callbackFn
####`onStationary(callbackFn, failureFn)` **DEPRECATED &mdash; Use `onChangePace` instead.
Your ```callbackFn``` will be executed each time the device has entered stationary-monitoring mode. The ```callbackFn``` will be provided with a ```Location``` object as the 1st param, with the usual params (```latitude, longitude, accuracy, speed, bearing, altitude```), in addition to a ```taskId``` used to signal that your callback is finished.
######@param {Object} location The Location data
......@@ -651,13 +658,29 @@ An interval of 0 is allowed, but not recommended, since location updates may be
the desired time between activity detections. Larger values will result in fewer activity detections while improving battery life. A value of 0 will result in activity detections at the fastest possible rate.
####`@param {Integer millis} minimumActivityRecognitionConfidence`
Each activity-recognition-result returned by the API is tagged with a "confidence" level expressed as a %. You can set your desired confidence to trigger a state-change. Defaults to `80`.
####`@param {String} triggerActivities`
These are the comma-delimited list of [activity-names](https://developers.google.com/android/reference/com/google/android/gms/location/DetectedActivity) returned by the `ActivityRecognition` API which will trigger a state-change from **stationary** to **moving**. By default, this list is set to all five **moving-states**: `"in_vehicle, on_bicycle, on_foot, running, walking"`. If you wish, you could configure the plugin to only engage **moving-mode** for vehicles by providing only `"in_vehicle"`.
####`@param {Integer minutes} stopTimeout`
The number of miutes to wait before turning off the GPS after the ActivityRecognition System (ARS) detects the device is ```STILL``` (defaults to 0, no timeout). If you don't set a value, the plugin is eager to turn off the GPS ASAP. An example use-case for this configuration is to delay GPS OFF while in a car waiting at a traffic light.
####`@param {Boolean} forceReload`
####`@param {Boolean} forceReloadOnMotionChange`
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). `forceReloadOnMotionChange: true` will reload the app only when a state-change occurs from **stationary -> moving** or vice-versa. (**WARNING** possibly disruptive to user).
####`@param {Boolean} forceReloadOnLocationChange`
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). `forceReloadOnLocationChange: true` will reload the app when a new location is recorded.
####`@param {Boolean} forceReloadOnGeofence`
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). `forceReloadOnGeolocation: true` will reload the app only when a geofence crossing event has occurred.
####`@param {Boolean} startOnBoot`
......
......@@ -32,23 +32,25 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
private static CordovaWebView gWebView;
public static Boolean forceReload = false;
public static final String ACTION_START = "start";
public static final String ACTION_STOP = "stop";
public static final String ACTION_FINISH = "finish";
public static final String ACTION_ERROR = "error";
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";
public static final String ACTION_GET_LOCATIONS = "getLocations";
public static final String ACTION_SYNC = "sync";
public static final String ACTION_GET_ODOMETER = "getOdometer";
public static final String ACTION_RESET_ODOMETER = "resetOdometer";
public static final String ACTION_ADD_GEOFENCE = "addGeofence";
public static final String ACTION_REMOVE_GEOFENCE = "removeGeofence";
public static final String ACTION_GET_GEOFENCES = "getGeofences";
public static final String ACTION_ON_GEOFENCE = "onGeofence";
public static final String ACTION_PLAY_SOUND = "playSound";
public static final String ACTION_START = "start";
public static final String ACTION_STOP = "stop";
public static final String ACTION_FINISH = "finish";
public static final String ACTION_ERROR = "error";
public static final String ACTION_CHANGE_PACE = "changePace";
public static final String ACTION_CONFIGURE = "configure";
public static final String ACTION_SET_CONFIG = "setConfig";
public static final String ACTION_ON_STATIONARY = "addStationaryRegionListener";
public static final String ACTION_ADD_MOTION_CHANGE_LISTENER = "addMotionChangeListener";
public static final String ACTION_ON_MOTION_CHANGE = "onMotionChange";
public static final String ACTION_GET_LOCATIONS = "getLocations";
public static final String ACTION_SYNC = "sync";
public static final String ACTION_GET_ODOMETER = "getOdometer";
public static final String ACTION_RESET_ODOMETER = "resetOdometer";
public static final String ACTION_ADD_GEOFENCE = "addGeofence";
public static final String ACTION_REMOVE_GEOFENCE = "removeGeofence";
public static final String ACTION_GET_GEOFENCES = "getGeofences";
public static final String ACTION_ON_GEOFENCE = "onGeofence";
public static final String ACTION_PLAY_SOUND = "playSound";
private Boolean isEnabled = false;
private Boolean stopOnTerminate = false;
......@@ -70,6 +72,7 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
private ToneGenerator toneGenerator;
private List<CallbackContext> motionChangeCallbacks = new ArrayList<CallbackContext>();
private List<CallbackContext> geofenceCallbacks = new ArrayList<CallbackContext>();
public static boolean isActive() {
......@@ -114,7 +117,7 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
} else {
callbackContext.error("- Configuration error!");
}
} else if (ACTION_ON_PACE_CHANGE.equalsIgnoreCase(action)) {
} else if (ACTION_CHANGE_PACE.equalsIgnoreCase(action)) {
if (!isEnabled) {
Log.w(TAG, "- Cannot change pace while disabled");
result = false;
......@@ -142,6 +145,9 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
} else if (ACTION_ON_STATIONARY.equalsIgnoreCase(action)) {
result = true;
this.stationaryCallback = callbackContext;
} else if (ACTION_ADD_MOTION_CHANGE_LISTENER.equalsIgnoreCase(action)) {
result = true;
motionChangeCallbacks.add(callbackContext);
} else if (ACTION_GET_LOCATIONS.equalsIgnoreCase(action)) {
result = true;
Bundle event = new Bundle();
......@@ -171,45 +177,18 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
resetOdometerCallback = callbackContext;
EventBus.getDefault().post(event);
} else if (ACTION_ADD_GEOFENCE.equalsIgnoreCase(action)) {
result = true;
JSONObject config = data.getJSONObject(0);
try {
Bundle event = new Bundle();
event.putString("name", action);
event.putBoolean("request", true);
event.putFloat("radius", (float) config.getLong("radius"));
event.putDouble("latitude", config.getDouble("latitude"));
event.putDouble("longitude", config.getDouble("longitude"));
event.putString("identifier", config.getString("identifier"));
if (config.has("notifyOnEntry")) {
event.putBoolean("notifyOnEntry", config.getBoolean("notifyOnEntry"));
}
if (config.has("notifyOnExit")) {
event.putBoolean("notifyOnExit", config.getBoolean("notifyOnExit"));
}
EventBus.getDefault().post(event);
result = onAddGeofence(data.getJSONObject(0));
if (result) {
callbackContext.success();
} catch (JSONException e) {
Log.w(TAG, e);
} else {
callbackContext.error("Failed to add geofence");
result = false;
}
} else if (ACTION_REMOVE_GEOFENCE.equalsIgnoreCase(action)) {
result = true;
try {
Bundle event = new Bundle();
event.putString("name", action);
event.putBoolean("request", true);
event.putString("identifier", data.getString(0));
EventBus.getDefault().post(event);
result = onRemoveGeofence(data.getString(0));
if (result) {
callbackContext.success();
} catch (JSONException e) {
Log.w(TAG, e);
} else {
callbackContext.error("Failed to add geofence");
result = false;
}
} else if (ACTION_ON_GEOFENCE.equalsIgnoreCase(action)) {
result = true;
......@@ -217,7 +196,6 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
} else if (ACTION_GET_GEOFENCES.equalsIgnoreCase(action)) {
result = true;
getGeofencesCallback = callbackContext;
Bundle event = new Bundle();
event.putString("name", action);
event.putBoolean("request", true);
......@@ -229,7 +207,39 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
}
return result;
}
private Boolean onAddGeofence(JSONObject config) {
try {
Bundle event = new Bundle();
event.putString("name", ACTION_ADD_GEOFENCE);
event.putBoolean("request", true);
event.putFloat("radius", (float) config.getLong("radius"));
event.putDouble("latitude", config.getDouble("latitude"));
event.putDouble("longitude", config.getDouble("longitude"));
event.putString("identifier", config.getString("identifier"));
if (config.has("notifyOnEntry")) {
event.putBoolean("notifyOnEntry", config.getBoolean("notifyOnEntry"));
}
if (config.has("notifyOnExit")) {
event.putBoolean("notifyOnExit", config.getBoolean("notifyOnExit"));
}
EventBus.getDefault().post(event);
return true;
} catch (JSONException e) {
Log.w(TAG, e);
return false;
}
}
private Boolean onRemoveGeofence(String identifier) {
Bundle event = new Bundle();
event.putString("name", ACTION_REMOVE_GEOFENCE);
event.putBoolean("request", true);
event.putString("identifier", identifier);
EventBus.getDefault().post(event);
return true;
}
private void setEnabled(boolean value) {
isEnabled = value;
......@@ -262,7 +272,6 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("activityIsActive", true);
editor.putBoolean("isMoving", isMoving);
if (config.has("distanceFilter")) {
editor.putFloat("distanceFilter", config.getInt("distanceFilter"));
......@@ -282,6 +291,9 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
if (config.has("minimumActivityRecognitionConfidence")) {
editor.putInt("minimumActivityRecognitionConfidence", config.getInt("minimumActivityRecognitionConfidence"));
}
if (config.has("triggerActivities")) {
editor.putString("triggerActivities", config.getString("triggerActivities"));
}
if (config.has("stopTimeout")) {
editor.putLong("stopTimeout", config.getLong("stopTimeout"));
}
......@@ -301,6 +313,15 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
if (config.has("forceReload")) {
editor.putBoolean("forceReload", config.getBoolean("forceReload"));
}
if (config.has("forceReloadOnLocationChange")) {
editor.putBoolean("forceReloadOnLocationChange", config.getBoolean("forceReloadOnLocationChange"));
}
if (config.has("forceReload")) { // @deprecated, alias to #forceReloadOnLocationChange
editor.putBoolean("forceReloadOnLocationChange", config.getBoolean("forceReload"));
}
if (config.has("forceReloadOnMotionChange")) {
editor.putBoolean("forceReloadOnMotionChange", config.getBoolean("forceReloadOnMotionChange"));
}
if (config.has("url")) {
editor.putString("url", config.getString("url"));
}
......@@ -391,7 +412,7 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
} else if (ACTION_RESET_ODOMETER.equalsIgnoreCase(name)) {
PluginResult result = new PluginResult(PluginResult.Status.OK);
resetOdometerCallback.sendPluginResult(result);
} else if (ACTION_ON_PACE_CHANGE.equalsIgnoreCase(name)) {
} else if (ACTION_CHANGE_PACE.equalsIgnoreCase(name)) {
PluginResult result = new PluginResult(PluginResult.Status.OK);
paceChangeCallback.sendPluginResult(result);
} else if (ACTION_GET_GEOFENCES.equalsIgnoreCase(name)) {
......@@ -405,6 +426,22 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
PluginResult result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
runInBackground(getGeofencesCallback, result);
}
} else if (ACTION_ON_MOTION_CHANGE.equalsIgnoreCase(name)) {
PluginResult result;
try {
JSONObject params = new JSONObject();
params.put("location", new JSONObject(event.getString("location")));
params.put("isMoving", event.getBoolean("isMoving"));
params.put("taskId", "android-bg-task-id");
result = new PluginResult(PluginResult.Status.OK, params);
} catch (JSONException e) {
e.printStackTrace();
result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
}
result.setKeepCallback(true);
for (CallbackContext callback : motionChangeCallbacks) {
runInBackground(callback, result);
}
}
}
......@@ -426,16 +463,9 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
result = new PluginResult(PluginResult.Status.OK, BackgroundGeolocationService.locationToJson(location, currentActivity));
result.setKeepCallback(true);
if (location instanceof com.transistorsoft.locationmanager.BackgroundGeolocationService.StationaryLocation) {
isMoving = false;
if (stationaryCallback != null) {
runInBackground(stationaryCallback, result);
}
} else {
isMoving = true;
result.setKeepCallback(true);
runInBackground(locationCallback, result);
}
isMoving = true;
result.setKeepCallback(true);
runInBackground(locationCallback, result);
}
/**
* EventBus handler for Geofencing events
......
......@@ -14,15 +14,17 @@
@property (nonatomic, strong) NSString* locationCallbackId;
@property (nonatomic, strong) NSMutableArray* geofenceListeners;
@property (nonatomic, strong) NSMutableArray* stationaryRegionListeners;
@property (nonatomic, strong) NSMutableArray* motionChangeListeners;
- (void) configure:(CDVInvokedUrlCommand*)command;
- (void) start:(CDVInvokedUrlCommand*)command;
- (void) stop:(CDVInvokedUrlCommand*)command;
- (void) finish:(CDVInvokedUrlCommand*)command;
- (void) error:(CDVInvokedUrlCommand*)command;
- (void) onPaceChange:(CDVInvokedUrlCommand*)command;
- (void) changePace:(CDVInvokedUrlCommand*)command;
- (void) setConfig:(CDVInvokedUrlCommand*)command;
- (void) addStationaryRegionListener:(CDVInvokedUrlCommand*)command;
- (void) addMotionChangeListener:(CDVInvokedUrlCommand*)command;
- (void) getStationaryLocation:(CDVInvokedUrlCommand *)command;
- (void) getLocations:(CDVInvokedUrlCommand *)command;
- (void) sync:(CDVInvokedUrlCommand *)command;
......
......@@ -10,14 +10,14 @@
NSDictionary *config;
}
@synthesize syncCallbackId, syncTaskId, locationCallbackId, geofenceListeners, stationaryRegionListeners;
@synthesize syncCallbackId, syncTaskId, locationCallbackId, geofenceListeners, stationaryRegionListeners, motionChangeListeners;
- (void)pluginInitialize
{
bgGeo = [[TSLocationManager alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onLocationChanged:) name:@"TSLocationManager.location" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onStationaryLocation:) name:@"TSLocationManager.stationary" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMotionChange:) name:@"TSLocationManager.motionchange" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onEnterGeofence:) name:@"TSLocationManager.geofence" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSyncComplete:) name:@"TSLocationManager.sync" object:nil];
}
......@@ -78,10 +78,10 @@
* Change pace to moving/stopped
* @param {Boolean} isMoving
*/
- (void) onPaceChange:(CDVInvokedUrlCommand *)command
- (void) changePace:(CDVInvokedUrlCommand *)command
{
BOOL moving = [[command.arguments objectAtIndex: 0] boolValue];
[bgGeo onPaceChange:moving];
[bgGeo changePace:moving];
}
/**
......@@ -102,16 +102,35 @@
}];
}
- (void) onStationaryLocation:(NSNotification*)notification
- (void) onMotionChange:(NSNotification*)notification
{
if (![self.stationaryRegionListeners count]) {
if (![self.stationaryRegionListeners count] && ![self.motionChangeListeners count]) {
return;
}
CLLocation *location = [notification.userInfo objectForKey:@"location"];
NSDictionary *locationData = [bgGeo locationToDictionary:location];
BOOL isMoving = [[notification.userInfo objectForKey:@"isMoving"] boolValue];
CLLocation *location = [notification.userInfo objectForKey:@"location"];
NSDictionary *locationData = [bgGeo locationToDictionary:location];
for (NSString *callbackId in self.stationaryRegionListeners) {
// @deprecated stationaryRegionListeners in favour of dual-function motionChangeListeners
if (!isMoving) {
for (NSString *callbackId in self.stationaryRegionListeners) {
NSLog(@"- CALLBACK: %@", callbackId);
NSDictionary *params = @{
@"isMoving": @(isMoving),
@"location": locationData,
@"taskId": @([bgGeo createBackgroundTask])
};
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:params];
[result setKeepCallbackAsBool:YES];
[self.commandDelegate runInBackground:^{
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
}];
}
}
for (NSString *callbackId in self.motionChangeListeners) {
NSDictionary *params = @{
@"isMoving": @(isMoving),
@"location": locationData,
@"taskId": @([bgGeo createBackgroundTask])
};
......@@ -120,7 +139,6 @@
[self.commandDelegate runInBackground:^{
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
}];
}
}
......@@ -149,6 +167,9 @@
- (void) onSyncComplete:(NSNotification*)notification
{
if (syncCallbackId == nil) {
return;
}
NSDictionary *params = @{
@"locations": [notification.userInfo objectForKey:@"locations"],
@"taskId": @(syncTaskId)
......@@ -217,6 +238,14 @@
[self.stationaryRegionListeners addObject:command.callbackId];
}
- (void) addMotionChangeListener:(CDVInvokedUrlCommand*)command
{
if (self.motionChangeListeners == nil) {
self.motionChangeListeners = [[NSMutableArray alloc] init];
}
[self.motionChangeListeners addObject:command.callbackId];
}
- (void) addGeofence:(CDVInvokedUrlCommand*)command
{
NSDictionary *cfg = [command.arguments objectAtIndex:0];
......
......@@ -14,7 +14,7 @@
- (UIBackgroundTaskIdentifier) createBackgroundTask;
- (void) stopBackgroundTask:(UIBackgroundTaskIdentifier)taskId;
- (void) error:(UIBackgroundTaskIdentifier)taskId message:(NSString*)message;
- (void) onPaceChange:(BOOL)value;
- (void) changePace:(BOOL)value;
- (void) setConfig:(NSDictionary*)command;
- (NSDictionary*) getStationaryLocation;
- (void) onSuspend:(NSNotification *)notification;
......
......@@ -6,7 +6,7 @@
<dict>
<key>Headers/TSLocationManager.h</key>
<data>
++tA66F/FJQ/qMup0fGBER20r9U=
XspAWrxbSpr4Vn4KKi6gq+GKYz4=
</data>
<key>Info.plist</key>
<data>
......@@ -21,7 +21,7 @@
<dict>
<key>Headers/TSLocationManager.h</key>
<data>
++tA66F/FJQ/qMup0fGBER20r9U=
XspAWrxbSpr4Vn4KKi6gq+GKYz4=
</data>
<key>Modules/module.modulemap</key>
<data>
......
......@@ -83,7 +83,7 @@ module.exports = {
exec(success || function() {},
failure || function() {},
'BackgroundGeoLocation',
'onPaceChange',
'changePace',
[isMoving]);
},
/**
......@@ -112,6 +112,7 @@ module.exports = {
},
/**
* Add a stationary-region listener. Whenever the devices enters "stationary-mode", your #success callback will be executed with #location param containing #radius of region
* @deprecated in favour of dual-function #onMotionChange
* @param {Function} success
* @param {Function} failure [optional] NOT IMPLEMENTED
*/
......@@ -136,6 +137,35 @@ module.exports = {
'addStationaryRegionListener',
[]);
},
/**
* Add a movement-state-change listener. Whenever the devices enters "stationary" or "moving" mode, your #success callback will be executed with #location param containing #radius of region
* @param {Function} success
* @param {Function} failure [optional] NOT IMPLEMENTED
*/
onMotionChange: function(success, failure) {
var me = this;
success = success || function(isMoving, location, taskId) {
me.finish(taskId);
};
var callback = function(params) {
var isMoving = params.isMoving;
var location = params.location;
var taskId = params.taskId || 'task-id-undefined';
if (!isMoving) {
me.stationaryLocation = location;
}
me._runBackgroundTask(taskId, function() {
success.call(me, isMoving, location, taskId);
}, failure);
};
exec(callback,
failure || function() {},
'BackgroundGeoLocation',
'addMotionChangeListener',
[]);
},
getLocations: function(success, failure) {
if (typeof(success) !== 'function') {
throw "BackgroundGeolocation#getLocations requires a success callback";
......
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