Commit eb0d6d80 authored by Chris Scott's avatar Chris Scott

Merge branch 'refactor'

parents d4d7571f 51857664
...@@ -12,17 +12,11 @@ ...@@ -12,17 +12,11 @@
- (void) start:(CDVInvokedUrlCommand*)command; - (void) start:(CDVInvokedUrlCommand*)command;
- (void) stop:(CDVInvokedUrlCommand*)command; - (void) stop:(CDVInvokedUrlCommand*)command;
- (void) test:(CDVInvokedUrlCommand*)command; - (void) test:(CDVInvokedUrlCommand*)command;
- (void) finish:(CDVInvokedUrlCommand*)command;
- (void) sync; - (void) sync;
- (void) onSuspend:(NSNotification *)notification; - (void) onSuspend:(NSNotification *)notification;
- (void) onResume:(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 @end
...@@ -8,57 +8,81 @@ ...@@ -8,57 +8,81 @@
#import "CDVBackgroundGeoLocation.h" #import "CDVBackgroundGeoLocation.h"
#import <Cordova/CDVJSON.h> #import <Cordova/CDVJSON.h>
@implementation CDVBackgroundGeoLocation @implementation CDVBackgroundGeoLocation {
BOOL enabled;
@synthesize enabled, token, url, locationManager, locationData, locationCache, suspendedAt; 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 - (CDVPlugin*) initWithWebView:(UIWebView*) theWebView
{ {
// background location cache, for when no network is detected. // 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 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSuspend:) name:UIApplicationDidEnterBackgroundNotification object:nil];
selector:@selector(onSuspend:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume:) name:UIApplicationWillEnterForegroundNotification object:nil];
selector:@selector(onResume:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
return self; return self;
} }
- (void) configure:(CDVInvokedUrlCommand*)command - (void) configure:(CDVInvokedUrlCommand*)command
{ {
self.locationManager = [[CLLocationManager alloc] init]; // in iOS, we call to javascript for HTTP now so token and url should be @deprecated until Android calls out to javascript.
self.locationManager.delegate = self; token = [command.arguments objectAtIndex: 0];
url = [command.arguments objectAtIndex: 1];
self.token = [command.arguments objectAtIndex: 0];
self.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(@"CDVBackgroundGeoLocation configure");
NSLog(@" -token: %@", self.token); NSLog(@" - token: %@", token);
NSLog(@" -url: %@", self.url); NSLog(@" - url: %@", url);
NSLog(@" - distanceFilter: %ld", (long)distanceFilter);
NSLog(@" - stationaryRadius: %ld", (long)stationaryRadius);
NSLog(@" - locationTimeout: %ld", (long)locationTimeout);
} }
- (void) start:(CDVInvokedUrlCommand*)command - (void) start:(CDVInvokedUrlCommand*)command
{ {
NSLog(@"CDVBackgroundGeoLocation start"); NSLog(@"CDVBackgroundGeoLocation start");
self.enabled = YES; enabled = YES;
} }
- (void) stop:(CDVInvokedUrlCommand*)command - (void) stop:(CDVInvokedUrlCommand*)command
{ {
NSLog(@"CDVBackgroundGeoLocation stop"); NSLog(@"CDVBackgroundGeoLocation stop");
self.enabled = NO; enabled = NO;
[self.locationManager stopMonitoringSignificantLocationChanges]; [locationManager stopUpdatingLocation];
[locationManager stopMonitoringSignificantLocationChanges];
} }
- (void) test:(CDVInvokedUrlCommand*)command - (void) test:(CDVInvokedUrlCommand*)command
{ {
NSLog(@"CDVBackgroundGeoLocation test"); NSLog(@"CDVBackgroundGeoLocation test");
[self.locationManager startMonitoringSignificantLocationChanges]; [locationManager startMonitoringSignificantLocationChanges];
if ([self.locationCache count] > 0){ if ([locationCache count] > 0){
[self sync]; [self sync];
} else { } else {
NSLog(@"CDVBackgroundGeoLocation could not find a location to sync"); NSLog(@"CDVBackgroundGeoLocation could not find a location to sync");
...@@ -67,20 +91,25 @@ ...@@ -67,20 +91,25 @@
-(void) onSuspend:(NSNotification *) notification -(void) onSuspend:(NSNotification *) notification
{ {
NSLog(@"CDVBackgroundGeoLocation suspend"); NSLog(@"CDVBackgroundGeoLocation suspend");
self.suspendedAt = [NSDate date]; suspendedAt = [NSDate date];
if (self.enabled) { if (enabled) {
[self.locationManager startMonitoringSignificantLocationChanges]; if (myRegion == nil) {
myRegion = [self createStationaryRegion];
[locationManager startMonitoringForRegion:myRegion];
}
[locationManager startMonitoringSignificantLocationChanges];
} }
} }
-(void) onResume:(NSNotification *) notification -(void) onResume:(NSNotification *) notification
{ {
NSLog(@"CDVBackgroundGeoLocation resume"); NSLog(@"CDVBackgroundGeoLocation resume");
if (self.enabled) { if (enabled) {
[self.locationManager stopMonitoringSignificantLocationChanges]; [locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopUpdatingLocation];
} }
// When coming back-to-life, flush the background queue. // When coming back-to-life, flush the background queue.
if ([self.locationCache count] > 0){ if ([locationCache count] > 0){
[self sync]; [self sync];
} }
} }
...@@ -93,10 +122,11 @@ ...@@ -93,10 +122,11 @@
// The omitted code should determine whether to reject the location update for being too // 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 // old, too close to the previous one, too inaccurate and so forth according to your own
// application design. // application design.
[self.locationCache addObjectsFromArray:locations]; [locationCache addObjectsFromArray:locations];
[self sync]; [self sync];
}
}
/** /**
* We are running in the background if this is being executed. * We are running in the background if this is being executed.
* We can't assume normal network access. * We can't assume normal network access.
...@@ -104,87 +134,111 @@ ...@@ -104,87 +134,111 @@
*/ */
-(void) sync -(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 // Note that the expiration handler block simply ends the task. It is important that we always
// end tasks that we have started. // end tasks that we have started.
UIBackgroundTaskIdentifier bgTask = 0;
UIApplication *app = [UIApplication sharedApplication]; UIApplication *app = [UIApplication sharedApplication];
// Some voodoo. bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
bgTask = [app beginBackgroundTaskWithExpirationHandler:
^{
[app endBackgroundTask:bgTask]; [app endBackgroundTask:bgTask];
}]; }];
// Prepare a reusable Request instance. We'll reuse it each time we iterate the queue below. // Fetch last recorded location
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: self.url] CLLocation *location = [locationCache lastObject];
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:10.0]; // Build a resultset for javascript callback.
CDVPluginResult* result = nil;
[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];
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. // Finish the voodoo.
if (bgTask != UIBackgroundTaskInvalid) if (bgTask != UIBackgroundTaskInvalid)
{ {
[app endBackgroundTask:bgTask]; [[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid; 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 // 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. // new location arrives, essentially monitoring the user's location even when they've killed the app.
// Might be desirable in certain apps. // Might be desirable in certain apps.
- (void)applicationWillTerminate:(UIApplication *)application { - (void)applicationWillTerminate:(UIApplication *)application {
[self.locationManager stopMonitoringSignificantLocationChanges]; [locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopUpdatingLocation];
if (myRegion != nil) {
[locationManager stopMonitoringForRegion:myRegion];
}
} }
- (void)dealloc - (void)dealloc
{ {
self.locationManager.delegate = nil; locationManager.delegate = nil;
} }
@end @end
...@@ -28,11 +28,15 @@ module.exports = { ...@@ -28,11 +28,15 @@ module.exports = {
if (!config.auth_token || !config.url) { if (!config.auth_token || !config.url) {
console.log("BackgroundGeoLocation requires an auth_token and url to report to the server"); 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() {}, exec(success || function() {},
failure || function() {}, failure || function() {},
'BackgroundGeoLocation', 'BackgroundGeoLocation',
'configure', 'configure',
[config.auth_token, config.url]); [config.auth_token, config.url, stationaryRadius, distanceFilter, locationTimeout]);
}, },
start: function(success, failure, config) { start: function(success, failure, config) {
exec(success || function() {}, exec(success || function() {},
...@@ -54,6 +58,14 @@ module.exports = { ...@@ -54,6 +58,14 @@ module.exports = {
'BackgroundGeoLocation', 'BackgroundGeoLocation',
'test', '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