Advertisement
Guest User

backgeolocation

a guest
Sep 2nd, 2015
198
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. ////
  2. //  CDVBackgroundGeoLocation
  3. //
  4. //  Created by Chris Scott <chris@transistorsoft.com> on 2013-06-15
  5. //
  6. #import "CDVLocation.h"
  7. #import "CDVBackgroundGeoLocation.h"
  8. #import <Cordova/CDVJSON.h>
  9.  
  10. // Debug sounds for bg-geolocation life-cycle events.
  11. // http://iphonedevwiki.net/index.php/AudioServices
  12. #define exitRegionSound         1005
  13. #define locationSyncSound       1004
  14. #define paceChangeYesSound      1110
  15. #define paceChangeNoSound       1112
  16. #define acquiringLocationSound  1103
  17. #define acquiredLocationSound   1052
  18. #define locationErrorSound      1073
  19.  
  20. @implementation CDVBackgroundGeoLocation {
  21.     BOOL isDebugging;
  22.     BOOL enabled;
  23.     BOOL isUpdatingLocation;
  24.     BOOL stopOnTerminate;
  25.  
  26.     NSString *token;
  27.     NSString *url;
  28.     UIBackgroundTaskIdentifier bgTask;
  29.     NSDate *lastBgTaskAt;
  30.  
  31.     NSError *locationError;
  32.  
  33.     BOOL isMoving;
  34.  
  35.     NSNumber *maxBackgroundHours;
  36.     CLLocationManager *locationManager;
  37.     UILocalNotification *localNotification;
  38.  
  39.     CDVLocationData *locationData;
  40.     CLLocation *lastLocation;
  41.     NSMutableArray *locationQueue;
  42.  
  43.     NSDate *suspendedAt;
  44.  
  45.     CLLocation *stationaryLocation;
  46.     CLCircularRegion *stationaryRegion;
  47.     NSInteger locationAcquisitionAttempts;
  48.  
  49.     BOOL isAcquiringStationaryLocation;
  50.     NSInteger maxStationaryLocationAttempts;
  51.  
  52.     BOOL isAcquiringSpeed;
  53.     NSInteger maxSpeedAcquistionAttempts;
  54.  
  55.     NSInteger stationaryRadius;
  56.     NSInteger distanceFilter;
  57.     NSInteger locationTimeout;
  58.     NSInteger desiredAccuracy;
  59.     CLActivityType activityType;
  60. }
  61.  
  62. @synthesize syncCallbackId;
  63. @synthesize stationaryRegionListeners;
  64.  
  65. - (void)pluginInitialize
  66. {
  67.     // background location cache, for when no network is detected.
  68.     locationManager = [[CLLocationManager alloc] init];
  69.     locationManager.delegate = self;
  70.  
  71.     localNotification = [[UILocalNotification alloc] init];
  72.     localNotification.timeZone = [NSTimeZone defaultTimeZone];
  73.  
  74.     locationQueue = [[NSMutableArray alloc] init];
  75.  
  76.     isMoving = NO;
  77.     isUpdatingLocation = NO;
  78.     stationaryLocation = nil;
  79.     stationaryRegion = nil;
  80.     isDebugging = NO;
  81.     stopOnTerminate = NO;
  82.  
  83.     maxStationaryLocationAttempts   = 4;
  84.     maxSpeedAcquistionAttempts      = 3;
  85.  
  86.     bgTask = UIBackgroundTaskInvalid;
  87.  
  88.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSuspend:) name:UIApplicationDidEnterBackgroundNotification object:nil];
  89.  
  90.     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume:) name:UIApplicationWillEnterForegroundNotification object:nil];
  91.    
  92. }
  93. /**
  94.  * configure plugin
  95.  * @param {String} token
  96.  * @param {String} url
  97.  * @param {Number} stationaryRadius
  98.  * @param {Number} distanceFilter
  99.  * @param {Number} locationTimeout
  100.  */
  101. - (void) configure:(CDVInvokedUrlCommand*)command
  102. {
  103.     // in iOS, we call to javascript for HTTP now so token and url should be @deprecated until Android calls out to javascript.
  104.     // Params.
  105.     //    0       1       2           3               4                5               6            7           8                9               10               11
  106.     //[params, headers, url, stationaryRadius, distanceFilter, locationTimeout, desiredAccuracy, debug, notificationTitle, notificationText, activityType, stopOnTerminate]
  107.  
  108.     // UNUSED ANDROID VARS
  109.     //params = [command.arguments objectAtIndex: 0];
  110.     //headers = [command.arguments objectAtIndex: 1];
  111.     //url = [command.arguments objectAtIndex: 2];
  112.     stationaryRadius    = [[command.arguments objectAtIndex: 3] intValue];
  113.     distanceFilter      = [[command.arguments objectAtIndex: 4] intValue];
  114.     locationTimeout     = [[command.arguments objectAtIndex: 5] intValue];
  115.     desiredAccuracy     = [self decodeDesiredAccuracy:[[command.arguments objectAtIndex: 6] intValue]];
  116.     isDebugging         = [[command.arguments objectAtIndex: 7] boolValue];
  117.     activityType        = [self decodeActivityType:[command.arguments objectAtIndex:10]];
  118.     stopOnTerminate     = [[command.arguments objectAtIndex: 11] boolValue];
  119.  
  120.     self.syncCallbackId = command.callbackId;
  121.  
  122.     locationManager.activityType = activityType;
  123.     locationManager.pausesLocationUpdatesAutomatically = YES;
  124.     locationManager.distanceFilter = distanceFilter; // meters
  125.     locationManager.desiredAccuracy = desiredAccuracy;
  126.    
  127.     NSLog(@"CDVBackgroundGeoLocation configure");
  128.     NSLog(@"  - token: %@", token);
  129.     NSLog(@"  - url: %@", url);
  130.     NSLog(@"  - distanceFilter: %ld", (long)distanceFilter);
  131.     NSLog(@"  - stationaryRadius: %ld", (long)stationaryRadius);
  132.     NSLog(@"  - locationTimeout: %ld", (long)locationTimeout);
  133.     NSLog(@"  - desiredAccuracy: %ld", (long)desiredAccuracy);
  134.     NSLog(@"  - activityType: %@", [command.arguments objectAtIndex:7]);
  135.     NSLog(@"  - debug: %d", isDebugging);
  136.     NSLog(@"  - stopOnTerminate: %d", stopOnTerminate);
  137.    
  138.     // ios 8 requires permissions to send local-notifications
  139.     if (isDebugging) {
  140.         UIApplication *app = [UIApplication sharedApplication];
  141.         if ([app respondsToSelector:@selector(registerUserNotificationSettings:)]) {
  142.             [app registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
  143.         }
  144.     }
  145. }
  146.  
  147. - (void) addStationaryRegionListener:(CDVInvokedUrlCommand*)command
  148. {
  149.     if (self.stationaryRegionListeners == nil) {
  150.         self.stationaryRegionListeners = [[NSMutableArray alloc] init];
  151.     }
  152.     [self.stationaryRegionListeners addObject:command.callbackId];
  153.     if (stationaryRegion) {
  154.         [self queue:stationaryLocation type:@"stationary"];
  155.     }
  156. }
  157.  
  158. - (void) flushQueue
  159. {
  160.     // Sanity-check the duration of last bgTask:  If greater than 30s, kill it.
  161.     if (bgTask != UIBackgroundTaskInvalid) {
  162.        
  163.         // Jason-Customization: START - reduce 30sec to 15 sec
  164.         if (-[lastBgTaskAt timeIntervalSinceNow] > 15.0) {
  165.         // Jason-Customization: END
  166.             NSLog(@"- CDVBackgroundGeoLocation#flushQueue has to kill an out-standing background-task!");
  167.             if (isDebugging) {
  168.                 [self notify:@"Outstanding bg-task was force-killed"];
  169.             }
  170.             [self stopBackgroundTask];
  171.         }
  172.         return;
  173.     }
  174.     if ([locationQueue count] > 0) {
  175.         NSMutableDictionary *data = [locationQueue lastObject];
  176.         [locationQueue removeObject:data];
  177.  
  178.         // Create a background-task and delegate to Javascript for syncing location
  179.         bgTask = [self createBackgroundTask];
  180.         [self.commandDelegate runInBackground:^{
  181.             [self sync:data];
  182.         }];
  183.     }
  184. }
  185. - (void) setConfig:(CDVInvokedUrlCommand*)command
  186. {
  187.     NSLog(@"- CDVBackgroundGeoLocation setConfig");
  188.     NSDictionary *config = [command.arguments objectAtIndex:0];
  189.  
  190.     if (config[@"desiredAccuracy"]) {
  191.         desiredAccuracy = [self decodeDesiredAccuracy:[config[@"desiredAccuracy"] floatValue]];
  192.         NSLog(@"    desiredAccuracy: %@", config[@"desiredAccuracy"]);
  193.     }
  194.     if (config[@"stationaryRadius"]) {
  195.         stationaryRadius = [config[@"stationaryRadius"] intValue];
  196.         NSLog(@"    stationaryRadius: %@", config[@"stationaryRadius"]);
  197.     }
  198.     if (config[@"distanceFilter"]) {
  199.         distanceFilter = [config[@"distanceFilter"] intValue];
  200.         NSLog(@"    distanceFilter: %@", config[@"distanceFilter"]);
  201.     }
  202.     if (config[@"locationTimeout"]) {
  203.         locationTimeout = [config[@"locationTimeout"] intValue];
  204.         NSLog(@"    locationTimeout: %@", config[@"locationTimeout"]);
  205.     }
  206.  
  207.     CDVPluginResult* result = nil;
  208.     result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
  209.     [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
  210. }
  211.  
  212. -(NSInteger)decodeDesiredAccuracy:(NSInteger)accuracy
  213. {
  214.     switch (accuracy) {
  215.         case 1000:
  216.             accuracy = kCLLocationAccuracyKilometer;
  217.             break;
  218.         case 100:
  219.             accuracy = kCLLocationAccuracyHundredMeters;
  220.             break;
  221.         case 10:
  222.             accuracy = kCLLocationAccuracyNearestTenMeters;
  223.             break;
  224.         case 0:
  225.             accuracy = kCLLocationAccuracyBest;
  226.             break;
  227.         default:
  228.             accuracy = kCLLocationAccuracyHundredMeters;
  229.     }
  230.     return accuracy;
  231. }
  232.  
  233. -(CLActivityType)decodeActivityType:(NSString*)name
  234. {
  235.     if ([name caseInsensitiveCompare:@"AutomotiveNavigation"]) {
  236.         return CLActivityTypeAutomotiveNavigation;
  237.     } else if ([name caseInsensitiveCompare:@"OtherNavigation"]) {
  238.         return CLActivityTypeOtherNavigation;
  239.     } else if ([name caseInsensitiveCompare:@"Fitness"]) {
  240.         return CLActivityTypeFitness;
  241.     } else {
  242.         return CLActivityTypeOther;
  243.     }
  244. }
  245.  
  246. /**
  247.  * Turn on background geolocation
  248.  */
  249. - (void) start:(CDVInvokedUrlCommand*)command
  250. {
  251.     enabled = YES;
  252.     UIApplicationState state = [[UIApplication sharedApplication] applicationState];
  253.  
  254.     NSLog(@"- CDVBackgroundGeoLocation start (background? %d)", state);
  255.  
  256.     [locationManager startMonitoringSignificantLocationChanges];
  257.     if (state == UIApplicationStateBackground) {
  258.         [self setPace:isMoving];
  259.     }
  260.     CDVPluginResult* result = nil;
  261.     result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
  262.     [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
  263. }
  264. /**
  265.  * Turn it off
  266.  */
  267. - (void) stop:(CDVInvokedUrlCommand*)command
  268. {
  269.     NSLog(@"- CDVBackgroundGeoLocation stop");
  270.     enabled = NO;
  271.     isMoving = NO;
  272.  
  273.     [self stopUpdatingLocation];
  274.     [locationManager stopMonitoringSignificantLocationChanges];
  275.     if (stationaryRegion != nil) {
  276.         [locationManager stopMonitoringForRegion:stationaryRegion];
  277.         stationaryRegion = nil;
  278.     }
  279.     CDVPluginResult* result = nil;
  280.     result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
  281.     [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
  282.  
  283. }
  284.  
  285. /**
  286.  * Change pace to moving/stopped
  287.  * @param {Boolean} isMoving
  288.  */
  289. - (void) onPaceChange:(CDVInvokedUrlCommand *)command
  290. {
  291.     isMoving = [[command.arguments objectAtIndex: 0] boolValue];
  292.     NSLog(@"- CDVBackgroundGeoLocation onPaceChange %d", isMoving);
  293.     UIApplicationState state = [[UIApplication sharedApplication] applicationState];
  294.     if (state == UIApplicationStateBackground) {
  295.         [self setPace:isMoving];
  296.     }
  297. }
  298.  
  299. /**
  300.  * toggle passive or aggressive location services
  301.  */
  302. - (void)setPace:(BOOL)value
  303. {
  304.     NSLog(@"- CDVBackgroundGeoLocation setPace %d, stationaryRegion? %d", value, stationaryRegion!=nil);
  305.     isMoving                        = value;
  306.     isAcquiringStationaryLocation   = NO;
  307.     isAcquiringSpeed                = NO;
  308.     locationAcquisitionAttempts     = 0;
  309.     stationaryLocation              = nil;
  310.  
  311.     if (isDebugging) {
  312.         AudioServicesPlaySystemSound (isMoving ? paceChangeYesSound : paceChangeNoSound);
  313.     }
  314.     if (isMoving) {
  315.         if (stationaryRegion) {
  316.             [locationManager stopMonitoringForRegion:stationaryRegion];
  317.             stationaryRegion = nil;
  318.         }
  319.         isAcquiringSpeed = YES;
  320.     } else {
  321.         isAcquiringStationaryLocation   = YES;
  322.     }
  323.     if (isAcquiringSpeed || isAcquiringStationaryLocation) {
  324.         // Crank up the GPS power temporarily to get a good fix on our current location
  325.         [self stopUpdatingLocation];
  326.         locationManager.distanceFilter = kCLDistanceFilterNone;
  327.         locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
  328.         [self startUpdatingLocation];
  329.     }
  330. }
  331.  
  332. /**
  333.  * Fetches current stationaryLocation
  334.  */
  335. - (void) getStationaryLocation:(CDVInvokedUrlCommand *)command
  336. {
  337.     NSLog(@"- CDVBackgroundGeoLocation getStationaryLocation");
  338.  
  339.     // Build a resultset for javascript callback.
  340.     CDVPluginResult* result = nil;
  341.  
  342.     if (stationaryLocation) {
  343.         NSMutableDictionary *returnInfo = [self locationToHash:stationaryLocation];
  344.  
  345.         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnInfo];
  346.     } else {
  347.         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:NO];
  348.     }
  349.     [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
  350. }
  351.  
  352. -(NSMutableDictionary*) locationToHash:(CLLocation*)location
  353. {
  354.     NSMutableDictionary *returnInfo;
  355.     returnInfo = [NSMutableDictionary dictionaryWithCapacity:10];
  356.  
  357.     NSNumber* timestamp = [NSNumber numberWithDouble:([location.timestamp timeIntervalSince1970] * 1000)];
  358.     [returnInfo setObject:timestamp forKey:@"timestamp"];
  359.     [returnInfo setObject:[NSNumber numberWithDouble:location.speed] forKey:@"speed"];
  360.     [returnInfo setObject:[NSNumber numberWithDouble:location.verticalAccuracy] forKey:@"altitudeAccuracy"];
  361.     [returnInfo setObject:[NSNumber numberWithDouble:location.horizontalAccuracy] forKey:@"accuracy"];
  362.     [returnInfo setObject:[NSNumber numberWithDouble:location.course] forKey:@"heading"];
  363.     [returnInfo setObject:[NSNumber numberWithDouble:location.altitude] forKey:@"altitude"];
  364.     [returnInfo setObject:[NSNumber numberWithDouble:location.coordinate.latitude] forKey:@"latitude"];
  365.     [returnInfo setObject:[NSNumber numberWithDouble:location.coordinate.longitude] forKey:@"longitude"];
  366.  
  367.     return returnInfo;
  368. }
  369. /**
  370.  * Called by js to signify the end of a background-geolocation event
  371.  */
  372. -(void) finish:(CDVInvokedUrlCommand*)command
  373. {
  374.     NSLog(@"- CDVBackgroundGeoLocation finish");
  375.     [self stopBackgroundTask];
  376. }
  377.  
  378. /**
  379.  * Suspend.  Turn on passive location services
  380.  */
  381. -(void) onSuspend:(NSNotification *) notification
  382. {
  383.     NSLog(@"- CDVBackgroundGeoLocation suspend (enabled? %d)", enabled);
  384.     suspendedAt = [NSDate date];
  385.  
  386.     if (enabled) {
  387.         // Sample incoming stationary-location candidate:  Is it within the current stationary-region?  If not, I guess we're moving.
  388.         if (!isMoving && stationaryRegion) {
  389.             if ([self locationAge:stationaryLocation] < (5 * 60.0)) {
  390.                 if (isDebugging) {
  391.                     AudioServicesPlaySystemSound (acquiredLocationSound);
  392.                     [self notify:[NSString stringWithFormat:@"Continue stationary\n%f,%f", [stationaryLocation coordinate].latitude, [stationaryLocation coordinate].longitude]];
  393.                 }
  394.                 [self queue:stationaryLocation type:@"stationary"];
  395.                 return;
  396.             }
  397.         }
  398.         [self setPace: isMoving];
  399.     }
  400. }
  401. /**@
  402.  * Resume.  Turn background off
  403.  */
  404. -(void) onResume:(NSNotification *) notification
  405. {
  406.     NSLog(@"- CDVBackgroundGeoLocation resume");
  407.     if (enabled) {
  408.         [self stopUpdatingLocation];
  409.     }
  410. }
  411.  
  412.  
  413.  
  414. /**@
  415.  * Termination. Checks to see if it should turn off
  416.  */
  417. -(void) onAppTerminate
  418. {
  419.     NSLog(@"- CDVBackgroundGeoLocation appTerminate");
  420.     if (enabled && stopOnTerminate) {
  421.         NSLog(@"- CDVBackgroundGeoLocation stoping on terminate");
  422.  
  423.         enabled = NO;
  424.         isMoving = NO;
  425.  
  426.         [self stopUpdatingLocation];
  427.         [locationManager stopMonitoringSignificantLocationChanges];
  428.         if (stationaryRegion != nil) {
  429.             [locationManager stopMonitoringForRegion:stationaryRegion];
  430.             stationaryRegion = nil;
  431.         }
  432.     }
  433. }
  434.  
  435.  
  436. -(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
  437. {
  438.     NSLog(@"- CDVBackgroundGeoLocation didUpdateLocations (isMoving: %d)", isMoving);
  439.  
  440.     locationError = nil;
  441.     if (isMoving && !isUpdatingLocation) {
  442.         [self startUpdatingLocation];
  443.     }
  444.  
  445.     CLLocation *location = [locations lastObject];
  446.  
  447.     if (!isMoving && !isAcquiringStationaryLocation && !stationaryLocation) {
  448.         // Perhaps our GPS signal was interupted, re-acquire a stationaryLocation now.
  449.         [self setPace: NO];
  450.     }
  451.    
  452.     // Jason - Customization: START : To return location without any checking on it
  453.     // // test the age of the location measurement to determine if the measurement is cached
  454.     // // in most cases you will not want to rely on cached measurements
  455.     // if ([self locationAge:location] > 5.0) return;
  456.  
  457.     // // test that the horizontal accuracy does not indicate an invalid measurement
  458.     // if (location.horizontalAccuracy < 0) return;
  459.  
  460.     lastLocation = location;
  461.     [self startUpdatingLocation];
  462.     // // test the measurement to see if it is more accurate than the previous measurement
  463.     // if (isAcquiringStationaryLocation) {
  464.     //     NSLog(@"- Acquiring stationary location, accuracy: %f", location.horizontalAccuracy);
  465.     //     if (isDebugging) {
  466.     //         AudioServicesPlaySystemSound (acquiringLocationSound);
  467.     //     }
  468.     //     if (stationaryLocation == nil || stationaryLocation.horizontalAccuracy > location.horizontalAccuracy) {
  469.     //         stationaryLocation = location;
  470.     //     }
  471.     //     if (++locationAcquisitionAttempts == maxStationaryLocationAttempts) {
  472.     //         isAcquiringStationaryLocation = NO;
  473.     //         [self startMonitoringStationaryRegion:stationaryLocation];
  474.     //     } else {
  475.     //         // Unacceptable stationary-location: bail-out and wait for another.
  476.     //         return;
  477.     //     }
  478.     // } else if (isAcquiringSpeed) {
  479.     //     if (isDebugging) {
  480.     //         AudioServicesPlaySystemSound (acquiringLocationSound);
  481.     //     }
  482.     //     if (++locationAcquisitionAttempts == maxSpeedAcquistionAttempts) {
  483.     //         if (isDebugging) {
  484.     //             [self notify:@"Aggressive monitoring engaged"];
  485.     //         }
  486.     //         // We should have a good sample for speed now, power down our GPS as configured by user.
  487.     //         isAcquiringSpeed = NO;
  488.     //         [locationManager setDesiredAccuracy:desiredAccuracy];
  489.     //         [locationManager setDistanceFilter:[self calculateDistanceFilter:location.speed]];
  490.     //         [self startUpdatingLocation];
  491.     //     } else {
  492.     //         return;
  493.     //     }
  494.     // } else if (isMoving) {
  495.     //     // Adjust distanceFilter incrementally based upon current speed
  496.     //     float newDistanceFilter = [self calculateDistanceFilter:location.speed];
  497.     //     if (newDistanceFilter != locationManager.distanceFilter) {
  498.     //         NSLog(@"- CDVBackgroundGeoLocation updated distanceFilter, new: %f, old: %f", newDistanceFilter, locationManager.distanceFilter);
  499.     //         [locationManager setDistanceFilter:newDistanceFilter];
  500.     //         [self startUpdatingLocation];
  501.     //     }
  502.     // } else if ([self locationIsBeyondStationaryRegion:location]) {
  503.     //     if (isDebugging) {
  504.     //         [self notify:@"Manual stationary exit-detection"];
  505.     //     }
  506.     //     [self setPace:YES];
  507.     // }
  508.     // Jason-customization: END
  509.     [self queue:location type:@"current"];
  510. }
  511. /**
  512. * Manual stationary location his-testing.  This seems to help stationary-exit detection in some places where the automatic geo-fencing soesn't
  513. */
  514. -(bool)locationIsBeyondStationaryRegion:(CLLocation*)location
  515. {
  516.     NSLog(@"- CDVBackgroundGeoLocation locationIsBeyondStationaryRegion");
  517.     if (![stationaryRegion containsCoordinate:[location coordinate]]) {
  518.         double pointDistance = [stationaryLocation distanceFromLocation:location];
  519.         return (pointDistance - stationaryLocation.horizontalAccuracy - location.horizontalAccuracy) > stationaryRadius;
  520.     } else {
  521.         return NO;
  522.     }
  523. }
  524. /**
  525.  * Calculates distanceFilter by rounding speed to nearest 5 and multiplying by 10.  Clamped at 1km max.
  526.  */
  527. -(float) calculateDistanceFilter:(float)speed
  528. {
  529.     float newDistanceFilter = distanceFilter;
  530.     if (speed < 100) {
  531.         // (rounded-speed-to-nearest-5) / 2)^2
  532.         // eg 5.2 becomes (5/2)^2
  533.         newDistanceFilter = pow((5.0 * floorf(fabsf(speed) / 5.0 + 0.5f)), 2) + distanceFilter;
  534.     }
  535.     return (newDistanceFilter < 1000) ? newDistanceFilter : 1000;
  536. }
  537.  
  538. -(void) queue:(CLLocation*)location type:(id)type
  539. {
  540.     NSLog(@"- CDVBackgroundGeoLocation queue %@", type);
  541.     NSMutableDictionary *data = [self locationToHash:location];
  542.     [data setObject:type forKey:@"location_type"];
  543.     [locationQueue addObject:data];
  544.     [self flushQueue];
  545. }
  546.  
  547. -(UIBackgroundTaskIdentifier) createBackgroundTask
  548. {
  549.     lastBgTaskAt = [NSDate date];
  550.     return [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
  551.         [self stopBackgroundTask];
  552.     }];
  553. }
  554.  
  555. /**
  556.  * We are running in the background if this is being executed.
  557.  * We can't assume normal network access.
  558.  * bgTask is defined as an instance variable of type UIBackgroundTaskIdentifier
  559.  */
  560. -(void) sync:(NSMutableDictionary*)data
  561. {
  562.     NSLog(@"- CDVBackgroundGeoLocation#sync");
  563.     NSLog(@"  type: %@, position: %@,%@ speed: %@", [data objectForKey:@"location_type"], [data objectForKey:@"latitude"], [data objectForKey:@"longitude"], [data objectForKey:@"speed"]);
  564.     if (isDebugging) {
  565.         [self notify:[NSString stringWithFormat:@"Location update: %s\nSPD: %0.0f | DF: %ld | ACY: %0.0f",
  566.                       ((isMoving) ? "MOVING" : "STATIONARY"),
  567.                       [[data objectForKey:@"speed"] doubleValue],
  568.                       (long) locationManager.distanceFilter,
  569.                       [[data objectForKey:@"accuracy"] doubleValue]]];
  570.  
  571.         AudioServicesPlaySystemSound (locationSyncSound);
  572.     }
  573.  
  574.     // Build a resultset for javascript callback.
  575.     NSString *locationType = [data objectForKey:@"location_type"];
  576.     if ([locationType isEqualToString:@"stationary"]) {
  577.         [self fireStationaryRegionListeners:data];
  578.     } else if ([locationType isEqualToString:@"current"]) {
  579.         CDVPluginResult* result = nil;
  580.         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:data];
  581.         [result setKeepCallbackAsBool:YES];
  582.         [self.commandDelegate sendPluginResult:result callbackId:self.syncCallbackId];
  583.     } else {
  584.         NSLog(@"- CDVBackgroundGeoLocation#sync could not determine location_type.");
  585.         [self stopBackgroundTask];
  586.     }
  587. }
  588.  
  589. - (void) fireStationaryRegionListeners:(NSMutableDictionary*)data
  590. {
  591.     NSLog(@"- CDVBackgroundGeoLocation#fireStationaryRegionListener");
  592.     if (![self.stationaryRegionListeners count]) {
  593.         [self stopBackgroundTask];
  594.         return;
  595.     }
  596.     // Any javascript stationaryRegion event-listeners?
  597.     [data setObject:[NSNumber numberWithDouble:stationaryRadius] forKey:@"radius"];
  598.  
  599.     CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:data];
  600.     [result setKeepCallbackAsBool:YES];
  601.     for (NSString *callbackId in self.stationaryRegionListeners)
  602.     {
  603.         [self.commandDelegate sendPluginResult:result callbackId:callbackId];
  604.     }
  605. }
  606.  
  607. /**
  608.  * Creates a new circle around user and region-monitors it for exit
  609.  */
  610. - (void) startMonitoringStationaryRegion:(CLLocation*)location {
  611.     stationaryLocation = location;
  612.  
  613.     // fire onStationary @event for Javascript.
  614.     [self queue:location type:@"stationary"];
  615.  
  616.     CLLocationCoordinate2D coord = [location coordinate];
  617.     NSLog(@"- CDVBackgroundGeoLocation createStationaryRegion (%f,%f)", coord.latitude, coord.longitude);
  618.  
  619.     if (isDebugging) {
  620.         AudioServicesPlaySystemSound (acquiredLocationSound);
  621.         [self notify:[NSString stringWithFormat:@"Acquired stationary location\n%f, %f", location.coordinate.latitude,location.coordinate.longitude]];
  622.     }
  623.     if (stationaryRegion != nil) {
  624.         [locationManager stopMonitoringForRegion:stationaryRegion];
  625.     }
  626.     isAcquiringStationaryLocation = NO;
  627.     stationaryRegion = [[CLCircularRegion alloc] initWithCenter: coord radius:stationaryRadius identifier:@"BackgroundGeoLocation stationary region"];
  628.     stationaryRegion.notifyOnExit = YES;
  629.     [locationManager startMonitoringForRegion:stationaryRegion];
  630.  
  631.     [self stopUpdatingLocation];
  632.     locationManager.distanceFilter = distanceFilter;
  633.     locationManager.desiredAccuracy = desiredAccuracy;
  634. }
  635.  
  636. - (bool) stationaryRegionContainsLocation:(CLLocation*)location {
  637.     CLCircularRegion *region = [locationManager.monitoredRegions member:stationaryRegion];
  638.     return ([region containsCoordinate:location.coordinate]) ? YES : NO;
  639. }
  640. - (void) stopBackgroundTask
  641. {
  642.     UIApplication *app = [UIApplication sharedApplication];
  643.     NSLog(@"- CDVBackgroundGeoLocation stopBackgroundTask (remaining t: %f)", app.backgroundTimeRemaining);
  644.     if (bgTask != UIBackgroundTaskInvalid)
  645.     {
  646.         [app endBackgroundTask:bgTask];
  647.         bgTask = UIBackgroundTaskInvalid;
  648.     }
  649.     [self flushQueue];
  650. }
  651. /**
  652.  * Called when user exits their stationary radius (ie: they walked ~50m away from their last recorded location.
  653.  * - turn on more aggressive location monitoring.
  654.  */
  655. - (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
  656. {
  657.     NSLog(@"- CDVBackgroundGeoLocation exit region");
  658.     if (isDebugging) {
  659.         AudioServicesPlaySystemSound (exitRegionSound);
  660.         [self notify:@"Exit stationary region"];
  661.     }
  662.     [self setPace:YES];
  663. }
  664.  
  665. /**
  666.  * 1. turn off std location services
  667.  * 2. turn on significantChanges API
  668.  * 3. create a region and start monitoring exits.
  669.  */
  670. - (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager
  671. {
  672.     NSLog(@"- CDVBackgroundGeoLocation paused location updates");
  673.     if (isDebugging) {
  674.         [self notify:@"Stop detected"];
  675.     }
  676.     if (locationError) {
  677.         isMoving = NO;
  678.         [self startMonitoringStationaryRegion:lastLocation];
  679.         [self stopUpdatingLocation];
  680.     } else {
  681.         [self setPace:NO];
  682.     }
  683. }
  684.  
  685. /**
  686.  * 1. Turn off significantChanges ApI
  687.  * 2. turn on std. location services
  688.  * 3. nullify stationaryRegion
  689.  */
  690. - (void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager
  691. {
  692.     NSLog(@"- CDVBackgroundGeoLocation resume location updates");
  693.     if (isDebugging) {
  694.         [self notify:@"Resume location updates"];
  695.     }
  696.     [self setPace:YES];
  697. }
  698.  
  699. - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
  700. {
  701.     NSLog(@"- CDVBackgroundGeoLocation locationManager failed:  %@", error);
  702.     if (isDebugging) {
  703.         AudioServicesPlaySystemSound (locationErrorSound);
  704.         [self notify:[NSString stringWithFormat:@"Location error: %@", error.localizedDescription]];
  705.     }
  706.  
  707.     locationError = error;
  708.  
  709.     switch(error.code) {
  710.         case kCLErrorLocationUnknown:
  711.         case kCLErrorNetwork:
  712.         case kCLErrorRegionMonitoringDenied:
  713.         case kCLErrorRegionMonitoringSetupDelayed:
  714.         case kCLErrorRegionMonitoringResponseDelayed:
  715.         case kCLErrorGeocodeFoundNoResult:
  716.         case kCLErrorGeocodeFoundPartialResult:
  717.         case kCLErrorGeocodeCanceled:
  718.             break;
  719.         case kCLErrorDenied:
  720.             [self stopUpdatingLocation];
  721.             break;
  722.         default:
  723.             [self stopUpdatingLocation];
  724.     }
  725. }
  726.  
  727. - (void) stopUpdatingLocation
  728. {
  729.     [locationManager stopUpdatingLocation];
  730.     isUpdatingLocation = NO;
  731. }
  732.  
  733. - (void) startUpdatingLocation
  734. {
  735.     SEL requestSelector = NSSelectorFromString(@"requestAlwaysAuthorization");
  736.     if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined && [locationManager respondsToSelector:requestSelector]) {
  737.         ((void (*)(id, SEL))[locationManager methodForSelector:requestSelector])(locationManager, requestSelector);
  738.         [locationManager startUpdatingLocation];
  739.         isUpdatingLocation = YES;
  740.     } else {
  741.         [locationManager startUpdatingLocation];
  742.         isUpdatingLocation = YES;
  743.     }
  744. }
  745. - (void) locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
  746. {
  747.     NSLog(@"- CDVBackgroundGeoLocation didChangeAuthorizationStatus %u", status);
  748.     if (isDebugging) {
  749.         [self notify:[NSString stringWithFormat:@"Authorization status changed %u", status]];
  750.     }
  751. }
  752.  
  753. - (NSTimeInterval) locationAge:(CLLocation*)location
  754. {
  755.     return -[location.timestamp timeIntervalSinceNow];
  756. }
  757.  
  758. - (void) notify:(NSString*)message
  759. {
  760.     localNotification.fireDate = [NSDate date];
  761.     localNotification.alertBody = message;
  762.     [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
  763. }
  764. /**
  765.  * If you don't stopMonitoring when application terminates, the app will be awoken still when a
  766.  * new location arrives, essentially monitoring the user's location even when they've killed the app.
  767.  * Might be desirable in certain apps.
  768.  */
  769. - (void)applicationWillTerminate:(UIApplication *)application {
  770.     [locationManager stopMonitoringSignificantLocationChanges];
  771.     [locationManager stopUpdatingLocation];
  772.     if (stationaryRegion != nil) {
  773.         [locationManager stopMonitoringForRegion:stationaryRegion];
  774.     }
  775. }
  776.  
  777. - (void)dealloc
  778. {
  779.     locationManager.delegate = nil;
  780. }
  781.  
  782. @end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement