Commit 51857664 authored by Chris Scott's avatar Chris Scott

Refactor ios to be more intelligent with geolocation, turning on more...

Refactor ios to be more intelligent with geolocation, turning on more aggressive standard location services wehn user is detected to be moving beyond a configurable radius
parent d4d7571f
......@@ -12,17 +12,11 @@
- (void) start:(CDVInvokedUrlCommand*)command;
- (void) stop:(CDVInvokedUrlCommand*)command;
- (void) test:(CDVInvokedUrlCommand*)command;
- (void) finish:(CDVInvokedUrlCommand*)command;
- (void) sync;
- (void) onSuspend:(NSNotification *)notification;
- (void) onResume:(NSNotification *)notification;
@property(nonatomic,retain) NSString *token;
@property(nonatomic,retain) NSString *url;
@property(nonatomic,assign) BOOL enabled;
@property(nonatomic,retain) NSNumber *maxBackgroundHours;
@property (nonatomic, strong) CLLocationManager* locationManager;
@property (nonatomic, strong) CDVLocationData* locationData;
@property (strong) NSMutableArray *locationCache;
@property (nonatomic, retain) NSDate *suspendedAt;
@end
......@@ -8,57 +8,81 @@
#import "CDVBackgroundGeoLocation.h"
#import <Cordova/CDVJSON.h>
@implementation CDVBackgroundGeoLocation
@synthesize enabled, token, url, locationManager, locationData, locationCache, suspendedAt;
@implementation CDVBackgroundGeoLocation {
BOOL enabled;
NSString *token;
NSString *url;
NSString *syncCallbackId;
UIBackgroundTaskIdentifier bgTask;
NSNumber *maxBackgroundHours;
CLLocationManager *locationManager;
CDVLocationData *locationData;
NSMutableArray *locationCache;
NSDate *suspendedAt;
CLCircularRegion *myRegion;
NSInteger stationaryRadius;
NSInteger distanceFilter;
NSInteger locationTimeout;
}
- (CDVPlugin*) initWithWebView:(UIWebView*) theWebView
{
// background location cache, for when no network is detected.
self.locationCache = [NSMutableArray array];
locationCache = [NSMutableArray array];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onSuspend:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSuspend:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onResume:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume:) name:UIApplicationWillEnterForegroundNotification object:nil];
return self;
}
- (void) configure:(CDVInvokedUrlCommand*)command
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.token = [command.arguments objectAtIndex: 0];
self.url = [command.arguments objectAtIndex: 1];
// in iOS, we call to javascript for HTTP now so token and url should be @deprecated until Android calls out to javascript.
token = [command.arguments objectAtIndex: 0];
url = [command.arguments objectAtIndex: 1];
// Location filtering.
stationaryRadius = [[command.arguments objectAtIndex: 2] intValue];
distanceFilter = [[command.arguments objectAtIndex: 3] intValue];
locationTimeout = [[command.arguments objectAtIndex: 4] intValue];
syncCallbackId = command.callbackId;
// Set a movement threshold for new events.
locationManager.activityType = CLActivityTypeOther;
locationManager.pausesLocationUpdatesAutomatically = YES;
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
locationManager.distanceFilter = distanceFilter; // meters
NSLog(@"CDVBackgroundGeoLocation configure");
NSLog(@" -token: %@", self.token);
NSLog(@" -url: %@", self.url);
NSLog(@" - token: %@", token);
NSLog(@" - url: %@", url);
NSLog(@" - distanceFilter: %ld", (long)distanceFilter);
NSLog(@" - stationaryRadius: %ld", (long)stationaryRadius);
NSLog(@" - locationTimeout: %ld", (long)locationTimeout);
}
- (void) start:(CDVInvokedUrlCommand*)command
{
NSLog(@"CDVBackgroundGeoLocation start");
self.enabled = YES;
enabled = YES;
}
- (void) stop:(CDVInvokedUrlCommand*)command
{
NSLog(@"CDVBackgroundGeoLocation stop");
self.enabled = NO;
[self.locationManager stopMonitoringSignificantLocationChanges];
enabled = NO;
[locationManager stopUpdatingLocation];
[locationManager stopMonitoringSignificantLocationChanges];
}
- (void) test:(CDVInvokedUrlCommand*)command
{
NSLog(@"CDVBackgroundGeoLocation test");
[self.locationManager startMonitoringSignificantLocationChanges];
if ([self.locationCache count] > 0){
[locationManager startMonitoringSignificantLocationChanges];
if ([locationCache count] > 0){
[self sync];
} else {
NSLog(@"CDVBackgroundGeoLocation could not find a location to sync");
......@@ -67,20 +91,25 @@
-(void) onSuspend:(NSNotification *) notification
{
NSLog(@"CDVBackgroundGeoLocation suspend");
self.suspendedAt = [NSDate date];
suspendedAt = [NSDate date];
if (self.enabled) {
[self.locationManager startMonitoringSignificantLocationChanges];
if (enabled) {
if (myRegion == nil) {
myRegion = [self createStationaryRegion];
[locationManager startMonitoringForRegion:myRegion];
}
[locationManager startMonitoringSignificantLocationChanges];
}
}
-(void) onResume:(NSNotification *) notification
{
NSLog(@"CDVBackgroundGeoLocation resume");
if (self.enabled) {
[self.locationManager stopMonitoringSignificantLocationChanges];
if (enabled) {
[locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopUpdatingLocation];
}
// When coming back-to-life, flush the background queue.
if ([self.locationCache count] > 0){
if ([locationCache count] > 0){
[self sync];
}
}
......@@ -93,10 +122,11 @@
// The omitted code should determine whether to reject the location update for being too
// old, too close to the previous one, too inaccurate and so forth according to your own
// application design.
[self.locationCache addObjectsFromArray:locations];
[locationCache addObjectsFromArray:locations];
[self sync];
}
}
/**
* We are running in the background if this is being executed.
* We can't assume normal network access.
......@@ -104,87 +134,111 @@
*/
-(void) sync
{
NSLog(@"BackgroundGeoLocation sync");
NSLog(@"- CDVBackgroundGeoLocation sync");
// Some voodoo.
// Note that the expiration handler block simply ends the task. It is important that we always
// end tasks that we have started.
UIBackgroundTaskIdentifier bgTask = 0;
UIApplication *app = [UIApplication sharedApplication];
// Some voodoo.
bgTask = [app beginBackgroundTaskWithExpirationHandler:
^{
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
}];
// Prepare a reusable Request instance. We'll reuse it each time we iterate the queue below.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: self.url]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
// The list of successfully recorded locations on server.
NSMutableArray *recordedLocations = [NSMutableArray array];
// Iterate the queue.
CLLocation *location;
for (location in self.locationCache) {
// Build the json-data.
NSString *lat = [NSString stringWithFormat: @"%f", location.coordinate.latitude];
NSString *lng = [NSString stringWithFormat: @"%f", location.coordinate.longitude];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSMutableDictionary *data = [NSMutableDictionary dictionary];
[params setValue: self.token forKey: @"auth_token"];
[params setValue: @"true" forKey: @"background_geolocation"];
[data setValue: lat forKey: @"latitude"];
[data setValue: lng forKey: @"longitude"];
[data setValue: [location.timestamp descriptionWithLocale:[NSLocale systemLocale]] forKey: @"recorded_at"];
[params setObject:data forKey:@"location"];
NSString *json = [params JSONString];
NSData *requestData = [NSData dataWithBytes:[json UTF8String] length:[json length]];
[request setHTTPBody: requestData];
// Synchronous HTTP request
NSHTTPURLResponse* urlResponse = nil;
NSError *error = [[NSError alloc] init];
[NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
if ([urlResponse statusCode] == 200) {
// Yeehaw!
NSLog(@"BackgroundGeoLocation HTTP SUCCESS");
[recordedLocations addObject:location];
} else {
// If any HTTP request fails, break out of emptying queue and try again later.
NSLog(@"BackgroundGeoLocation HTTP FAILED");
break;
}
}
// Remove our successfully recorded locations from cache.
[self.locationCache removeObjectsInArray:recordedLocations];
// Fetch last recorded location
CLLocation *location = [locationCache lastObject];
// Build a resultset for javascript callback.
CDVPluginResult* result = nil;
NSMutableDictionary* returnInfo = [NSMutableDictionary dictionaryWithCapacity:8];
NSNumber* timestamp = [NSNumber numberWithDouble:([location.timestamp timeIntervalSince1970] * 1000)];
[returnInfo setObject:timestamp forKey:@"timestamp"];
[returnInfo setObject:[NSNumber numberWithDouble:location.speed] forKey:@"velocity"];
[returnInfo setObject:[NSNumber numberWithDouble:location.verticalAccuracy] forKey:@"altitudeAccuracy"];
[returnInfo setObject:[NSNumber numberWithDouble:location.horizontalAccuracy] forKey:@"accuracy"];
[returnInfo setObject:[NSNumber numberWithDouble:location.course] forKey:@"heading"];
[returnInfo setObject:[NSNumber numberWithDouble:location.altitude] forKey:@"altitude"];
[returnInfo setObject:[NSNumber numberWithDouble:location.coordinate.latitude] forKey:@"latitude"];
[returnInfo setObject:[NSNumber numberWithDouble:location.coordinate.longitude] forKey:@"longitude"];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnInfo];
[result setKeepCallbackAsBool:YES];
// Inform javascript a background-fetch event has occurred.
[self.commandDelegate sendPluginResult:result callbackId:syncCallbackId];
}
-(void) finish:(CDVInvokedUrlCommand*)command
{
NSLog(@"CDVBackgroundGeoLocation finish");
//_completionHandler(UIBackgroundFetchResultNewData);
// Finish the voodoo.
if (bgTask != UIBackgroundTaskInvalid)
{
[app endBackgroundTask:bgTask];
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
}
/**
* Called when user exits their stationary radius (ie: they walked ~50m away from their last recorded location.
* - turn on more aggressive location monitoring.
*/
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(@"- CDVBackgroundGeoLocation exit region");
if (myRegion != nil) {
[locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopMonitoringForRegion:myRegion];
[locationManager startUpdatingLocation];
myRegion = nil;
}
}
/**
* 1. turn off std location services
* 2. turn on significantChanges API
* 3. create a region and start monitoring exits.
* 4. clear locationCache
*/
- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager
{
NSLog(@"- CDVBackgroundGeoLocation paused location updates");
[locationManager stopUpdatingLocation];
[locationManager startMonitoringSignificantLocationChanges];
myRegion = [self createStationaryRegion];
[manager startMonitoringForRegion:myRegion];
}
/**
* 1. Turn off significantChanges ApI
* 2. turn on std. location services
* 3. nullify myRegion
*/
- (void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager
{
NSLog(@"- CDVBackgroundGeoLocation resume location updates");
[locationManager stopMonitoringSignificantLocationChanges];
[locationManager startUpdatingLocation];
}
-(CLCircularRegion*) createStationaryRegion {
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter: [[locationManager location] coordinate] radius:stationaryRadius identifier:@"BackgroundGeoLocation stationary region"];
region.notifyOnExit = YES;
return region;
}
// If you don't stopMonitorying 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.
// Might be desirable in certain apps.
- (void)applicationWillTerminate:(UIApplication *)application {
[self.locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopUpdatingLocation];
if (myRegion != nil) {
[locationManager stopMonitoringForRegion:myRegion];
}
}
- (void)dealloc
{
self.locationManager.delegate = nil;
locationManager.delegate = nil;
}
@end
......@@ -28,11 +28,15 @@ module.exports = {
if (!config.auth_token || !config.url) {
console.log("BackgroundGeoLocation requires an auth_token and url to report to the server");
}
var stationaryRadius = config.stationaryRadius || 50, // meters
distanceFilter = config.distanceFilter || 500, // meters
locationTimeout = config.locationTimeout || 60; // seconds
exec(success || function() {},
failure || function() {},
'BackgroundGeoLocation',
'configure',
[config.auth_token, config.url]);
[config.auth_token, config.url, stationaryRadius, distanceFilter, locationTimeout]);
},
start: function(success, failure, config) {
exec(success || function() {},
......@@ -54,6 +58,14 @@ module.exports = {
'BackgroundGeoLocation',
'test',
[]);
},
finish: function(success, failure) {
exec(success || function() {},
failure || function() {},
'BackgroundGeoLocation',
'finish',
[]);
}
};
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