Commit 85b75257 authored by Chris Scott's avatar Chris Scott

Merge pull request #59 from christocracy/geofencing

Geofencing
parents b8b92c56 41ee596a
This diff is collapsed.
......@@ -138,26 +138,28 @@ var app = {
app.onClickHome();
/**
* This would be your own callback for Ajax-requests after POSTing background geolocation to your server.
*/
var yourAjaxCallback = function(response) {
// Very important to call #finish -- it signals to the native plugin that it can destroy the background thread, which your callbackFn is running in.
// IF YOU DON'T, THE OS CAN KILL YOUR APP FOR RUNNING TOO LONG IN THE BACKGROUND
bgGeo.finish();
};
/**
* This callback will be executed every time a geolocation is recorded in the background.
*/
var callbackFn = function(location) {
var callbackFn = function(location, taskId) {
console.log('[js] BackgroundGeoLocation callback: ' + JSON.stringify(location));
// Update our current-position marker.
app.setCurrentLocation(location);
// After you Ajax callback is complete, you MUST signal to the native code, which is running a background-thread, that you're done and it can gracefully kill that thread.
yourAjaxCallback.call(this);
/**
* This would be your own callback for Ajax-requests after POSTing background geolocation to your server.
*/
var yourAjaxCallback = function(response) {
// Very important to call #finish -- it signals to the native plugin that it can destroy the background thread, which your callbackFn is running in.
// IF YOU DON'T, THE OS CAN KILL YOUR APP FOR RUNNING TOO LONG IN THE BACKGROUND
bgGeo.finish(taskId);
};
yourAjaxCallback.call(this, {status: 200});
};
var failureFn = function(error) {
......@@ -165,7 +167,7 @@ var app = {
};
// Only ios emits this stationary event
bgGeo.onStationary(function(location) {
bgGeo.onStationary(function(location, taskId) {
console.log('[js] BackgroundGeoLocation onStationary ' + JSON.stringify(location));
app.setCurrentLocation(location);
......@@ -187,6 +189,8 @@ var app = {
var center = new google.maps.LatLng(coords.latitude, coords.longitude);
app.stationaryRadius.setRadius(radius);
app.stationaryRadius.setCenter(center);
bgGeo.finish(taskId);
});
......@@ -195,22 +199,22 @@ var app = {
debug: true, // <-- enable this hear sounds for background-geolocation life-cycle.
desiredAccuracy: 0,
stationaryRadius: 50,
distanceFilter: 25,
distanceFilter: 50,
disableElasticity: false, // <-- [iOS] Default is 'false'. Set true to disable speed-based distanceFilter elasticity
locationUpdateInterval: 5000,
minimumActivityRecognitionConfidence: 80, // 0-100%. Minimum activity-confidence for a state-change
fastestLocationUpdateInterval: 5000,
activityRecognitionInterval: 10000,
stopTimeout: 0,
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.
forceReload: false, // <-- [Android] If the user closes the app **while location-tracking is started** , reboot app (WARNING: possibly distruptive to user)
stopOnTerminate: true, // <-- [Android] Allow the background-service to run headless when user closes the app.
startOnBoot: false, // <-- [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
*/
url: 'http://posttestserver.com/post.php?dir=cordova-background-geolocation',
batchSync: false, // <-- [Default: false] Set true to sync locations to server in a single HTTP request.
batchSync: true, // <-- [Default: false] Set true to sync locations to server in a single HTTP request.
autoSync: true, // <-- [Default: true] Set true to sync each location to server as it arrives.
maxDaysToPersist: 1, // <-- Maximum days to persist a location in plugin's SQLite database when HTTP fails
headers: {
......@@ -221,9 +225,10 @@ var app = {
}
});
bgGeo.onGeofence(function(identifier) {
bgGeo.onGeofence(function(identifier, taskId) {
alert('Enter Geofence: ' + identifier);
console.log('[js] Geofence ENTER: ', identifier);
console.log('[js] Geofence ENTER: ', identifier, taskId);
bgGeo.finish(taskId);
});
// Add longpress event for adding GeoFence of hard-coded radius 200m.
......
......@@ -30,6 +30,7 @@
<service android:name="com.transistorsoft.locationmanager.BackgroundGeolocationService" />
<service android:name="com.transistorsoft.locationmanager.LocationService" />
<service android:name="com.transistorsoft.locationmanager.ActivityRecognitionService" />
<service android:name="com.transistorsoft.locationmanager.GeofenceService" />
<receiver android:name="com.transistorsoft.locationmanager.BootReceiver" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
......
package com.transistorsoft.cordova.bggeo;
import java.util.List;
import java.util.ArrayList;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
......@@ -11,8 +13,8 @@ import android.os.Bundle;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;
import com.transistorsoft.locationmanager.BackgroundGeolocationService;
import com.transistorsoft.locationmanager.BackgroundGeolocationService.PaceChangeEvent;
import com.transistorsoft.locationmanager.BackgroundGeolocationService.PausedEvent;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;
//import com.transistorsoft.locationmanager.BackgroundGeolocationService.StationaryLocation;
import de.greenrobot.event.EventBus;
......@@ -37,6 +39,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
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_ON_GEOFENCE = "onGeofence";
private Boolean isEnabled = false;
private Boolean stopOnTerminate = false;
......@@ -48,16 +52,13 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
// Geolocation callback
private CallbackContext locationCallback;
// Called when DetectedActivity is STILL
private CallbackContext stationaryCallback;
private CallbackContext getLocationsCallback;
private CallbackContext syncCallback;
private CallbackContext getOdometerCallback;
private CallbackContext resetOdometerCallback;
private CallbackContext paceChangeCallback;
private List<CallbackContext> geofenceCallbacks = new ArrayList<CallbackContext>();
public static boolean isActive() {
return gWebView != null;
......@@ -70,7 +71,6 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
backgroundServiceIntent = new Intent(this.cordova.getActivity(), BackgroundGeolocationService.class);
// Register for events fired by our IntentService "LocationService"
EventBus.getDefault().register(this);
}
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
......@@ -98,17 +98,22 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
Log.w(TAG, "- Cannot change pace while disabled");
result = false;
callbackContext.error("Cannot #changePace while disabled");
} else {
PaceChangeEvent event = new PaceChangeEvent(data.getBoolean(0));
EventBus.getDefault().post(event);
} else {
result = true;
callbackContext.success();
paceChangeCallback = callbackContext;
Bundle event = new Bundle();
event.putString("name", action);
event.putBoolean("request", true);
event.putBoolean("isMoving", data.getBoolean(0));
EventBus.getDefault().post(event);
}
} else if (ACTION_SET_CONFIG.equalsIgnoreCase(action)) {
result = applyConfig(data);
// TODO reconfigure Service
if (result) {
Bundle event = new Bundle();
event.putString("name", action);
event.putBoolean("request", true);
EventBus.getDefault().post(event);
callbackContext.success();
} else {
callbackContext.error("- Configuration error!");
......@@ -144,6 +149,29 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
event.putBoolean("request", true);
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"));
EventBus.getDefault().post(event);
callbackContext.success();
} catch (JSONException e) {
Log.w(TAG, e);
callbackContext.error("Failed to add geofence");
result = false;
}
} else if (ACTION_ON_GEOFENCE.equalsIgnoreCase(action)) {
result = true;
geofenceCallbacks.add(callbackContext);
}
return result;
}
......@@ -159,10 +187,12 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
editor.commit();
if (isEnabled) {
EventBus.getDefault().register(this);
if (!BackgroundGeolocationService.isInstanceCreated()) {
activity.startService(backgroundServiceIntent);
}
} else {
EventBus.getDefault().unregister(this);
activity.stopService(backgroundServiceIntent);
}
}
......@@ -189,6 +219,9 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
if (config.has("locationUpdateInterval")) {
editor.putInt("locationUpdateInterval", config.getInt("locationUpdateInterval"));
}
if (config.has("fastestLocationUpdateInterval")) {
editor.putInt("fastestLocationUpdateInterval", config.getInt("fastestLocationUpdateInterval"));
}
if (config.has("activityRecognitionInterval")) {
editor.putLong("activityRecognitionInterval", config.getLong("activityRecognitionInterval"));
}
......@@ -205,7 +238,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
editor.putInt("stopAfterElapsedMinutes", config.getInt("stopAfterElapsedMinutes"));
}
if (config.has("stopOnTerminate")) {
editor.putBoolean("stopOnTerminate", config.getBoolean("stopOnTerminate"));
stopOnTerminate = config.getBoolean("stopOnTerminate");
editor.putBoolean("stopOnTerminate", stopOnTerminate);
}
if (config.has("startOnBoot")) {
editor.putBoolean("startOnBoot", config.getBoolean("startOnBoot"));
......@@ -296,7 +330,10 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
runInBackground(getOdometerCallback, result);
} else if (ACTION_RESET_ODOMETER.equalsIgnoreCase(name)) {
PluginResult result = new PluginResult(PluginResult.Status.OK);
runInBackground(resetOdometerCallback, result);
resetOdometerCallback.sendPluginResult(result);
} else if (ACTION_ON_PACE_CHANGE.equalsIgnoreCase(name)) {
PluginResult result = new PluginResult(PluginResult.Status.OK);
paceChangeCallback.sendPluginResult(result);
}
}
......@@ -329,7 +366,30 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
runInBackground(locationCallback, result);
}
}
/**
* EventBus handler for Geofencing events
*/
public void onEventMainThread(GeofencingEvent geofenceEvent) {
Log.i(TAG, "- Rx GeofencingEvent: " + geofenceEvent);
if (!geofenceCallbacks.isEmpty()) {
for (Geofence geofence : geofenceEvent.getTriggeringGeofences()) {
JSONObject params = new JSONObject();
try {
params.put("identifier", geofence.getRequestId());
} catch (JSONException e) {
e.printStackTrace();
}
PluginResult result = new PluginResult(PluginResult.Status.OK, params);
result.setKeepCallback(true);
for (CallbackContext callback : geofenceCallbacks) {
runInBackground(callback, result);
}
}
}
}
/**
* Run a javascript callback in Background
* @param cb
......@@ -356,6 +416,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
Activity activity = this.cordova.getActivity();
EventBus.getDefault().unregister(this);
SharedPreferences settings = activity.getSharedPreferences("TSLocationManager", 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("activityIsActive", false);
......
......@@ -10,7 +10,7 @@
@interface CDVBackgroundGeolocation : CDVPlugin
@property (nonatomic, strong) NSString* syncCallbackId;
@property (nonatomic, strong) NSString* geofenceCallbackId;
@property (nonatomic, strong) NSMutableArray* geofenceListeners;
@property (nonatomic, strong) NSMutableArray* stationaryRegionListeners;
- (void) configure:(CDVInvokedUrlCommand*)command;
......
......@@ -10,7 +10,7 @@
NSDictionary *config;
}
@synthesize syncCallbackId, geofenceCallbackId, stationaryRegionListeners;
@synthesize syncCallbackId, geofenceListeners, stationaryRegionListeners;
- (void)pluginInitialize
{
......@@ -87,44 +87,61 @@
* location handler from BackgroundGeolocation
*/
- (void)onLocationChanged:(NSNotification*)notification {
CLLocation *location = notification.object;
NSDictionary *locationData = [bgGeo locationToDictionary:location];
CLLocation *location = [notification.userInfo objectForKey:@"location"];
CDVPluginResult* result = nil;
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:locationData];
NSDictionary *params = @{
@"location": [bgGeo locationToDictionary:location],
@"taskId": @([bgGeo createBackgroundTask])
};
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:params];
[result setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:result callbackId:self.syncCallbackId];
[self.commandDelegate runInBackground:^{
[self.commandDelegate sendPluginResult:result callbackId:self.syncCallbackId];
}];
}
- (void) onStationaryLocation:(NSNotification*)notification
{
CLLocation *location = notification.object;
NSDictionary *locationData = [bgGeo locationToDictionary:location];
if (![self.stationaryRegionListeners count]) {
[bgGeo stopBackgroundTask];
return;
}
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:locationData];
[result setKeepCallbackAsBool:YES];
for (NSString *callbackId in self.stationaryRegionListeners)
{
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
CLLocation *location = [notification.userInfo objectForKey:@"location"];
NSDictionary *locationData = [bgGeo locationToDictionary:location];
for (NSString *callbackId in self.stationaryRegionListeners) {
NSDictionary *params = @{
@"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];
}];
}
[bgGeo stopBackgroundTask];
}
- (void) onEnterGeofence:(NSNotification*)notification
{
if (self.geofenceCallbackId == nil) {
if (![self.geofenceListeners count]) {
return;
}
CLCircularRegion *region = notification.object;
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:region.identifier];
[result setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:result callbackId:self.geofenceCallbackId];
CLCircularRegion *region = [notification.userInfo objectForKey:@"geofence"];
for (NSString *callbackId in self.stationaryRegionListeners) {
NSDictionary *params = @{
@"identifier": region.identifier,
@"taskId": @([bgGeo createBackgroundTask])
};
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:params];
[result setKeepCallbackAsBool:YES];
[self.commandDelegate runInBackground:^{
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
}];
}
}
/**
* Fetches current stationaryLocation
......@@ -187,14 +204,18 @@
- (void) onGeofence:(CDVInvokedUrlCommand*)command
{
self.geofenceCallbackId = command.callbackId;
if (self.geofenceListeners == nil) {
self.geofenceListeners = [[NSMutableArray alloc] init];
}
[self.geofenceListeners addObject:command.callbackId];
}
/**
* Called by js to signify the end of a background-geolocation event
*/
-(void) finish:(CDVInvokedUrlCommand*)command
{
[bgGeo finish];
UIBackgroundTaskIdentifier taskId = [[command.arguments objectAtIndex: 0] integerValue];
[bgGeo stopBackgroundTask:taskId];
}
/**
* If you don't stopMonitoring when application terminates, the app will be awoken still when a
......
......@@ -9,10 +9,10 @@
- (void) configure:(NSDictionary*)config;
- (void) start;
- (void) stop;
- (void) finish;
- (NSArray*) sync;
- (NSArray*) getLocations;
- (void) stopBackgroundTask;
- (UIBackgroundTaskIdentifier) createBackgroundTask;
- (void) stopBackgroundTask:(UIBackgroundTaskIdentifier)taskId;
- (void) onPaceChange:(BOOL)value;
- (void) setConfig:(NSDictionary*)command;
- (NSDictionary*) getStationaryLocation;
......
......@@ -6,7 +6,7 @@
<dict>
<key>Headers/TSLocationManager.h</key>
<data>
93eNJDqyN5vuBJn9oYeT7lb2x1w=
w7pffjKj41qqtvUN0J6fOruESPM=
</data>
<key>Info.plist</key>
<data>
......@@ -21,7 +21,7 @@
<dict>
<key>Headers/TSLocationManager.h</key>
<data>
93eNJDqyN5vuBJn9oYeT7lb2x1w=
w7pffjKj41qqtvUN0J6fOruESPM=
</data>
<key>Modules/module.modulemap</key>
<data>
......
......@@ -9,24 +9,30 @@
var exec = require("cordova/exec");
module.exports = {
/**
* @property {Object} stationaryRegion
* @property {Object} stationaryLocation
*/
stationaryRegion: null,
stationaryLocation: null,
/**
* @property {Object} config
*/
config: {},
configure: function(success, failure, config) {
var me = this;
config = config || {};
this.config = config;
success = success || function(location) {};
var mySuccess = function(location) {
success = success || function(location, taskId) {
me.finish(taskId);
};
var mySuccess = function(params) {
var location = params.location || params;
var taskId = params.taskId || 'task-id-undefined';
// Transform timestamp to Date instance.
if (location.timestamp) {
location.timestamp = new Date(location.timestamp);
}
success.call(this, location);
success.call(this, location, taskId);
}
exec(mySuccess,
failure || function() {},
......@@ -49,12 +55,15 @@ module.exports = {
'stop',
[]);
},
finish: function(success, failure) {
finish: function(taskId, success, failure) {
if (!taskId) {
throw "BackgroundGeolocation#finish must now be provided with a taskId as 1st param, eg: bgGeo.finish(taskId). taskId is provided by 2nd param in callback";
}
exec(success || function() {},
failure || function() {},
'BackgroundGeoLocation',
'finish',
[]);
[taskId]);
},
changePace: function(isMoving, success, failure) {
exec(success || function() {},
......@@ -94,10 +103,15 @@ module.exports = {
*/
onStationary: function(success, failure) {
var me = this;
success = success || function() {};
var callback = function(region) {
me.stationaryRegion = region;
success.apply(me, arguments);
success = success || function(location, taskId) {
me.finish(taskId);
};
var callback = function(params) {
var location = params.location || params,
taskId = params.taskId || 'task-id-undefined';
me.stationaryLocation = location;
success.call(me, location, taskId);
};
exec(callback,
failure || function() {},
......@@ -180,7 +194,13 @@ module.exports = {
if (!typeof(success) === 'function') {
throw "#onGeofence requires a success callback";
}
exec(success,
var me = this;
var mySuccess = function(params) {
var identifier = params.identifier,
taskId = params.taskId || 'task-id-undefined';
success.call(me, identifier, taskId);
}
exec(mySuccess,
failure || function() {},
'BackgroundGeoLocation',
'onGeofence',
......
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