Commit de8f7ad9 authored by Chris Scott's avatar Chris Scott

Implement fired event when stationary-region is acquired. refactor queue mechanism

parent cfc139fb
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
CDVLocationData *locationData; CDVLocationData *locationData;
CLLocation *lastLocation; CLLocation *lastLocation;
NSMutableArray *locationQueue;
NSDate *suspendedAt; NSDate *suspendedAt;
...@@ -56,7 +57,9 @@ ...@@ -56,7 +57,9 @@
NSInteger desiredAccuracy; NSInteger desiredAccuracy;
CLActivityType activityType; CLActivityType activityType;
} }
@synthesize syncCallbackId; @synthesize syncCallbackId;
@synthesize stationaryRegionListeners;
- (void)pluginInitialize - (void)pluginInitialize
{ {
...@@ -67,6 +70,8 @@ ...@@ -67,6 +70,8 @@
localNotification = [[UILocalNotification alloc] init]; localNotification = [[UILocalNotification alloc] init];
localNotification.timeZone = [NSTimeZone defaultTimeZone]; localNotification.timeZone = [NSTimeZone defaultTimeZone];
locationQueue = [[NSMutableArray alloc] init];
isMoving = NO; isMoving = NO;
isUpdatingLocation = NO; isUpdatingLocation = NO;
stationaryLocation = nil; stationaryLocation = nil;
...@@ -125,6 +130,39 @@ ...@@ -125,6 +130,39 @@
NSLog(@" - activityType: %@", [command.arguments objectAtIndex:7]); NSLog(@" - activityType: %@", [command.arguments objectAtIndex:7]);
NSLog(@" - debug: %d", isDebugging); NSLog(@" - debug: %d", isDebugging);
} }
- (void) addStationaryRegionListener:(CDVInvokedUrlCommand*)command
{
if (self.stationaryRegionListeners == nil) {
self.stationaryRegionListeners = [[NSMutableArray alloc] init];
}
[self.stationaryRegionListeners addObject:command.callbackId];
if (stationaryRegion) {
[self queue:stationaryLocation type:@"stationary"];
}
}
- (void) flushQueue
{
NSLog(@"- CDVBackgroundGeoLocation#flushQueue, %lu, %d", (unsigned long)[locationQueue count], bgTask == UIBackgroundTaskInvalid);
// Sanity-check the duration of last bgTask: If greater than 30s, kill it.
if (bgTask != UIBackgroundTaskInvalid) {
if (-[lastBgTaskAt timeIntervalSinceNow] > 30.0) {
[self stopBackgroundTask];
}
return;
}
if ([locationQueue count] > 0) {
NSMutableDictionary *data = [locationQueue lastObject];
[locationQueue removeObject:data];
// Create a background-task and delegate to Javascript for syncing location
bgTask = [self createBackgroundTask];
[self.commandDelegate runInBackground:^{
[self sync:data];
}];
}
}
- (void) setConfig:(CDVInvokedUrlCommand*)command - (void) setConfig:(CDVInvokedUrlCommand*)command
{ {
NSLog(@"- CDVBackgroundGeoLocation setConfig"); NSLog(@"- CDVBackgroundGeoLocation setConfig");
...@@ -194,7 +232,7 @@ ...@@ -194,7 +232,7 @@
enabled = YES; enabled = YES;
UIApplicationState state = [[UIApplication sharedApplication] applicationState]; UIApplicationState state = [[UIApplication sharedApplication] applicationState];
NSLog(@"- CDVBackgroundGeoLocation start (background? %d)", state); NSLog(@"- CDVBackgroundGeoLocation start (background? %ld)", state);
[locationManager startMonitoringSignificantLocationChanges]; [locationManager startMonitoringSignificantLocationChanges];
if (state == UIApplicationStateBackground) { if (state == UIApplicationStateBackground) {
...@@ -249,17 +287,16 @@ ...@@ -249,17 +287,16 @@
isAcquiringStationaryLocation = NO; isAcquiringStationaryLocation = NO;
isAcquiringSpeed = NO; isAcquiringSpeed = NO;
locationAcquisitionAttempts = 0; locationAcquisitionAttempts = 0;
stationaryLocation = nil;
// Kill the current stationary-region.
if (stationaryRegion != nil) {
[locationManager stopMonitoringForRegion:stationaryRegion];
stationaryRegion = nil;
}
if (isDebugging) { if (isDebugging) {
AudioServicesPlaySystemSound (isMoving ? paceChangeYesSound : paceChangeNoSound); AudioServicesPlaySystemSound (isMoving ? paceChangeYesSound : paceChangeNoSound);
} }
if (isMoving) { if (isMoving) {
if (stationaryRegion) {
[locationManager stopMonitoringForRegion:stationaryRegion];
stationaryRegion = nil;
}
isAcquiringSpeed = YES; isAcquiringSpeed = YES;
} else { } else {
isAcquiringStationaryLocation = YES; isAcquiringStationaryLocation = YES;
...@@ -280,22 +317,11 @@ ...@@ -280,22 +317,11 @@
{ {
NSLog(@"- CDVBackgroundGeoLocation getStationaryLocation"); NSLog(@"- CDVBackgroundGeoLocation getStationaryLocation");
NSMutableDictionary* returnInfo;
// Build a resultset for javascript callback. // Build a resultset for javascript callback.
CDVPluginResult* result = nil; CDVPluginResult* result = nil;
if (stationaryLocation) { if (stationaryLocation) {
returnInfo = [NSMutableDictionary dictionaryWithCapacity:9]; NSMutableDictionary *returnInfo = [self locationToHash:stationaryLocation];
NSNumber* timestamp = [NSNumber numberWithDouble:([stationaryLocation.timestamp timeIntervalSince1970] * 1000)];
[returnInfo setObject:timestamp forKey:@"timestamp"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.speed] forKey:@"velocity"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.verticalAccuracy] forKey:@"altitudeAccuracy"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.horizontalAccuracy] forKey:@"accuracy"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.course] forKey:@"heading"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.altitude] forKey:@"altitude"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.coordinate.latitude] forKey:@"latitude"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.coordinate.longitude] forKey:@"longitude"];
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnInfo]; result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnInfo];
} else { } else {
...@@ -303,6 +329,24 @@ ...@@ -303,6 +329,24 @@
} }
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
} }
-(NSMutableDictionary*) locationToHash:(CLLocation*)location
{
NSMutableDictionary *returnInfo;
returnInfo = [NSMutableDictionary dictionaryWithCapacity:10];
NSNumber* timestamp = [NSNumber numberWithDouble:([stationaryLocation.timestamp timeIntervalSince1970] * 1000)];
[returnInfo setObject:timestamp forKey:@"timestamp"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.speed] forKey:@"speed"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.verticalAccuracy] forKey:@"altitudeAccuracy"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.horizontalAccuracy] forKey:@"accuracy"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.course] forKey:@"heading"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.altitude] forKey:@"altitude"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.coordinate.latitude] forKey:@"latitude"];
[returnInfo setObject:[NSNumber numberWithDouble:stationaryLocation.coordinate.longitude] forKey:@"longitude"];
return returnInfo;
}
/** /**
* Called by js to signify the end of a background-geolocation event * Called by js to signify the end of a background-geolocation event
*/ */
...@@ -321,6 +365,17 @@ ...@@ -321,6 +365,17 @@
suspendedAt = [NSDate date]; suspendedAt = [NSDate date];
if (enabled) { if (enabled) {
// Sample incoming stationary-location candidate: Is it within the current stationary-region? If not, I guess we're moving.
if (!isMoving && stationaryRegion) {
if ([self locationAge:stationaryLocation] < (5 * 60.0)) {
if (isDebugging) {
AudioServicesPlaySystemSound (acquiredLocationSound);
[self notify:[NSString stringWithFormat:@"Continue stationary\n%f,%f", [stationaryLocation coordinate].latitude, [stationaryLocation coordinate].longitude]];
}
[self queue:stationaryLocation type:@"stationary"];
return;
}
}
[self setPace: isMoving]; [self setPace: isMoving];
} }
} }
...@@ -345,7 +400,6 @@ ...@@ -345,7 +400,6 @@
} }
CLLocation *location = [locations lastObject]; CLLocation *location = [locations lastObject];
lastLocation = location;
if (!isMoving && !isAcquiringStationaryLocation && !stationaryLocation) { if (!isMoving && !isAcquiringStationaryLocation && !stationaryLocation) {
// Perhaps our GPS signal was interupted, re-acquire a stationaryLocation now. // Perhaps our GPS signal was interupted, re-acquire a stationaryLocation now.
...@@ -354,13 +408,13 @@ ...@@ -354,13 +408,13 @@
// test the age of the location measurement to determine if the measurement is cached // test the age of the location measurement to determine if the measurement is cached
// in most cases you will not want to rely on cached measurements // in most cases you will not want to rely on cached measurements
NSTimeInterval locationAge = -[location.timestamp timeIntervalSinceNow]; if ([self locationAge:location] > 5.0) return;
if (locationAge > 5.0) return;
// test that the horizontal accuracy does not indicate an invalid measurement // test that the horizontal accuracy does not indicate an invalid measurement
if (location.horizontalAccuracy < 0) return; if (location.horizontalAccuracy < 0) return;
lastLocation = location;
// test the measurement to see if it is more accurate than the previous measurement // test the measurement to see if it is more accurate than the previous measurement
if (isAcquiringStationaryLocation) { if (isAcquiringStationaryLocation) {
NSLog(@"- Acquiring stationary location, accuracy: %f", location.horizontalAccuracy); NSLog(@"- Acquiring stationary location, accuracy: %f", location.horizontalAccuracy);
...@@ -402,42 +456,28 @@ ...@@ -402,42 +456,28 @@
[self startUpdatingLocation]; [self startUpdatingLocation];
} }
} }
[self queue:location type:@"current"];
// Uh-oh: already a background-task in-effect. // Uh-oh: already a background-task in-effect.
// If we have a bgTask hanging around for 60 seconds, kill it and move on; otherwise, wait a bit longer for the existing bgTask to finish. // If we have a bgTask hanging around for 60 seconds, kill it and move on; otherwise, wait a bit longer for the existing bgTask to finish.
/*
if (bgTask != UIBackgroundTaskInvalid) { if (bgTask != UIBackgroundTaskInvalid) {
// Fail-safe: If Javascript doesn't explicitly execute our #finish method (perhaps it crashed or waiting for long HTTP timeout), NSLog(@"Found existing background-task. Added to Queue");
// bgTask will never get cleared. Enforces that bgTask can never last longer than 60s
NSTimeInterval duration = -[lastBgTaskAt timeIntervalSinceNow]; return;
if (duration > 60.0) {
[self stopBackgroundTask];
} else {
NSLog(@"Abort: found existing background-task");
if (isDebugging) {
[self notify:@"waiting for existing background-task"];
}
return;
}
} }
*/
// Create a background-task and delegate to Javascript for syncing location
bgTask = [self createBackgroundTask];
[self.commandDelegate runInBackground:^{
[self sync:location];
}];
NSLog(@" position: %f,%f, speed: %f", location.coordinate.latitude, location.coordinate.longitude, location.speed);
if (isDebugging) {
[self notify:[NSString stringWithFormat:@"Location update: %s\nSPD: %0.1f | DF: %0.1f | ACY: %d",
((isMoving) ? "MOVING" : "STATIONARY"),
fabsf(location.speed),
locationManager.distanceFilter,
(int) location.horizontalAccuracy]];
AudioServicesPlaySystemSound (locationSyncSound);
}
} }
-(void) queue:(CLLocation*)location type:(id)type
{
NSLog(@"- CDVBackgroundGeoLocation queue %@", type);
NSMutableDictionary *data = [self locationToHash:location];
[data setObject:type forKey:@"location_type"];
[locationQueue addObject:data];
[self flushQueue];
}
-(UIBackgroundTaskIdentifier) createBackgroundTask -(UIBackgroundTaskIdentifier) createBackgroundTask
{ {
lastBgTaskAt = [NSDate date]; lastBgTaskAt = [NSDate date];
...@@ -465,44 +505,55 @@ ...@@ -465,44 +505,55 @@
* We can't assume normal network access. * We can't assume normal network access.
* bgTask is defined as an instance variable of type UIBackgroundTaskIdentifier * bgTask is defined as an instance variable of type UIBackgroundTaskIdentifier
*/ */
-(void) sync:(CLLocation*)location -(void) sync:(NSMutableDictionary*)data
{ {
NSMutableDictionary* returnInfo = [NSMutableDictionary dictionaryWithCapacity:8]; NSLog(@"- CDVBackgroundGeoLocation#sync");
NSNumber* timestamp = [NSNumber numberWithDouble:([location.timestamp timeIntervalSince1970] * 1000)]; NSLog(@" type: %@, position: %@,%@ speed: %@", [data objectForKey:@"location_type"], [data objectForKey:@"latitude"], [data objectForKey:@"longitude"], [data objectForKey:@"speed"]);
[returnInfo setObject:timestamp forKey:@"timestamp"]; if (isDebugging) {
[returnInfo setObject:[NSNumber numberWithDouble:location.speed] forKey:@"velocity"]; [self notify:[NSString stringWithFormat:@"Location update: %s\nSPD: %@ | DF: %0.1f | ACY: %@",
[returnInfo setObject:[NSNumber numberWithDouble:location.verticalAccuracy] forKey:@"altitudeAccuracy"]; ((isMoving) ? "MOVING" : "STATIONARY"),
[returnInfo setObject:[NSNumber numberWithDouble:location.horizontalAccuracy] forKey:@"accuracy"]; [data objectForKey:@"speed"],
[returnInfo setObject:[NSNumber numberWithDouble:location.course] forKey:@"heading"]; locationManager.distanceFilter,
[returnInfo setObject:[NSNumber numberWithDouble:location.altitude] forKey:@"altitude"]; [data objectForKey:@"accuracy"]]];
[returnInfo setObject:[NSNumber numberWithDouble:location.coordinate.latitude] forKey:@"latitude"];
[returnInfo setObject:[NSNumber numberWithDouble:location.coordinate.longitude] forKey:@"longitude"]; AudioServicesPlaySystemSound (locationSyncSound);
}
// Build a resultset for javascript callback. // Build a resultset for javascript callback.
CDVPluginResult* result = nil; NSString *locationType = [data objectForKey:@"location_type"];
if ([locationType isEqualToString:@"stationary"]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnInfo]; [self fireStationaryRegionListeners:data];
[result setKeepCallbackAsBool:YES]; } else if ([locationType isEqualToString:@"current"]) {
CDVPluginResult* result = nil;
[self.commandDelegate sendPluginResult:result callbackId:self.syncCallbackId]; result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:data];
[result setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:result callbackId:self.syncCallbackId];
} else {
NSLog(@"- CDVBackgroundGeoLocation#sync could not determine location_type.");
[self stopBackgroundTask];
}
} }
/** /**
* Creates a new circle around user and region-monitors it for exit * Creates a new circle around user and region-monitors it for exit
*/ */
- (void) startMonitoringStationaryRegion:(CLLocation*)location { - (void) startMonitoringStationaryRegion:(CLLocation*)location {
stationaryLocation = location;
// fire onStationary @event for Javascript.
[self queue:location type:@"stationary"];
CLLocationCoordinate2D coord = [location coordinate]; CLLocationCoordinate2D coord = [location coordinate];
NSLog(@"- CDVBackgroundGeoLocation createStationaryRegion (%f,%f)", coord.latitude, coord.longitude); NSLog(@"- CDVBackgroundGeoLocation createStationaryRegion (%f,%f)", coord.latitude, coord.longitude);
if (isDebugging) { if (isDebugging) {
AudioServicesPlaySystemSound (acquiredLocationSound); AudioServicesPlaySystemSound (acquiredLocationSound);
[self notify:@"Acquired stationary location"]; [self notify:[NSString stringWithFormat:@"Acquired stationary location\n%f, %f", location.coordinate.latitude,location.coordinate.longitude]];
} }
if (stationaryRegion != nil) { if (stationaryRegion != nil) {
[locationManager stopMonitoringForRegion:stationaryRegion]; [locationManager stopMonitoringForRegion:stationaryRegion];
} }
isAcquiringStationaryLocation = NO; isAcquiringStationaryLocation = NO;
stationaryRegion = [[CLCircularRegion alloc] initWithCenter: coord radius:stationaryRadius identifier:@"BackgroundGeoLocation stationary region"]; stationaryRegion = [[CLCircularRegion alloc] initWithCenter: coord radius:stationaryRadius identifier:@"BackgroundGeoLocation stationary region"];
stationaryRegion.notifyOnExit = YES; stationaryRegion.notifyOnExit = YES;
[locationManager startMonitoringForRegion:stationaryRegion]; [locationManager startMonitoringForRegion:stationaryRegion];
...@@ -512,6 +563,27 @@ ...@@ -512,6 +563,27 @@
locationManager.desiredAccuracy = desiredAccuracy; locationManager.desiredAccuracy = desiredAccuracy;
} }
- (void) fireStationaryRegionListeners:(NSMutableDictionary*)data
{
NSLog(@"- CDVBackgroundGeoLocation#fireStationaryRegionListeners: %d", [locationQueue count]);
if (![self.stationaryRegionListeners count]) {
[self stopBackgroundTask];
return;
}
// Any javascript stationaryRegion event-listeners?
[data setObject:[NSNumber numberWithDouble:stationaryRadius] forKey:@"radius"];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:data];
[result setKeepCallbackAsBool:YES];
for (NSString *callbackId in self.stationaryRegionListeners)
{
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
}
}
- (bool) stationaryRegionContainsLocation:(CLLocation*)location {
CLCircularRegion *region = [locationManager.monitoredRegions member:stationaryRegion];
return ([region containsCoordinate:location.coordinate]) ? YES : NO;
}
- (void) stopBackgroundTask - (void) stopBackgroundTask
{ {
UIApplication *app = [UIApplication sharedApplication]; UIApplication *app = [UIApplication sharedApplication];
...@@ -521,6 +593,7 @@ ...@@ -521,6 +593,7 @@
[app endBackgroundTask:bgTask]; [app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid; bgTask = UIBackgroundTaskInvalid;
} }
[self flushQueue];
} }
/** /**
* Called when user exits their stationary radius (ie: they walked ~50m away from their last recorded location. * Called when user exits their stationary radius (ie: they walked ~50m away from their last recorded location.
...@@ -617,6 +690,11 @@ ...@@ -617,6 +690,11 @@
} }
} }
- (NSTimeInterval) locationAge:(CLLocation*)location
{
return -[location.timestamp timeIntervalSinceNow];
}
- (void) notify:(NSString*)message - (void) notify:(NSString*)message
{ {
localNotification.fireDate = [NSDate date]; localNotification.fireDate = [NSDate date];
......
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