summaryrefslogtreecommitdiff
path: root/platform/ios/src/MGLLocationManager.m
diff options
context:
space:
mode:
authorJesse Bounds <jesse@rebounds.net>2016-02-24 18:01:48 -0800
committerJesse Bounds <jesse@rebounds.net>2016-02-26 14:19:04 -0800
commit09c91008910746667b26d04eca679dfa5bb6b03b (patch)
tree710fead6e631c61f955887afbcfa47a282664d17 /platform/ios/src/MGLLocationManager.m
parentb3adb96e4a34123a337027f5d3ecc377e33397c3 (diff)
downloadqtlocation-mapboxgl-09c91008910746667b26d04eca679dfa5bb6b03b.tar.gz
Introduce MGLLocation Manager
https://github.com/mapbox/mapbox-gl-native/pull/4115 This change makes background data gathering more efficient by disabling standard location updates when the device has been stationary for at least five minutes. It also establishes region monitoring and significant location change monitoring so that if the device appears to be in motion again then background telemetry data collection can resume. All of this reduces the amount of time required for telemetry data collection to the time the device is in motion only. It also only applies to host apps that already run in the background and have the always location permission from their users. This also includes some changes to make the internal pause/resume API of the MGLMapboxEvents class less complex and autonomous. The side effects of the map view waking or sleeping are no longer required for mapbox events to work as intended.
Diffstat (limited to 'platform/ios/src/MGLLocationManager.m')
-rw-r--r--platform/ios/src/MGLLocationManager.m159
1 files changed, 159 insertions, 0 deletions
diff --git a/platform/ios/src/MGLLocationManager.m b/platform/ios/src/MGLLocationManager.m
new file mode 100644
index 0000000000..7a3f8de4ff
--- /dev/null
+++ b/platform/ios/src/MGLLocationManager.m
@@ -0,0 +1,159 @@
+#import "MGLLocationManager.h"
+#import <UIKit/UIKit.h>
+
+static const NSTimeInterval fiveMinuteTimeInterval = 300.0;
+static const NSTimeInterval fiveSecondTimeInterval = 5.0;
+static const CLLocationDistance regionRadiusLocationDistance = 300.0;
+static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManagerRegionIdentifier.fence.center";
+
+@interface MGLLocationManager ()
+
+@property (nonatomic) CLLocationManager *standardLocationManager;
+@property (nonatomic) BOOL hostAppHasBackgroundCapability;
+@property (nonatomic, getter=isUpdatingLocation) BOOL updatingLocation;
+@property (nonatomic) NSDate *backgroundLocationServiceTimeoutAllowedDate;
+@property (nonatomic) NSTimer *backgroundLocationServiceTimeoutTimer;
+
+@end
+
+@implementation MGLLocationManager
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+ _hostAppHasBackgroundCapability = [backgroundModes containsObject:@"location"];
+ }
+ return self;
+}
+
+- (void)startUpdatingLocation {
+ if ([self isUpdatingLocation]) {
+ return;
+ }
+
+ [self configurePassiveStandardLocationManager];
+ [self startLocationServices];
+}
+
+- (void)stopUpdatingLocation {
+ if ([self isUpdatingLocation]) {
+ [self.standardLocationManager stopUpdatingLocation];
+ [self.standardLocationManager stopMonitoringSignificantLocationChanges];
+ self.updatingLocation = NO;
+ if ([self.delegate respondsToSelector:@selector(locationManagerDidStopLocationUpdates:)]) {
+ [self.delegate locationManagerDidStopLocationUpdates:self];
+ }
+ }
+}
+
+#pragma mark - Utilities
+
+- (void)configurePassiveStandardLocationManager {
+ if (!self.standardLocationManager) {
+ CLLocationManager *standardLocationManager = [[CLLocationManager alloc] init];
+ standardLocationManager.delegate = self;
+ standardLocationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
+ standardLocationManager.distanceFilter = 1;
+ self.standardLocationManager = standardLocationManager;
+ }
+}
+
+- (void)startLocationServices {
+ if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized ||
+ [CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse) {
+
+ // If the host app can run in the background with `always` location permissions then allow background
+ // updates and start the significant location change service and background timeout timer
+ if (self.hostAppHasBackgroundCapability && [CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorized) {
+ [self.standardLocationManager startMonitoringSignificantLocationChanges];
+ [self startBackgroundTimeoutTimer];
+ // On iOS 9 and above also allow background location updates
+ if ([self.standardLocationManager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) {
+ self.standardLocationManager.allowsBackgroundLocationUpdates = YES;
+ }
+ }
+
+ [self.standardLocationManager startUpdatingLocation];
+ self.updatingLocation = YES;
+ if ([self.delegate respondsToSelector:@selector(locationManagerDidStartLocationUpdates:)]) {
+ [self.delegate locationManagerDidStartLocationUpdates:self];
+ }
+ }
+}
+
+- (void)timeoutAllowedCheck {
+ if (self.backgroundLocationServiceTimeoutAllowedDate == nil) {
+ return;
+ }
+
+ if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive ||
+ [UIApplication sharedApplication].applicationState == UIApplicationStateInactive ) {
+ [self startBackgroundTimeoutTimer];
+ return;
+ }
+
+ NSTimeInterval timeIntervalSinceTimeoutAllowed = [[NSDate date] timeIntervalSinceDate:self.backgroundLocationServiceTimeoutAllowedDate];
+ if (timeIntervalSinceTimeoutAllowed > 0) {
+ [self.standardLocationManager stopUpdatingLocation];
+ self.backgroundLocationServiceTimeoutAllowedDate = nil;
+ if ([self.delegate respondsToSelector:@selector(locationManagerBackgroundLocationUpdatesDidTimeout:)]) {
+ [self.delegate locationManagerBackgroundLocationUpdatesDidTimeout:self];
+ }
+ }
+}
+
+- (void)startBackgroundTimeoutTimer {
+ [self.backgroundLocationServiceTimeoutTimer invalidate];
+ self.backgroundLocationServiceTimeoutAllowedDate = [[NSDate date] dateByAddingTimeInterval:fiveMinuteTimeInterval];
+ self.backgroundLocationServiceTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:fiveSecondTimeInterval target:self selector:@selector(timeoutAllowedCheck) userInfo:nil repeats:YES];
+}
+
+- (void)establishRegionMonitoringForLocation:(CLLocation *)location {
+ CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:regionRadiusLocationDistance identifier:MGLLocationManagerRegionIdentifier];
+ region.notifyOnEntry = NO;
+ region.notifyOnExit = YES;
+ [self.standardLocationManager startMonitoringForRegion:region];
+}
+
+#pragma mark - CLLocationManagerDelegate
+
+- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
+ switch (status) {
+ case kCLAuthorizationStatusAuthorized: // Also handles kCLAuthorizationStatusAuthorizedAlways
+ case kCLAuthorizationStatusAuthorizedWhenInUse:
+ [self startUpdatingLocation];
+ break;
+ default:
+ [self stopUpdatingLocation];
+ break;
+ }
+}
+
+- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
+ CLLocation *location = locations.lastObject;
+ if (location.speed > 0.0) {
+ [self startBackgroundTimeoutTimer];
+ }
+ if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < regionRadiusLocationDistance) {
+ [self establishRegionMonitoringForLocation:location];
+ }
+ if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) {
+ [self.delegate locationManager:self didUpdateLocations:locations];
+ }
+}
+
+- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
+ [self startBackgroundTimeoutTimer];
+ [self.standardLocationManager startUpdatingLocation];
+}
+
+- (void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager {
+ if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
+ if ([self.delegate respondsToSelector:@selector(locationManagerBackgroundLocationUpdatesDidAutomaticallyPause:)]) {
+ [self.delegate locationManagerBackgroundLocationUpdatesDidAutomaticallyPause:self];
+ }
+ }
+}
+
+@end