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

Merge pull request #59 from christocracy/geofencing

Geofencing
parents b8b92c56 41ee596a
...@@ -22,6 +22,10 @@ The plugin creates the object `window.plugins.backgroundGeoLocation` with the me ...@@ -22,6 +22,10 @@ The plugin creates the object `window.plugins.backgroundGeoLocation` with the me
`onStationary(callback, fail)` `onStationary(callback, fail)`
`addGeofence(callback, fail)`
`onGeofence(config, callback, fail)`
`getLocations(callback, fail)` `getLocations(callback, fail)`
`sync(callback, fail)` `sync(callback, fail)`
...@@ -49,27 +53,27 @@ The plugin creates the object `window.plugins.backgroundGeoLocation` with the me ...@@ -49,27 +53,27 @@ The plugin creates the object `window.plugins.backgroundGeoLocation` with the me
// As with all Cordova plugins, you must configure within an #deviceready callback. // As with all Cordova plugins, you must configure within an #deviceready callback.
// //
function onDeviceReady() { function onDeviceReady() {
/**
* This callback will be executed every time a geolocation is recorded in the background.
*/
var callbackFn = function(location, taskId) {
console.log('[js] BackgroundGeoLocation callback: ' + location.latitude + ',' + location.longitude);
/** /**
* This would be your own callback for Ajax-requests after POSTing background geolocation to your server. * This would be your own callback for Ajax-requests after POSTing background geolocation to your server.
* eg:
* $.post({url: url, success: yourAjaxCallback});
*/ */
var yourAjaxCallback = function(response) { var yourAjaxCallback = function(response) {
//// ////
// IMPORTANT: You must execute the #finish method here to inform the native plugin that you're finished, // IMPORTANT: You must execute the #finish, providing the taskId provided to callbackFn above in order to inform the native plugin that you're finished,
// and the background-task may be completed. You must do this regardless if your HTTP request is successful or not. // and the background-task may be completed. You must do this regardless if your HTTP request is successful or not.
// IF YOU DON'T, ios will CRASH YOUR APP for spending too much time in the background. // IF YOU DON'T, ios will CRASH YOUR APP for spending too much time in the background.
// //
// //
bgGeo.finish(); bgGeo.finish(taskId);
}; };
/**
* 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);
// Do your HTTP request here to POST location to your server.
//
//
yourAjaxCallback.call(this); yourAjaxCallback.call(this);
}; };
...@@ -191,11 +195,35 @@ Keep in mind that it is **not** possible to use ```start()``` at the ```pause``` ...@@ -191,11 +195,35 @@ Keep in mind that it is **not** possible to use ```start()``` at the ```pause```
## Methods ## Methods
#####`configure(locationCallback, failureCallback, config)` ####`configure(locationCallback, failureCallback, config)`
Configures the plugin's parameters (@see following [Config](https://github.com/christocracy/cordova-background-geolocation/blob/edge/README.md#config) section for accepted ```config``` params. The ```locationCallback``` will be executed each time a new Geolocation is recorded and provided with the following parameters:
Configures the plugin's parameters (@see following [Config](https://github.com/christocracy/cordova-background-geolocation/blob/edge/README.md#config) section for accepted ```config``` params. The ```locationCallback``` will be executed each time a new Geolocation is recorded. ######@param {Object} location The Location data
######@param {Integer} taskId The taskId used to send to bgGeo.finish(taskId) in order to signal completion of your callbackFn
```
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);
// 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
bgGeo.finish(taskId);
}, failureFn, {
distanceFilter: 50,
desiredAccuracy: 0,
stationaryRadius: 25
});
```
#####`setConfig(successFn, failureFn, config)` ####`setConfig(successFn, failureFn, config)`
Reconfigure plugin's configuration (@see followign ##Config## section for accepted ```config``` params. **NOTE** The plugin will continue to send recorded Geolocation to the ```locationCallback``` you provided to ```configure``` method -- use this method only to change configuration params (eg: ```distanceFilter```, ```stationaryRadius```, etc). Reconfigure plugin's configuration (@see followign ##Config## section for accepted ```config``` params. **NOTE** The plugin will continue to send recorded Geolocation to the ```locationCallback``` you provided to ```configure``` method -- use this method only to change configuration params (eg: ```distanceFilter```, ```stationaryRadius```, etc).
``` ```
...@@ -205,7 +233,7 @@ bgGeo.setConfig(function(){}, function(){}, { ...@@ -205,7 +233,7 @@ bgGeo.setConfig(function(){}, function(){}, {
}); });
``` ```
#####`start(successFn, failureFn)` ####`start(successFn, failureFn)`
Enable background geolocation tracking. Enable background geolocation tracking.
...@@ -213,7 +241,7 @@ Enable background geolocation tracking. ...@@ -213,7 +241,7 @@ Enable background geolocation tracking.
bgGeo.start() bgGeo.start()
``` ```
#####`stop(successFn, failureFn)` ####`stop(successFn, failureFn)`
Disable background geolocation tracking. Disable background geolocation tracking.
...@@ -221,7 +249,7 @@ Disable background geolocation tracking. ...@@ -221,7 +249,7 @@ Disable background geolocation tracking.
bgGeo.stop(); bgGeo.stop();
``` ```
#####`changePace(enabled, successFn, failureFn)` ####`changePace(enabled, successFn, failureFn)`
Initiate or cancel immediate background tracking. When set to ```true```, the plugin will begin aggressively tracking the devices Geolocation, bypassing stationary monitoring. If you were making a "Jogging" application, this would be your [Start Workout] button to immediately begin GPS tracking. Send ```false``` to disable aggressive GPS monitoring and return to stationary-monitoring mode. Initiate or cancel immediate background tracking. When set to ```true```, the plugin will begin aggressively tracking the devices Geolocation, bypassing stationary monitoring. If you were making a "Jogging" application, this would be your [Start Workout] button to immediately begin GPS tracking. Send ```false``` to disable aggressive GPS monitoring and return to stationary-monitoring mode.
``` ```
...@@ -229,16 +257,62 @@ bgGeo.changePace(true); // <-- Aggressive GPS monitoring immediately engaged. ...@@ -229,16 +257,62 @@ bgGeo.changePace(true); // <-- Aggressive GPS monitoring immediately engaged.
bgGeo.changePace(false); // <-- Disable aggressive GPS monitoring. Engages stationary-mode. bgGeo.changePace(false); // <-- Disable aggressive GPS monitoring. Engages stationary-mode.
``` ```
#####`onStationary(callbackFn, failureFn)` ####`onStationary(callbackFn, failureFn)`
Your ```callbackFn``` will be executed each time the device has entered stationary-monitoring mode. The ```callbackFn``` will be provided with a ```Geolocation``` object as the 1st param, with the usual params (```latitude, longitude, accuracy, speed, bearing, altitude```). 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
######@param {Integer} taskId The taskId used to send to bgGeo.finish(taskId) in order to signal completion of your callbackFn
``` ```
bgGeo.onStationary(function(location) { bgGeo.onStationary(function(location, taskId) {
console.log('- Device is stopped: ', location.latitude, location.longitude); console.log('- Device is stopped: ', location.latitude, location.longitude);
// 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
bgGeo.finish(taskId);
});
```
####`addGeofence(config, callbackFn, failureFn)`
Adds a geofence to be monitored by the native plugin. Monitoring of a geofence is halted after a crossing occurs. The `config` object accepts the following params.
######@config {String} identifier The name of your geofence, eg: "Home", "Office"
######@config {Float} radius The radius (meters) of the geofence. In practice, you should make this >= 100 meters.
######@config {Float} latitude Latitude of the center-point of the circular geofence.
######@config {Float} longitude Longitude of the center-point of the circular geofence.
```
bgGeo.addGeofence({
identifier: "Home",
radius: 150,
latitude: 45.51921926,
longitude: -73.61678581
}, function() {
console.log("Successfully added geofence");
}, function(error) {
console.warn("Failed to add geofence", error);
}); });
``` ```
#####`getLocations(callbackFn, failureFn)` ####`onGeofence(callbackFn)`
Adds a geofence event-listener. Your supplied callback will be called when any monitored geofence crossing occurs. The `callbackFn` will be provided the following parameters:
######@param {String} identifier The name of the geofence which generated the crossing event, eg: "Home"
######@param {Integer} taskId The background taskId which you must send back to the native plugin via `bgGeo.finish(taskId)` in order to signal that your callback is complete.
```
bgGeo.onGeofence(function(identifier, taskId) {
console.log('A geofence has been entered: ' + identifier);
// 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
bgGeo.finish(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: 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:
``` ```
...@@ -247,7 +321,7 @@ Fetch all the locations currently stored in native plugin's SQLite database. Yo ...@@ -247,7 +321,7 @@ Fetch all the locations currently stored in native plugin's SQLite database. Yo
}); });
``` ```
#####`sync(callbackFn, failureFn)` ####`sync(callbackFn, failureFn)`
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 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: 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 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:
...@@ -261,7 +335,7 @@ If the plugin is configured for HTTP with an ```#url``` and ```#autoSync: false` ...@@ -261,7 +335,7 @@ If the plugin is configured for HTTP with an ```#url``` and ```#autoSync: false`
``` ```
#####`getOdometer(callbackFn, failureFn)` ####`getOdometer(callbackFn, failureFn)`
The plugin constantly tracks distance travelled. To fetch the current **odometer** reading: The plugin constantly tracks distance travelled. To fetch the current **odometer** reading:
...@@ -271,7 +345,7 @@ The plugin constantly tracks distance travelled. To fetch the current **odomete ...@@ -271,7 +345,7 @@ The plugin constantly tracks distance travelled. To fetch the current **odomete
}); });
``` ```
#####`resetOdometer(callbackFn, failureFn)` ####`resetOdometer(callbackFn, failureFn)`
Reset the **odometer** to zero. The plugin never automatically resets the odometer so it's up to you to reset it as desired. Reset the **odometer** to zero. The plugin never automatically resets the odometer so it's up to you to reset it as desired.
...@@ -279,7 +353,7 @@ Reset the **odometer** to zero. The plugin never automatically resets the odome ...@@ -279,7 +353,7 @@ Reset the **odometer** to zero. The plugin never automatically resets the odome
Use the following config-parameters with the #configure method: Use the following config-parameters with the #configure method:
#####`@param {Boolean} debug` ####`@param {Boolean} debug`
When enabled, the plugin will emit sounds for life-cycle events of background-geolocation! **NOTE iOS**: In addition, you must manually enable the *Audio and Airplay* background mode in *Background Capabilities* to hear these debugging sounds. When enabled, the plugin will emit sounds for life-cycle events of background-geolocation! **NOTE iOS**: In addition, you must manually enable the *Audio and Airplay* background mode in *Background Capabilities* to hear these debugging sounds.
...@@ -291,18 +365,18 @@ When enabled, the plugin will emit sounds for life-cycle events of background-ge ...@@ -291,18 +365,18 @@ When enabled, the plugin will emit sounds for life-cycle events of background-ge
![Enable Background Audio](/enable-background-audio.png "Enable Background Audio") ![Enable Background Audio](/enable-background-audio.png "Enable Background Audio")
#####`@param {Integer} desiredAccuracy [0, 10, 100, 1000] in meters` ####`@param {Integer} desiredAccuracy [0, 10, 100, 1000] in meters`
Specify the desired-accuracy of the geolocation system with 1 of 4 values, ```0, 10, 100, 1000``` where ```0``` means HIGHEST POWER, HIGHEST ACCURACY and ```1000``` means LOWEST POWER, LOWEST ACCURACY Specify the desired-accuracy of the geolocation system with 1 of 4 values, ```0, 10, 100, 1000``` where ```0``` means HIGHEST POWER, HIGHEST ACCURACY and ```1000``` means LOWEST POWER, LOWEST ACCURACY
- [Android](https://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#PRIORITY_BALANCED_POWER_ACCURACY) - [Android](https://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html#PRIORITY_BALANCED_POWER_ACCURACY)
- [iOS](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/instp/CLLocationManager/desiredAccuracy) - [iOS](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/instp/CLLocationManager/desiredAccuracy)
#####`@param {Integer} stationaryRadius (meters)` ####`@param {Integer} stationaryRadius (meters)`
When stopped, the minimum distance the device must move beyond the stationary location for aggressive background-tracking to engage. Note, since the plugin uses iOS significant-changes API, the plugin cannot detect the exact moment the device moves out of the stationary-radius. In normal conditions, it can take as much as 3 city-blocks to 1/2 km before staionary-region exit is detected. When stopped, the minimum distance the device must move beyond the stationary location for aggressive background-tracking to engage. Note, since the plugin uses iOS significant-changes API, the plugin cannot detect the exact moment the device moves out of the stationary-radius. In normal conditions, it can take as much as 3 city-blocks to 1/2 km before staionary-region exit is detected.
#####`@param {Integer} distanceFilter` ####`@param {Integer} distanceFilter`
The minimum distance (measured in meters) a device must move horizontally before an update event is generated. @see [Apple docs](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html#//apple_ref/occ/instp/CLLocationManager/distanceFilter). However, #distanceFilter is elastically auto-calculated by the plugin: When speed increases, #distanceFilter increases; when speed decreases, so does distanceFilter. The minimum distance (measured in meters) a device must move horizontally before an update event is generated. @see [Apple docs](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html#//apple_ref/occ/instp/CLLocationManager/distanceFilter). However, #distanceFilter is elastically auto-calculated by the plugin: When speed increases, #distanceFilter increases; when speed decreases, so does distanceFilter.
...@@ -336,14 +410,14 @@ Compare now background-geolocation in the scope of a city. In this image, the l ...@@ -336,14 +410,14 @@ Compare now background-geolocation in the scope of a city. In this image, the l
![distanceFilter at city scale](/distance-filter-city.png "distanceFilter at city scale") ![distanceFilter at city scale](/distance-filter-city.png "distanceFilter at city scale")
#####`@param {Boolean} stopOnTerminate` ####`@param {Boolean} stopOnTerminate`
Enable this in order to force a stop() when the application terminated (e.g. on iOS, double-tap home button, swipe away the app). On Android, ```stopOnTerminate: false``` will cause the plugin to operate as a headless background-service (in this case, you should configure an #url in order for the background-service to send the location to your server) Enable this in order to force a stop() when the application terminated (e.g. on iOS, double-tap home button, swipe away the app). On Android, ```stopOnTerminate: false``` will cause the plugin to operate as a headless background-service (in this case, you should configure an #url in order for the background-service to send the location to your server)
#####`@param {Boolean} stopAfterElapsedMinutes` ####`@param {Boolean} stopAfterElapsedMinutes`
The plugin can optionally auto-stop monitoring location when some number of minutes elapse after being the #start method was called. The plugin can optionally auto-stop monitoring location when some number of minutes elapse after being the #start method was called.
#### In-Plugin SQLite Storage ### In-Plugin SQLite Storage
The plugin will cache **every** recorded geolocation to its internal SQLite database -- when you sync the locations and your server responds with HTTP ```200, 201 or 204```, the plugin will **DELETE** the stored location from cache. The plugin has a cache-pruning feature with ```@config {Integer} maxDaysToPersist``` -- If the plugin hasn't successfully synced these these records in the database before ```maxDaysToPersist``` expires, the plugin will give up and those geolocation records will be pruned from the database. The plugin will cache **every** recorded geolocation to its internal SQLite database -- when you sync the locations and your server responds with HTTP ```200, 201 or 204```, the plugin will **DELETE** the stored location from cache. The plugin has a cache-pruning feature with ```@config {Integer} maxDaysToPersist``` -- If the plugin hasn't successfully synced these these records in the database before ```maxDaysToPersist``` expires, the plugin will give up and those geolocation records will be pruned from the database.
...@@ -356,34 +430,36 @@ If you **don't** configure the optional HTTP feature, the only way to delete the ...@@ -356,34 +430,36 @@ If you **don't** configure the optional HTTP feature, the only way to delete the
}); });
``` ```
#### HTTP Features ### HTTP Features
#####`@param {String} url` ####`@param {String} url`
Your server url where you wish to HTTP POST location data to. Your server url where you wish to HTTP POST location data to.
#####`@param {String} batchSync [false]` ####`@param {String} batchSync [false]`
Default is ```false```. If you've enabled HTTP feature by configuring an ```#url```, ```batchSync: true``` will POST all the locations currently stored in native SQLite datbase to your server in a single HTTP POST request. With ```batchSync: false```, an HTTP POST request will be initiated for **each** location in database. Default is ```false```. If you've enabled HTTP feature by configuring an ```#url```, ```batchSync: true``` will POST all the locations currently stored in native SQLite datbase to your server in a single HTTP POST request. With ```batchSync: false```, an HTTP POST request will be initiated for **each** location in database.
#####`@param {String} autoSync [true]` ####`@param {String} autoSync [true]`
Default is ```true```. If you've enabeld HTTP feature by configuring an ```#url```, the plugin will attempt to HTTP POST each location to your server **as it is recorded**. If you set ```autoSync: false```, it's up to you to **manually** execute the ```#sync``` method to initate the HTTP POST (**NOTE** The plugin will continue to persist **every** recorded location in the SQLite database until you execute ```#sync```). Default is ```true```. If you've enabeld HTTP feature by configuring an ```#url```, the plugin will attempt to HTTP POST each location to your server **as it is recorded**. If you set ```autoSync: false```, it's up to you to **manually** execute the ```#sync``` method to initate the HTTP POST (**NOTE** The plugin will continue to persist **every** recorded location in the SQLite database until you execute ```#sync```).
#####`@param {Object} params` ####`@param {Object} params`
Optional HTTP params sent along in HTTP request to above ```#url```. Optional HTTP params sent along in HTTP request to above ```#url```.
#####`@param {Object} headers` ####`@param {Object} headers`
Optional HTTP params sent along in HTTP request to above ```#url```. Optional HTTP params sent along in HTTP request to above ```#url```.
#####`@param {Integer} maxDaysToPersist` ####`@param {Integer} maxDaysToPersist`
Maximum number of days to store a geolocation in plugin's SQLite database when your server fails to respond with ```HTTP 200 OK```. The plugin will continue attempting to sync with your server until ```maxDaysToPersist``` when it will give up and remove the location from the database. Maximum number of days to store a geolocation in plugin's SQLite database when your server fails to respond with ```HTTP 200 OK```. The plugin will continue attempting to sync with your server until ```maxDaysToPersist``` when it will give up and remove the location from the database.
Both iOS and Android can send the Geolocation to your server simply by configuring an ```#url``` in addition to optional ```#headers``` and ```#params```. This is the preferred way to send the Geolocation to your server, rather than doing it yourself with Ajax in your javascript. Both iOS and Android can send the Geolocation to your server simply by configuring an ```#url``` in addition to optional ```#headers``` and ```#params```. This is the preferred way to send the Geolocation to your server, rather than doing it yourself with Ajax in your javascript.
#### Sample HTTP Request arriving at your server
``` ```
bgGeo.configure(callbackFn, failureFn, { bgGeo.configure(callbackFn, failureFn, {
. .
...@@ -447,7 +523,7 @@ No Post Params. ...@@ -447,7 +523,7 @@ No Post Params.
### Android Config ### Android Config
#####`@param {Integer millis} locationUpdateInterval` ####`@param {Integer millis} locationUpdateInterval`
Set the desired interval for active location updates, in milliseconds. Set the desired interval for active location updates, in milliseconds.
...@@ -457,7 +533,7 @@ This interval is inexact. You may not receive updates at all (if no location sou ...@@ -457,7 +533,7 @@ This interval is inexact. You may not receive updates at all (if no location sou
Applications with only the coarse location permission may have their interval silently throttled. Applications with only the coarse location permission may have their interval silently throttled.
#####`@param {Integer millis} fastestLocationUpdateInterval` ####`@param {Integer millis} fastestLocationUpdateInterval`
Explicitly set the fastest interval for location updates, in milliseconds. Explicitly set the fastest interval for location updates, in milliseconds.
...@@ -476,19 +552,19 @@ If ```#fastestLocationUpdateInterval``` is set slower than ```#locationUpdateInt ...@@ -476,19 +552,19 @@ If ```#fastestLocationUpdateInterval``` is set slower than ```#locationUpdateInt
======== ========
An interval of 0 is allowed, but not recommended, since location updates may be extremely fast on future implementations. An interval of 0 is allowed, but not recommended, since location updates may be extremely fast on future implementations.
#####`@param {Integer millis} activityRecognitionInterval` ####`@param {Integer millis} activityRecognitionInterval`
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. 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 minutes} stopTimeout` ####`@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. 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} forceReload`
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) by setting ```foreceReload: true```. This will guarantee that locations are always sent to your Javascript callback (**WARNING** possibly disruptive to user).
#####`@param {Boolean} startOnBoot` ####`@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. 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.
...@@ -496,19 +572,19 @@ Set to ```true``` to start the background-service whenever the device boots. Un ...@@ -496,19 +572,19 @@ Set to ```true``` to start the background-service whenever the device boots. Un
### iOS Config ### iOS Config
#####`@param {Boolean} disableElasticity [false]` ####`@param {Boolean} disableElasticity [false]`
Defaults to ```false```. Set ```true``` to disable automatic speed-based ```#distanceFilter``` elasticity. eg: When device is moving at highway speeds, locations are returned at ~ 1 / km. Defaults to ```false```. Set ```true``` to disable automatic speed-based ```#distanceFilter``` elasticity. eg: When device is moving at highway speeds, locations are returned at ~ 1 / km.
#####`@param {String} activityType [AutomotiveNavigation, OtherNavigation, Fitness, Other]` ####`@param {String} activityType [AutomotiveNavigation, OtherNavigation, Fitness, Other]`
Presumably, this affects ios GPS algorithm. See [Apple docs](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html#//apple_ref/occ/instp/CLLocationManager/activityType) for more information Presumably, this affects ios GPS algorithm. See [Apple docs](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html#//apple_ref/occ/instp/CLLocationManager/activityType) for more information
### WP8 Config ### WP8 Config
#####`{Integer [0, 10, 100, 1000]} desiredAccuracy` ####`{Integer [0, 10, 100, 1000]} desiredAccuracy`
###### Windows Phone ##### Windows Phone
The underlying GeoLocator you can choose to use 'DesiredAccuracy' or 'DesiredAccuracyInMeters'. Since this plugins default configuration accepts meters, the default desiredAccuracy is mapped to the Windows Phone DesiredAccuracyInMeters leaving the DesiredAccuracy enum empty. For more info see the [MS docs](http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.geolocation.geolocator.desiredaccuracyinmeters) for more information. The underlying GeoLocator you can choose to use 'DesiredAccuracy' or 'DesiredAccuracyInMeters'. Since this plugins default configuration accepts meters, the default desiredAccuracy is mapped to the Windows Phone DesiredAccuracyInMeters leaving the DesiredAccuracy enum empty. For more info see the [MS docs](http://msdn.microsoft.com/en-us/library/windows/apps/windows.devices.geolocation.geolocator.desiredaccuracyinmeters) for more information.
## Licence ## ## Licence ##
......
...@@ -138,26 +138,28 @@ var app = { ...@@ -138,26 +138,28 @@ var app = {
app.onClickHome(); 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. * 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)); console.log('[js] BackgroundGeoLocation callback: ' + JSON.stringify(location));
// Update our current-position marker. // Update our current-position marker.
app.setCurrentLocation(location); 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. // 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) { var failureFn = function(error) {
...@@ -165,7 +167,7 @@ var app = { ...@@ -165,7 +167,7 @@ var app = {
}; };
// Only ios emits this stationary event // Only ios emits this stationary event
bgGeo.onStationary(function(location) { bgGeo.onStationary(function(location, taskId) {
console.log('[js] BackgroundGeoLocation onStationary ' + JSON.stringify(location)); console.log('[js] BackgroundGeoLocation onStationary ' + JSON.stringify(location));
app.setCurrentLocation(location); app.setCurrentLocation(location);
...@@ -188,6 +190,8 @@ var app = { ...@@ -188,6 +190,8 @@ var app = {
app.stationaryRadius.setRadius(radius); app.stationaryRadius.setRadius(radius);
app.stationaryRadius.setCenter(center); app.stationaryRadius.setCenter(center);
bgGeo.finish(taskId);
}); });
// BackgroundGeoLocation is highly configurable. // BackgroundGeoLocation is highly configurable.
...@@ -195,22 +199,22 @@ var app = { ...@@ -195,22 +199,22 @@ var app = {
debug: true, // <-- enable this hear sounds for background-geolocation life-cycle. debug: true, // <-- enable this hear sounds for background-geolocation life-cycle.
desiredAccuracy: 0, desiredAccuracy: 0,
stationaryRadius: 50, stationaryRadius: 50,
distanceFilter: 25, distanceFilter: 50,
disableElasticity: false, // <-- [iOS] Default is 'false'. Set true to disable speed-based distanceFilter elasticity disableElasticity: false, // <-- [iOS] Default is 'false'. Set true to disable speed-based distanceFilter elasticity
locationUpdateInterval: 5000, locationUpdateInterval: 5000,
minimumActivityRecognitionConfidence: 80, // 0-100%. Minimum activity-confidence for a state-change minimumActivityRecognitionConfidence: 80, // 0-100%. Minimum activity-confidence for a state-change
fastestLocationUpdateInterval: 5000, fastestLocationUpdateInterval: 5000,
activityRecognitionInterval: 10000, activityRecognitionInterval: 10000,
stopTimeout: 0, stopTimeout: 0,
forceReload: true, // <-- [Android] If the user closes the app **while location-tracking is started** , reboot app (WARNING: possibly distruptive to user) forceReload: false, // <-- [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. stopOnTerminate: true, // <-- [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. startOnBoot: false, // <-- [Android] Auto start background-service in headless mode when device is powered-up.
activityType: 'AutomotiveNavigation', activityType: 'AutomotiveNavigation',
/** /**
* HTTP Feature: set an url to allow the native background service to POST locations to your server * 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', 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. 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 maxDaysToPersist: 1, // <-- Maximum days to persist a location in plugin's SQLite database when HTTP fails
headers: { headers: {
...@@ -221,9 +225,10 @@ var app = { ...@@ -221,9 +225,10 @@ var app = {
} }
}); });
bgGeo.onGeofence(function(identifier) { bgGeo.onGeofence(function(identifier, taskId) {
alert('Enter Geofence: ' + identifier); 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. // Add longpress event for adding GeoFence of hard-coded radius 200m.
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
<service android:name="com.transistorsoft.locationmanager.BackgroundGeolocationService" /> <service android:name="com.transistorsoft.locationmanager.BackgroundGeolocationService" />
<service android:name="com.transistorsoft.locationmanager.LocationService" /> <service android:name="com.transistorsoft.locationmanager.LocationService" />
<service android:name="com.transistorsoft.locationmanager.ActivityRecognitionService" /> <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"> <receiver android:name="com.transistorsoft.locationmanager.BootReceiver" android:enabled="true" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
......
package com.transistorsoft.cordova.bggeo; package com.transistorsoft.cordova.bggeo;
import java.util.List;
import java.util.ArrayList;
import org.apache.cordova.CallbackContext; import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView; import org.apache.cordova.CordovaWebView;
...@@ -11,8 +13,8 @@ import android.os.Bundle; ...@@ -11,8 +13,8 @@ import android.os.Bundle;
import com.google.android.gms.location.ActivityRecognitionResult; import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity; import com.google.android.gms.location.DetectedActivity;
import com.transistorsoft.locationmanager.BackgroundGeolocationService; import com.transistorsoft.locationmanager.BackgroundGeolocationService;
import com.transistorsoft.locationmanager.BackgroundGeolocationService.PaceChangeEvent; import com.google.android.gms.location.Geofence;
import com.transistorsoft.locationmanager.BackgroundGeolocationService.PausedEvent; import com.google.android.gms.location.GeofencingEvent;
//import com.transistorsoft.locationmanager.BackgroundGeolocationService.StationaryLocation; //import com.transistorsoft.locationmanager.BackgroundGeolocationService.StationaryLocation;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
...@@ -37,6 +39,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -37,6 +39,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
public static final String ACTION_SYNC = "sync"; public static final String ACTION_SYNC = "sync";
public static final String ACTION_GET_ODOMETER = "getOdometer"; public static final String ACTION_GET_ODOMETER = "getOdometer";
public static final String ACTION_RESET_ODOMETER = "resetOdometer"; 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 isEnabled = false;
private Boolean stopOnTerminate = false; private Boolean stopOnTerminate = false;
...@@ -48,16 +52,13 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -48,16 +52,13 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
// Geolocation callback // Geolocation callback
private CallbackContext locationCallback; private CallbackContext locationCallback;
// Called when DetectedActivity is STILL
private CallbackContext stationaryCallback; private CallbackContext stationaryCallback;
private CallbackContext getLocationsCallback; private CallbackContext getLocationsCallback;
private CallbackContext syncCallback; private CallbackContext syncCallback;
private CallbackContext getOdometerCallback; private CallbackContext getOdometerCallback;
private CallbackContext resetOdometerCallback; private CallbackContext resetOdometerCallback;
private CallbackContext paceChangeCallback;
private List<CallbackContext> geofenceCallbacks = new ArrayList<CallbackContext>();
public static boolean isActive() { public static boolean isActive() {
return gWebView != null; return gWebView != null;
...@@ -70,7 +71,6 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -70,7 +71,6 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
backgroundServiceIntent = new Intent(this.cordova.getActivity(), BackgroundGeolocationService.class); backgroundServiceIntent = new Intent(this.cordova.getActivity(), BackgroundGeolocationService.class);
// Register for events fired by our IntentService "LocationService" // Register for events fired by our IntentService "LocationService"
EventBus.getDefault().register(this);
} }
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException { public boolean execute(String action, JSONArray data, CallbackContext callbackContext) throws JSONException {
...@@ -99,16 +99,21 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -99,16 +99,21 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
result = false; result = false;
callbackContext.error("Cannot #changePace while disabled"); callbackContext.error("Cannot #changePace while disabled");
} else { } else {
PaceChangeEvent event = new PaceChangeEvent(data.getBoolean(0));
EventBus.getDefault().post(event);
result = true; 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)) { } else if (ACTION_SET_CONFIG.equalsIgnoreCase(action)) {
result = applyConfig(data); result = applyConfig(data);
// TODO reconfigure Service
if (result) { if (result) {
Bundle event = new Bundle();
event.putString("name", action);
event.putBoolean("request", true);
EventBus.getDefault().post(event);
callbackContext.success(); callbackContext.success();
} else { } else {
callbackContext.error("- Configuration error!"); callbackContext.error("- Configuration error!");
...@@ -144,6 +149,29 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -144,6 +149,29 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
event.putBoolean("request", true); event.putBoolean("request", true);
resetOdometerCallback = callbackContext; resetOdometerCallback = callbackContext;
EventBus.getDefault().post(event); 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; return result;
} }
...@@ -159,10 +187,12 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -159,10 +187,12 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
editor.commit(); editor.commit();
if (isEnabled) { if (isEnabled) {
EventBus.getDefault().register(this);
if (!BackgroundGeolocationService.isInstanceCreated()) { if (!BackgroundGeolocationService.isInstanceCreated()) {
activity.startService(backgroundServiceIntent); activity.startService(backgroundServiceIntent);
} }
} else { } else {
EventBus.getDefault().unregister(this);
activity.stopService(backgroundServiceIntent); activity.stopService(backgroundServiceIntent);
} }
} }
...@@ -189,6 +219,9 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -189,6 +219,9 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
if (config.has("locationUpdateInterval")) { if (config.has("locationUpdateInterval")) {
editor.putInt("locationUpdateInterval", config.getInt("locationUpdateInterval")); editor.putInt("locationUpdateInterval", config.getInt("locationUpdateInterval"));
} }
if (config.has("fastestLocationUpdateInterval")) {
editor.putInt("fastestLocationUpdateInterval", config.getInt("fastestLocationUpdateInterval"));
}
if (config.has("activityRecognitionInterval")) { if (config.has("activityRecognitionInterval")) {
editor.putLong("activityRecognitionInterval", config.getLong("activityRecognitionInterval")); editor.putLong("activityRecognitionInterval", config.getLong("activityRecognitionInterval"));
} }
...@@ -205,7 +238,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -205,7 +238,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
editor.putInt("stopAfterElapsedMinutes", config.getInt("stopAfterElapsedMinutes")); editor.putInt("stopAfterElapsedMinutes", config.getInt("stopAfterElapsedMinutes"));
} }
if (config.has("stopOnTerminate")) { if (config.has("stopOnTerminate")) {
editor.putBoolean("stopOnTerminate", config.getBoolean("stopOnTerminate")); stopOnTerminate = config.getBoolean("stopOnTerminate");
editor.putBoolean("stopOnTerminate", stopOnTerminate);
} }
if (config.has("startOnBoot")) { if (config.has("startOnBoot")) {
editor.putBoolean("startOnBoot", config.getBoolean("startOnBoot")); editor.putBoolean("startOnBoot", config.getBoolean("startOnBoot"));
...@@ -296,7 +330,10 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -296,7 +330,10 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
runInBackground(getOdometerCallback, result); runInBackground(getOdometerCallback, result);
} else if (ACTION_RESET_ODOMETER.equalsIgnoreCase(name)) { } else if (ACTION_RESET_ODOMETER.equalsIgnoreCase(name)) {
PluginResult result = new PluginResult(PluginResult.Status.OK); 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,6 +366,29 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -329,6 +366,29 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
runInBackground(locationCallback, result); 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 * Run a javascript callback in Background
...@@ -356,6 +416,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin { ...@@ -356,6 +416,8 @@ public class CDVBackgroundGeolocation extends CordovaPlugin {
Activity activity = this.cordova.getActivity(); Activity activity = this.cordova.getActivity();
EventBus.getDefault().unregister(this);
SharedPreferences settings = activity.getSharedPreferences("TSLocationManager", 0); SharedPreferences settings = activity.getSharedPreferences("TSLocationManager", 0);
SharedPreferences.Editor editor = settings.edit(); SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("activityIsActive", false); editor.putBoolean("activityIsActive", false);
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
@interface CDVBackgroundGeolocation : CDVPlugin @interface CDVBackgroundGeolocation : CDVPlugin
@property (nonatomic, strong) NSString* syncCallbackId; @property (nonatomic, strong) NSString* syncCallbackId;
@property (nonatomic, strong) NSString* geofenceCallbackId; @property (nonatomic, strong) NSMutableArray* geofenceListeners;
@property (nonatomic, strong) NSMutableArray* stationaryRegionListeners; @property (nonatomic, strong) NSMutableArray* stationaryRegionListeners;
- (void) configure:(CDVInvokedUrlCommand*)command; - (void) configure:(CDVInvokedUrlCommand*)command;
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
NSDictionary *config; NSDictionary *config;
} }
@synthesize syncCallbackId, geofenceCallbackId, stationaryRegionListeners; @synthesize syncCallbackId, geofenceListeners, stationaryRegionListeners;
- (void)pluginInitialize - (void)pluginInitialize
{ {
...@@ -87,44 +87,61 @@ ...@@ -87,44 +87,61 @@
* location handler from BackgroundGeolocation * location handler from BackgroundGeolocation
*/ */
- (void)onLocationChanged:(NSNotification*)notification { - (void)onLocationChanged:(NSNotification*)notification {
CLLocation *location = notification.object; CLLocation *location = [notification.userInfo objectForKey:@"location"];
NSDictionary *locationData = [bgGeo locationToDictionary:location];
CDVPluginResult* result = nil; NSDictionary *params = @{
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:locationData]; @"location": [bgGeo locationToDictionary:location],
@"taskId": @([bgGeo createBackgroundTask])
};
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:params];
[result setKeepCallbackAsBool:YES]; [result setKeepCallbackAsBool:YES];
[self.commandDelegate runInBackground:^{
[self.commandDelegate sendPluginResult:result callbackId:self.syncCallbackId]; [self.commandDelegate sendPluginResult:result callbackId:self.syncCallbackId];
}];
} }
- (void) onStationaryLocation:(NSNotification*)notification - (void) onStationaryLocation:(NSNotification*)notification
{ {
CLLocation *location = notification.object;
NSDictionary *locationData = [bgGeo locationToDictionary:location];
if (![self.stationaryRegionListeners count]) { if (![self.stationaryRegionListeners count]) {
[bgGeo stopBackgroundTask];
return; return;
} }
CLLocation *location = [notification.userInfo objectForKey:@"location"];
NSDictionary *locationData = [bgGeo locationToDictionary:location];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:locationData]; for (NSString *callbackId in self.stationaryRegionListeners) {
NSDictionary *params = @{
@"location": locationData,
@"taskId": @([bgGeo createBackgroundTask])
};
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:params];
[result setKeepCallbackAsBool:YES]; [result setKeepCallbackAsBool:YES];
for (NSString *callbackId in self.stationaryRegionListeners) [self.commandDelegate runInBackground:^{
{
[self.commandDelegate sendPluginResult:result callbackId:callbackId]; [self.commandDelegate sendPluginResult:result callbackId:callbackId];
}];
} }
[bgGeo stopBackgroundTask];
} }
- (void) onEnterGeofence:(NSNotification*)notification - (void) onEnterGeofence:(NSNotification*)notification
{ {
if (self.geofenceCallbackId == nil) { if (![self.geofenceListeners count]) {
return; return;
} }
CLCircularRegion *region = notification.object; CLCircularRegion *region = [notification.userInfo objectForKey:@"geofence"];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:region.identifier]; 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]; [result setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:result callbackId:self.geofenceCallbackId]; [self.commandDelegate runInBackground:^{
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
}];
}
} }
/** /**
* Fetches current stationaryLocation * Fetches current stationaryLocation
...@@ -187,14 +204,18 @@ ...@@ -187,14 +204,18 @@
- (void) onGeofence:(CDVInvokedUrlCommand*)command - (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 * Called by js to signify the end of a background-geolocation event
*/ */
-(void) finish:(CDVInvokedUrlCommand*)command -(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 * If you don't stopMonitoring when application terminates, the app will be awoken still when a
......
...@@ -9,10 +9,10 @@ ...@@ -9,10 +9,10 @@
- (void) configure:(NSDictionary*)config; - (void) configure:(NSDictionary*)config;
- (void) start; - (void) start;
- (void) stop; - (void) stop;
- (void) finish;
- (NSArray*) sync; - (NSArray*) sync;
- (NSArray*) getLocations; - (NSArray*) getLocations;
- (void) stopBackgroundTask; - (UIBackgroundTaskIdentifier) createBackgroundTask;
- (void) stopBackgroundTask:(UIBackgroundTaskIdentifier)taskId;
- (void) onPaceChange:(BOOL)value; - (void) onPaceChange:(BOOL)value;
- (void) setConfig:(NSDictionary*)command; - (void) setConfig:(NSDictionary*)command;
- (NSDictionary*) getStationaryLocation; - (NSDictionary*) getStationaryLocation;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<dict> <dict>
<key>Headers/TSLocationManager.h</key> <key>Headers/TSLocationManager.h</key>
<data> <data>
93eNJDqyN5vuBJn9oYeT7lb2x1w= w7pffjKj41qqtvUN0J6fOruESPM=
</data> </data>
<key>Info.plist</key> <key>Info.plist</key>
<data> <data>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<dict> <dict>
<key>Headers/TSLocationManager.h</key> <key>Headers/TSLocationManager.h</key>
<data> <data>
93eNJDqyN5vuBJn9oYeT7lb2x1w= w7pffjKj41qqtvUN0J6fOruESPM=
</data> </data>
<key>Modules/module.modulemap</key> <key>Modules/module.modulemap</key>
<data> <data>
......
...@@ -9,24 +9,30 @@ ...@@ -9,24 +9,30 @@
var exec = require("cordova/exec"); var exec = require("cordova/exec");
module.exports = { module.exports = {
/** /**
* @property {Object} stationaryRegion * @property {Object} stationaryLocation
*/ */
stationaryRegion: null, stationaryLocation: null,
/** /**
* @property {Object} config * @property {Object} config
*/ */
config: {}, config: {},
configure: function(success, failure, config) { configure: function(success, failure, config) {
var me = this;
config = config || {}; config = config || {};
this.config = config; this.config = config;
success = success || function(location) {}; success = success || function(location, taskId) {
var mySuccess = function(location) { me.finish(taskId);
};
var mySuccess = function(params) {
var location = params.location || params;
var taskId = params.taskId || 'task-id-undefined';
// Transform timestamp to Date instance. // Transform timestamp to Date instance.
if (location.timestamp) { if (location.timestamp) {
location.timestamp = new Date(location.timestamp); location.timestamp = new Date(location.timestamp);
} }
success.call(this, location); success.call(this, location, taskId);
} }
exec(mySuccess, exec(mySuccess,
failure || function() {}, failure || function() {},
...@@ -49,12 +55,15 @@ module.exports = { ...@@ -49,12 +55,15 @@ module.exports = {
'stop', '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() {}, exec(success || function() {},
failure || function() {}, failure || function() {},
'BackgroundGeoLocation', 'BackgroundGeoLocation',
'finish', 'finish',
[]); [taskId]);
}, },
changePace: function(isMoving, success, failure) { changePace: function(isMoving, success, failure) {
exec(success || function() {}, exec(success || function() {},
...@@ -94,10 +103,15 @@ module.exports = { ...@@ -94,10 +103,15 @@ module.exports = {
*/ */
onStationary: function(success, failure) { onStationary: function(success, failure) {
var me = this; var me = this;
success = success || function() {}; success = success || function(location, taskId) {
var callback = function(region) { me.finish(taskId);
me.stationaryRegion = region; };
success.apply(me, arguments); 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, exec(callback,
failure || function() {}, failure || function() {},
...@@ -180,7 +194,13 @@ module.exports = { ...@@ -180,7 +194,13 @@ module.exports = {
if (!typeof(success) === 'function') { if (!typeof(success) === 'function') {
throw "#onGeofence requires a success callback"; 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() {}, failure || function() {},
'BackgroundGeoLocation', 'BackgroundGeoLocation',
'onGeofence', '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