Commit d8c5132b authored by Chris Scott's avatar Chris Scott

Merge pull request #72 from christocracy/geofencing

Geofencing
parents a46e882b d8d4a6b7
......@@ -220,14 +220,17 @@ Configures the plugin's parameters (@see following [Config](https://github.com/c
```
bgGeo.configure(function(location, taskId) {
var coords = location.coords,
timestamp = location.timestamp
latitude = coords.latitude,
longitude = coords.longitude,
speed = coords.speed;
console.log("A location has arrived:", timestamp, latitude, longitude, speed);
try {
var coords = location.coords,
timestamp = location.timestamp
latitude = coords.latitude,
longitude = coords.longitude,
speed = coords.speed;
console.log("A location has arrived:", timestamp, latitude, longitude, speed);
} catch(e) {
console.error("An error occurred in my application code", e);
}
// The plugin runs your callback in a background-thread:
// you MUST signal to the native plugin when your callback is finished so it can halt the thread.
// IF YOU DON'T, iOS WILL KILL YOUR APP
......@@ -281,8 +284,11 @@ Your ```callbackFn``` will be executed each time the device has entered stationa
```
bgGeo.onStationary(function(location, taskId) {
console.log('- Device is stopped: ', location.latitude, location.longitude);
try {
console.log('- Device is stopped: ', location.latitude, location.longitude);
} catch(e) {
console.error('An error occurred in my application code', e);
}
// The plugin runs your callback in a background-thread:
// you MUST signal to the native plugin when your callback is finished so it can halt the thread.
// IF YOU DON'T, iOS WILL KILL YOUR APP
......@@ -352,8 +358,11 @@ Adds a geofence event-listener. Your supplied callback will be called when any
```
bgGeo.onGeofence(function(params, taskId) {
console.log('A geofence has been entered: ' + identifier);
try {
console.log('A geofence has been entered: ' + identifier);
} catch(e) {
console.error('An error occurred in my application code', e);
}
// The plugin runs your callback in a background-thread:
// you MUST signal to the native plugin when your callback is finished so it can halt the thread.
// IF YOU DON'T, iOS WILL KILL YOUR APP
......@@ -364,9 +373,20 @@ bgGeo.onGeofence(function(params, taskId) {
####`getLocations(callbackFn, failureFn)`
Fetch all the locations currently stored in native plugin's SQLite database. Your ```callbackFn`` will receive an ```Array``` of locations in the 1st parameter. Eg:
The `callbackFn` will be executed with following params:
######@param {Array} locations. The list of locations stored in SQLite database.
######@param {Integer} taskId The background taskId which you must send back to the native plugin via `bgGeo.finish(taskId)` in order to signal the end of your background thread.
```
bgGeo.getLocations(function(locations) {
console.log("locations: ", locations);
bgGeo.getLocations(function(locations, taskId) {
try {
console.log("locations: ", locations);
} catch(e) {
console.error("An error occurred in my application code");
}
bgGeo.finish(taskId);
});
```
......@@ -374,10 +394,22 @@ Fetch all the locations currently stored in native plugin's SQLite database. Yo
If the plugin is configured for HTTP with an ```#url``` and ```#autoSync: false```, this method will initiate POSTing the locations currently stored in the native SQLite database to your configured ```#url```. All records in the database will be DELETED. If you configured ```batchSync: true```, all the locations will be sent to your server in a single HTTP POST request, otherwise the plugin will create execute an HTTP post for **each** location in the database (REST-style). Your ```callbackFn``` will be executed and provided with an Array of all the locations from the SQLite database. If you configured the plugin for HTTP (by configuring an `#url`, your `callbackFn` will be executed after the HTTP request(s) have completed. If the plugin failed to sync to your server (possibly because of no network connection), the ```failureFn``` will be called with an ```errorMessage```. If you are **not** using the HTTP features, ```sync``` is the only way to clear the native SQLite datbase. Eg:
Your callback will be provided with the following params
######@param {Array} locations. The list of locations stored in SQLite database.
######@param {Integer} taskId The background taskId which you must send back to the native plugin via `bgGeo.finish(taskId)` in order to signal the end of your background thread.
```
bgGeo.sync(function(locations) {
// Here are all the locations from the database. The database is now EMPTY.
console.log('synced locations: ', locations);
bgGeo.sync(function(locations, taskId) {
try {
// Here are all the locations from the database. The database is now EMPTY.
console.log('synced locations: ', locations);
} catch(e) {
console.error('An error occurred in my application code', e);
}
// Be sure to call finish(taskId) in order to signal the end of the background-thread.
bgGeo.finish(taskId);
}, function(errorMessage) {
console.warn('Sync FAILURE: ', errorMessage);
});
......
......@@ -15,7 +15,8 @@ import com.google.android.gms.location.DetectedActivity;
import com.transistorsoft.locationmanager.BackgroundGeolocationService;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;
//import com.transistorsoft.locationmanager.BackgroundGeolocationService.StationaryLocation;
import android.app.AlertDialog;
import android.content.DialogInterface;
import de.greenrobot.event.EventBus;
import android.app.Activity;
......@@ -23,6 +24,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.util.Log;
import android.media.AudioManager;
import android.media.ToneGenerator;
public class CDVBackgroundGeolocation extends CordovaPlugin {
private static final String TAG = "BackgroundGeolocation";
......@@ -32,6 +35,7 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
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";
......@@ -64,6 +68,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
private CallbackContext paceChangeCallback;
private CallbackContext getGeofencesCallback;
private ToneGenerator toneGenerator;
private List<CallbackContext> geofenceCallbacks = new ArrayList<CallbackContext>();
public static boolean isActive() {
......@@ -76,6 +82,7 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
backgroundServiceIntent = new Intent(this.cordova.getActivity(), BackgroundGeolocationService.class);
toneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
// Register for events fired by our IntentService "LocationService"
}
......@@ -96,6 +103,10 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
} else if (ACTION_FINISH.equalsIgnoreCase(action)) {
result = true;
callbackContext.success();
} else if (ACTION_ERROR.equalsIgnoreCase(action)) {
result = true;
this.onError(data.getString(1));
callbackContext.success();
} else if (ACTION_CONFIGURE.equalsIgnoreCase(action)) {
result = applyConfig(data);
if (result) {
......@@ -213,11 +224,7 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
EventBus.getDefault().post(event);
} else if (ACTION_PLAY_SOUND.equalsIgnoreCase(action)) {
result = true;
Bundle event = new Bundle();
event.putString("name", action);
event.putBoolean("request", true);
event.putInt("soundId", data.getInt(0));
EventBus.getDefault().post(event);
playSound(data.getInt(0));
callbackContext.success();
}
return result;
......@@ -350,8 +357,10 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
if (ACTION_GET_LOCATIONS.equalsIgnoreCase(name)) {
try {
JSONArray json = new JSONArray(event.getString("data"));
PluginResult result = new PluginResult(PluginResult.Status.OK, json);
JSONObject params = new JSONObject();
params.put("locations", new JSONArray(event.getString("data")));
params.put("taskId", "android-bg-task-id");
PluginResult result = new PluginResult(PluginResult.Status.OK, params);
runInBackground(getLocationsCallback, result);
} catch (JSONException e) {
// TODO Auto-generated catch block
......@@ -363,8 +372,10 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
Boolean success = event.getBoolean("success");
if (success) {
try {
JSONArray json = new JSONArray(event.getString("data"));
PluginResult result = new PluginResult(PluginResult.Status.OK, json);
JSONObject params = new JSONObject();
params.put("locations", new JSONArray(event.getString("data")));
params.put("taskId", "android-bg-task-id");
PluginResult result = new PluginResult(PluginResult.Status.OK, params);
runInBackground(syncCallback, result);
} catch (JSONException e) {
// TODO Auto-generated catch block
......@@ -459,6 +470,12 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
}
}
private void playSound(int soundId) {
int duration = 1000;
toneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
toneGenerator.startTone(soundId, duration);
}
/**
* Run a javascript callback in Background
* @param cb
......@@ -474,6 +491,27 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
}
}
private void onError(String error) {
String message = "BG Geolocation caught a Javascript exception while running in background-thread:\n".concat(error);
Log.e(TAG, message);
SharedPreferences settings = this.cordova.getActivity().getSharedPreferences("TSLocationManager", 0);
// Show alert popup with js error
if (settings.contains("debug") && settings.getBoolean("debug", false)) {
playSound(68);
AlertDialog.Builder builder = new AlertDialog.Builder(this.cordova.getActivity());
builder.setMessage(message)
.setCancelable(false)
.setNegativeButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//do things
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
/**
* Override method in CordovaPlugin.
* Checks to see if it should turn off
......
......@@ -10,6 +10,7 @@
@interface CDVBackgroundGeolocation : CDVPlugin
@property (nonatomic, strong) NSString* syncCallbackId;
@property (nonatomic) UIBackgroundTaskIdentifier syncTaskId;
@property (nonatomic, strong) NSString* locationCallbackId;
@property (nonatomic, strong) NSMutableArray* geofenceListeners;
@property (nonatomic, strong) NSMutableArray* stationaryRegionListeners;
......@@ -18,6 +19,7 @@
- (void) start:(CDVInvokedUrlCommand*)command;
- (void) stop:(CDVInvokedUrlCommand*)command;
- (void) finish:(CDVInvokedUrlCommand*)command;
- (void) error:(CDVInvokedUrlCommand*)command;
- (void) onPaceChange:(CDVInvokedUrlCommand*)command;
- (void) setConfig:(CDVInvokedUrlCommand*)command;
- (void) addStationaryRegionListener:(CDVInvokedUrlCommand*)command;
......
......@@ -10,7 +10,7 @@
NSDictionary *config;
}
@synthesize syncCallbackId, locationCallbackId, geofenceListeners, stationaryRegionListeners;
@synthesize syncCallbackId, syncTaskId, locationCallbackId, geofenceListeners, stationaryRegionListeners;
- (void)pluginInitialize
{
......@@ -149,10 +149,16 @@
- (void) onSyncComplete:(NSNotification*)notification
{
NSArray *locations = [notification.userInfo objectForKey:@"locations"];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:locations];
NSDictionary *params = @{
@"locations": [notification.userInfo objectForKey:@"locations"],
@"taskId": @(syncTaskId)
};
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:params];
[self.commandDelegate sendPluginResult:result callbackId:syncCallbackId];
syncCallbackId = nil;
// Ready for another sync task.
syncCallbackId = nil;
syncTaskId = UIBackgroundTaskInvalid;
}
/**
......@@ -170,10 +176,12 @@
* Fetches current stationaryLocation
*/
- (void) getLocations:(CDVInvokedUrlCommand *)command
{
NSArray* locations = [bgGeo getLocations];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:locations];
{
NSDictionary *params = @{
@"locations": [bgGeo getLocations],
@"taskId": @([bgGeo createBackgroundTask])
};
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:params];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
......@@ -187,10 +195,15 @@
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
return;
}
syncCallbackId = command.callbackId;
// Important to set these before we execute #sync since this fires a *very fast* async NSNotification event!
syncCallbackId = command.callbackId;
syncTaskId = [bgGeo createBackgroundTask];
NSArray* locations = [bgGeo sync];
if (locations == nil) {
syncCallbackId = nil;
syncCallbackId = nil;
syncTaskId = UIBackgroundTaskInvalid;
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Sync failed. Is there a network connection?"];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
......@@ -273,6 +286,17 @@
UIBackgroundTaskIdentifier taskId = [[command.arguments objectAtIndex: 0] integerValue];
[bgGeo stopBackgroundTask:taskId];
}
/**
* Called by js to signal a caught exception from application code.
*/
-(void) error:(CDVInvokedUrlCommand*)command
{
UIBackgroundTaskIdentifier taskId = [[command.arguments objectAtIndex: 0] integerValue];
NSString *error = [command.arguments objectAtIndex:1];
[bgGeo error:taskId message:error];
}
/**
* If you don't stopMonitoring when application terminates, the app will be awoken still when a
* new location arrives, essentially monitoring the user's location even when they've killed the app.
......
......@@ -13,6 +13,7 @@
- (NSArray*) getLocations;
- (UIBackgroundTaskIdentifier) createBackgroundTask;
- (void) stopBackgroundTask:(UIBackgroundTaskIdentifier)taskId;
- (void) error:(UIBackgroundTaskIdentifier)taskId message:(NSString*)message;
- (void) onPaceChange:(BOOL)value;
- (void) setConfig:(NSDictionary*)command;
- (NSDictionary*) getStationaryLocation;
......
......@@ -6,7 +6,7 @@
<dict>
<key>Headers/TSLocationManager.h</key>
<data>
D1StsS2xjuq9sRvFuNw3O74hUYA=
++tA66F/FJQ/qMup0fGBER20r9U=
</data>
<key>Info.plist</key>
<data>
......@@ -21,7 +21,7 @@
<dict>
<key>Headers/TSLocationManager.h</key>
<data>
D1StsS2xjuq9sRvFuNw3O74hUYA=
++tA66F/FJQ/qMup0fGBER20r9U=
</data>
<key>Modules/module.modulemap</key>
<data>
......
......@@ -16,7 +16,9 @@ module.exports = {
* @property {Object} config
*/
config: {},
/**
* @private {Error} error
*/
configure: function(success, failure, config) {
var me = this;
config = config || {};
......@@ -32,7 +34,9 @@ module.exports = {
if (location.timestamp) {
location.timestamp = new Date(location.timestamp);
}
success.call(this, location, taskId);
me._runBackgroundTask(taskId, function() {
success.call(this, location, taskId);
});
}
exec(mySuccess,
failure || function() {},
......@@ -65,6 +69,16 @@ module.exports = {
'finish',
[taskId]);
},
error: function(taskId, message) {
if (!taskId) {
throw "BackgroundGeolocation#error must now be provided with a taskId as 1st param, eg: bgGeo.finish(taskId). taskId is provided by 2nd param in callback";
}
exec(function() {},
function() {},
'BackgroundGeoLocation',
'error',
[taskId, message]);
},
changePace: function(isMoving, success, failure) {
exec(success || function() {},
failure || function() {},
......@@ -79,7 +93,7 @@ module.exports = {
* @param {Integer} timeout
*/
setConfig: function(success, failure, config) {
this.apply(this.config, config);
this._apply(this.config, config);
exec(success || function() {},
failure || function() {},
'BackgroundGeoLocation',
......@@ -111,7 +125,10 @@ module.exports = {
taskId = params.taskId || 'task-id-undefined';
me.stationaryLocation = location;
success.call(me, location, taskId);
me._runBackgroundTask(taskId, function() {
success.call(me, location, taskId);
}, failure);
};
exec(callback,
failure || function() {},
......@@ -124,8 +141,12 @@ module.exports = {
throw "BackgroundGeolocation#getLocations requires a success callback";
}
var me = this;
var mySuccess = function(locations) {
success.call(this, me._setTimestamp(locations));
var mySuccess = function(params) {
var taskId = params.taskId;
var locations = me._setTimestamp(params.locations);
me._runBackgroundTask(taskId, function() {
success.call(me, locations, taskId);
});
}
exec(mySuccess,
failure || function() {},
......@@ -141,8 +162,13 @@ module.exports = {
throw "BackgroundGeolocation#sync requires a success callback";
}
var me = this;
var mySuccess = function(locations) {
success.call(this, me._setTimestamp(locations));
var mySuccess = function(params) {
var locations = me._setTimestamp(params.locations);
var taskId = params.taskId;
me._runBackgroundTask(taskId, function() {
success.call(me, locations, taskId);
});
}
exec(mySuccess,
failure || function() {},
......@@ -215,7 +241,10 @@ module.exports = {
var mySuccess = function(params) {
var taskId = params.taskId || 'task-id-undefined';
delete(params.taskId);
success.call(me, params, taskId);
me._runBackgroundTask(taskId, function() {
success.call(me, params, taskId);
}, failure);
};
exec(mySuccess,
failure || function() {},
......@@ -257,7 +286,24 @@ module.exports = {
}
return rs;
},
apply: function(destination, source) {
_runBackgroundTask: function(taskId, callback) {
var me = this;
try {
callback.call(this);
} catch(e) {
console.log("*************************************************************************************");
console.error("BackgroundGeolocation caught a Javascript Exception in your application code");
console.log(" while running in a background thread. Auto-finishing background-task:", taskId);
console.log(" to prevent application crash");
console.log("*************************************************************************************");
console.log("STACK:\n", e.stack);
console.error(e);
// And finally, here's our raison d'etre: catching the error in order to ensure background-task is completed.
this.error(taskId, e.message);
}
},
_apply: function(destination, source) {
source = source || {};
for (var property in source) {
if (source.hasOwnProperty(property)) {
......
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