diff --git a/platform/ios/src/MGLAPIClient.m b/platform/ios/src/MGLAPIClient.m index b4f85b563..58f1c70bb 100644 --- a/platform/ios/src/MGLAPIClient.m +++ b/platform/ios/src/MGLAPIClient.m @@ -2,8 +2,10 @@ #import "NSBundle+MGLAdditions.h" #import "MGLAccountManager.h" -static NSString * const MGLAPIClientUserAgent = @"MapboxEventsiOS/1.1"; -static NSString * const MGLAPIClientBaseURL = @"https://api.tiles.mapbox.com"; +static NSString * const MGLAPIClientUserAgentBase = @"MapboxEventsiOS"; +static NSString * const MGLAPIClientBaseURL = @"https://api.mapbox.com"; +static NSString * const MGLAPIClientEventsPath = @"events/v2"; +static NSString * const MGLAPIClientDomain = @"com.mapbox.mglnative"; static NSString * const MGLAPIClientHeaderFieldUserAgentKey = @"User-Agent"; static NSString * const MGLAPIClientHeaderFieldContentTypeKey = @"Content-Type"; @@ -41,13 +43,24 @@ static NSString * const MGLAPIClientHTTPMethodPost = @"POST"; #pragma mark Public API - (void)postEvents:(nonnull NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler { - NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self requestForEvents:events] - completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { - [self.dataTasks removeObject:dataTask]; - if (completionHandler) { - completionHandler(error); - } - }]; + __block NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self requestForEvents:events] + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + NSError *statusError = nil; + if (httpResponse.statusCode >= 400) { + NSString *description = [NSString stringWithFormat:@"The session data task failed. Original request was: %@", dataTask.originalRequest]; + NSString *reason = [NSString stringWithFormat:@"The status code was %@", @(httpResponse.statusCode)]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedString(description, nil), + NSLocalizedFailureReasonErrorKey: NSLocalizedString(reason, nil)}; + statusError = [[NSError alloc] initWithDomain:MGLAPIClientDomain code:1 userInfo:userInfo]; + } + if (completionHandler) { + error = error ? error : statusError; + completionHandler(error); + } + [self.dataTasks removeObject:dataTask]; + dataTask = nil; + }]; [dataTask resume]; [self.dataTasks addObject:dataTask]; } @@ -64,7 +77,7 @@ static NSString * const MGLAPIClientHTTPMethodPost = @"POST"; #pragma mark Utilities - (NSURLRequest *)requestForEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events { - NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", self.baseURL, [MGLAccountManager accessToken]]; + NSString *url = [NSString stringWithFormat:@"%@/%@?access_token=%@", self.baseURL, MGLAPIClientEventsPath, [MGLAccountManager accessToken]]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; [request setValue:self.userAgent forHTTPHeaderField:MGLAPIClientHeaderFieldUserAgentKey]; [request setValue:MGLAPIClientHeaderFieldContentTypeValue forHTTPHeaderField:MGLAPIClientHeaderFieldContentTypeKey]; @@ -106,7 +119,10 @@ static NSString * const MGLAPIClientHTTPMethodPost = @"POST"; NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; NSString *appBuildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; - _userAgent = [NSString stringWithFormat:@"%@/%@/%@ %@", appName, appVersion, appBuildNumber, MGLAPIClientUserAgent]; + NSString *semanticVersion = [NSBundle mgl_frameworkBundle].infoDictionary[@"MGLSemanticVersionString"]; + NSString *shortVersion = [NSBundle mgl_frameworkBundle].infoDictionary[@"CFBundleShortVersionString"]; + NSString *sdkVersion = semanticVersion ? semanticVersion : shortVersion; + _userAgent = [NSString stringWithFormat:@"%@/%@/%@ %@/%@", appName, appVersion, appBuildNumber, MGLAPIClientUserAgentBase, sdkVersion]; } #pragma mark - JSON Serialization @@ -118,7 +134,6 @@ static NSString * const MGLAPIClientHTTPMethodPost = @"POST"; #pragma mark NSURLSessionDelegate - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { - if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index ccf8a7bc3..158228ca2 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -450,16 +450,7 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) _pendingLongitude = NAN; _targetCoordinate = kCLLocationCoordinate2DInvalid; - // metrics: map load event - mbgl::LatLng latLng = _mbglMap->getLatLng(padding); - int zoom = round(_mbglMap->getZoom()); - - [MGLMapboxEvents pushEvent:MGLEventTypeMapLoad withAttributes:@{ - MGLEventKeyLatitude: @(latLng.latitude), - MGLEventKeyLongitude: @(latLng.longitude), - MGLEventKeyZoomLevel: @(zoom), - MGLEventKeyPushEnabled: @([MGLMapboxEvents checkPushEnabled]) - }]; + [MGLMapboxEvents pushEvent:MGLEventTypeMapLoad withAttributes:@{}]; } - (void)createGLView @@ -1321,12 +1312,9 @@ mbgl::Duration MGLDurationInSeconds(NSTimeInterval duration) _mbglMap->cancelTransitions(); - if (doubleTap.state == UIGestureRecognizerStateBegan) + if (doubleTap.state == UIGestureRecognizerStateEnded) { [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap]; - } - else if (doubleTap.state == UIGestureRecognizerStateEnded) - { CGPoint gesturePoint = [doubleTap locationInView:doubleTap.view]; if (self.userTrackingMode != MGLUserTrackingModeNone) { diff --git a/platform/ios/src/MGLMapboxEvents.h b/platform/ios/src/MGLMapboxEvents.h index 9d90bd967..98f59ffd3 100644 --- a/platform/ios/src/MGLMapboxEvents.h +++ b/platform/ios/src/MGLMapboxEvents.h @@ -4,29 +4,20 @@ NS_ASSUME_NONNULL_BEGIN +// Event types extern NSString *const MGLEventTypeAppUserTurnstile; extern NSString *const MGLEventTypeMapLoad; extern NSString *const MGLEventTypeMapTap; extern NSString *const MGLEventTypeMapDragEnd; extern NSString *const MGLEventTypeLocation; -extern NSString *const MGLEventTypeVisit; -extern NSString *const MGLEventTypeLocalDebug; +// Event keys extern NSString *const MGLEventKeyLatitude; extern NSString *const MGLEventKeyLongitude; extern NSString *const MGLEventKeyZoomLevel; -extern NSString *const MGLEventKeySpeed; -extern NSString *const MGLEventKeyCourse; -extern NSString *const MGLEventKeyAltitude; -extern NSString *const MGLEventKeyHorizontalAccuracy; -extern NSString *const MGLEventKeyVerticalAccuracy; -extern NSString *const MGLEventKeyPushEnabled; -extern NSString *const MGLEventKeyEmailEnabled; extern NSString *const MGLEventKeyGestureID; -extern NSString *const MGLEventKeyArrivalDate; -extern NSString *const MGLEventKeyDepartureDate; -extern NSString *const MGLEventKeyLocalDebugDescription; +// Gestures extern NSString *const MGLEventGestureSingleTap; extern NSString *const MGLEventGestureDoubleTap; extern NSString *const MGLEventGestureTwoFingerSingleTap; @@ -48,7 +39,6 @@ typedef NS_MUTABLE_DICTIONARY_OF(NSString *, id) MGLMutableMapboxEventAttributes + (void)pushEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary; + (void)ensureMetricsOptoutExists; + (void)flush; -+ (BOOL)checkPushEnabled; @end diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m index 9097c5b10..35f6327fc 100644 --- a/platform/ios/src/MGLMapboxEvents.m +++ b/platform/ios/src/MGLMapboxEvents.m @@ -11,31 +11,15 @@ #include #include -static const NSUInteger version = 1; - +// Event types NSString *const MGLEventTypeAppUserTurnstile = @"appUserTurnstile"; NSString *const MGLEventTypeMapLoad = @"map.load"; NSString *const MGLEventTypeMapTap = @"map.click"; NSString *const MGLEventTypeMapDragEnd = @"map.dragend"; NSString *const MGLEventTypeLocation = @"location"; -NSString *const MGLEventTypeVisit = @"visit"; NSString *const MGLEventTypeLocalDebug = @"debug"; -NSString *const MGLEventKeyLatitude = @"lat"; -NSString *const MGLEventKeyLongitude = @"lng"; -NSString *const MGLEventKeyZoomLevel = @"zoom"; -NSString *const MGLEventKeySpeed = @"speed"; -NSString *const MGLEventKeyCourse = @"course"; -NSString *const MGLEventKeyAltitude = @"altitude"; -NSString *const MGLEventKeyHorizontalAccuracy = @"horizontalAccuracy"; -NSString *const MGLEventKeyVerticalAccuracy = @"verticalAccuracy"; -NSString *const MGLEventKeyPushEnabled = @"enabled.push"; -NSString *const MGLEventKeyEmailEnabled = @"enabled.email"; -NSString *const MGLEventKeyGestureID = @"gesture"; -NSString *const MGLEventKeyArrivalDate = @"arrivalDate"; -NSString *const MGLEventKeyDepartureDate = @"departureDate"; -NSString *const MGLEventKeyLocalDebugDescription = @"debug.description"; - +// Gestures NSString *const MGLEventGestureSingleTap = @"SingleTap"; NSString *const MGLEventGestureDoubleTap = @"DoubleTap"; NSString *const MGLEventGestureTwoFingerSingleTap = @"TwoFingerTap"; @@ -45,15 +29,49 @@ NSString *const MGLEventGesturePinchStart = @"Pinch"; NSString *const MGLEventGestureRotateStart = @"Rotation"; NSString *const MGLEventGesturePitchStart = @"Pitch"; -const NSUInteger MGLMaximumEventsPerFlush = 20; -const NSTimeInterval MGLFlushInterval = 60; +// Event keys +NSString *const MGLEventKeyLatitude = @"lat"; +NSString *const MGLEventKeyLongitude = @"lng"; +NSString *const MGLEventKeyZoomLevel = @"zoom"; +NSString *const MGLEventKeySpeed = @"speed"; +NSString *const MGLEventKeyCourse = @"course"; +NSString *const MGLEventKeyGestureID = @"gesture"; +NSString *const MGLEventKeyLocalDebugDescription = @"debug.description"; + +static NSString *const MGLEventKeyEvent = @"event"; +static NSString *const MGLEventKeyCreated = @"created"; +static NSString *const MGLEventKeyVendorID = @"userId"; +static NSString *const MGLEventKeyModel = @"model"; +static NSString *const MGLEventKeyEnabledTelemetry = @"enabled.telemetry"; +static NSString *const MGLEventKeyOperatingSystem = @"operatingSystem"; +static NSString *const MGLEventKeyResolution = @"resolution"; +static NSString *const MGLEventKeyAccessibilityFontScale = @"accessibilityFontScale"; +static NSString *const MGLEventKeyOrientation = @"orientation"; +static NSString *const MGLEventKeyBatteryLevel = @"batteryLevel"; +static NSString *const MGLEventKeyPluggedIn = @"pluggedIn"; +static NSString *const MGLEventKeyWifi = @"wifi"; +static NSString *const MGLEventKeySource = @"source"; +static NSString *const MGLEventKeySessionId = @"sessionId"; +static NSString *const MGLEventKeyApplicationState = @"applicationState"; +static NSString *const MGLEventKeyAltitude = @"altitude"; + +// SDK event source +static NSString *const MGLEventSource = @"mapbox"; + +// Event application state +static NSString *const MGLApplicationStateForeground = @"Foreground"; +static NSString *const MGLApplicationStateBackground = @"Background"; +static NSString *const MGLApplicationStateInactive = @"Inactive"; +static NSString *const MGLApplicationStateUnknown = @"Unknown"; + +const NSUInteger MGLMaximumEventsPerFlush = 180; +const NSTimeInterval MGLFlushInterval = 180; @interface MGLMapboxEventsData : NSObject @property (nonatomic) NSString *vendorId; @property (nonatomic) NSString *model; @property (nonatomic) NSString *iOSVersion; -@property (nonatomic) NSString *carrier; @property (nonatomic) CGFloat scale; @end @@ -63,7 +81,6 @@ const NSTimeInterval MGLFlushInterval = 60; - (instancetype)init { if (self = [super init]) { _vendorId = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; - _model = [self sysInfoByName:"hw.machine"]; _iOSVersion = [NSString stringWithFormat:@"%@ %@", [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion]; if ([UIScreen instancesRespondToSelector:@selector(nativeScale)]) { @@ -94,7 +111,7 @@ const NSTimeInterval MGLFlushInterval = 60; @property (nonatomic) MGLMapboxEventsData *data; @property (nonatomic, copy) NSString *appBundleId; -@property (nonatomic, copy) NSString *instanceID; +@property (nonatomic, readonly) NSString *instanceID; @property (nonatomic, copy) NSString *dateForDebugLogFile; @property (nonatomic) NSDateFormatter *rfc3339DateFormatter; @property (nonatomic) MGLAPIClient *apiClient; @@ -106,11 +123,13 @@ const NSTimeInterval MGLFlushInterval = 60; @property (nonatomic) dispatch_queue_t debugLogSerialQueue; @property (nonatomic) MGLLocationManager *locationManager; @property (nonatomic) NSTimer *timer; +@property (nonatomic) NSDate *lastInstanceIDRotationDate; @end @implementation MGLMapboxEvents { id _userDefaultsObserver; + NSString *_instanceID; } + (void)initialize { @@ -139,7 +158,6 @@ const NSTimeInterval MGLFlushInterval = 60; self = [super init]; if (self) { _appBundleId = [[NSBundle mainBundle] bundleIdentifier]; - _instanceID = [[NSUUID UUID] UUIDString]; _apiClient = [[MGLAPIClient alloc] init]; NSString *uniqueID = [[NSProcessInfo processInfo] globallyUniqueString]; @@ -216,6 +234,18 @@ const NSTimeInterval MGLFlushInterval = 60; [self pauseMetricsCollection]; } +- (NSString *)instanceID { + if (self.lastInstanceIDRotationDate && [[NSDate date] timeIntervalSinceDate:self.lastInstanceIDRotationDate] >= 0) { + _instanceID = nil; + } + if (!_instanceID) { + _instanceID = [[NSUUID UUID] UUIDString]; + NSTimeInterval twentyFourHourTimeInterval = 24 * 3600; + self.lastInstanceIDRotationDate = [[NSDate date] dateByAddingTimeInterval:twentyFourHourTimeInterval]; + } + return _instanceID; +} + - (void)pauseOrResumeMetricsCollectionIfRequired { // Prevent blue status bar when host app has `when in use` permission only and it is not in foreground if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse && @@ -259,89 +289,6 @@ const NSTimeInterval MGLFlushInterval = 60; [self.locationManager startUpdatingLocation]; } -+ (void)pushEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary { - [[MGLMapboxEvents sharedManager] pushEvent:event withAttributes:attributeDictionary]; -} - -- (void)pushEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary { - if (!event) { - return; - } - - if ([event isEqualToString:MGLEventTypeMapLoad]) { - [self pushTurnstileEvent]; - } - - if ([self isPaused]) { - return; - } - - NSDictionary *stockAttributes = @{@"event": event, - @"version": @(version), - @"created": [self.rfc3339DateFormatter stringFromDate:[NSDate date]], - @"instance": self.instanceID, - @"vendorId": self.data.vendorId, - @"appBundleId": self.appBundleId, - @"model": self.data.model, - @"operatingSystem": self.data.iOSVersion, - @"resolution": @(self.data.scale), - @"wifi": @([[MGLReachability reachabilityForLocalWiFi] isReachableViaWiFi]), - @"orientation": [self deviceOrientation], - @"batteryLevel": @([self batteryLevel]), - @"applicationState": [self applicationState], - @"accessibilityFontScale": @([self contentSizeScale])}; - MGLMutableMapboxEventAttributes *allEventAttributes = [NSMutableDictionary dictionaryWithDictionary:attributeDictionary]; - [allEventAttributes addEntriesFromDictionary:stockAttributes]; - MGLMapboxEventAttributes *finalEvent = [allEventAttributes copy]; - [self.eventQueue addObject:finalEvent]; - [self writeEventToLocalDebugLog:finalEvent]; - - // Has Flush Limit Been Reached? - if (self.eventQueue.count >= MGLMaximumEventsPerFlush) { - [self flush]; - } else if (self.eventQueue.count == 1) { - // If this is first new event on queue start timer, - [self startTimer]; - } -} - -- (void)pushTurnstileEvent { - __weak MGLMapboxEvents *weakSelf = self; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - MGLMapboxEvents *strongSelf = weakSelf; - - if (!strongSelf) { - return; - } - - NSString *vendorID = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; - if (!vendorID) { - return; - } - - NSDictionary *turnstileEventAttributes = @{@"event" : MGLEventTypeAppUserTurnstile, - @"created" : [strongSelf.rfc3339DateFormatter stringFromDate:[NSDate date]], - @"appBundleId" : strongSelf.appBundleId, - @"vendorId": vendorID, - @"version": @(version), - @"instance": strongSelf.instanceID}; - - if ([MGLAccountManager accessToken] == nil) { - return; - } - [strongSelf.apiClient postEvent:turnstileEventAttributes completionHandler:^(NSError * _Nullable error) { - if (error) { - [strongSelf pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription: @"Network error", - @"error": error}]; - return; - } - [strongSelf writeEventToLocalDebugLog:turnstileEventAttributes]; - }]; - }); -} - + (void)flush { [[MGLMapboxEvents sharedManager] flush]; } @@ -364,10 +311,145 @@ const NSTimeInterval MGLFlushInterval = 60; [self.timer invalidate]; self.timer = nil; } - + [self pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription:@"flush"}]; } +- (void)pushTurnstileEvent { + __weak MGLMapboxEvents *weakSelf = self; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + MGLMapboxEvents *strongSelf = weakSelf; + + if (!strongSelf) { + return; + } + + NSString *vendorID = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; + if (!vendorID) { + return; + } + + NSDictionary *turnstileEventAttributes = @{MGLEventKeyEvent: MGLEventTypeAppUserTurnstile, + MGLEventKeyCreated: [strongSelf.rfc3339DateFormatter stringFromDate:[NSDate date]], + MGLEventKeyVendorID: vendorID, + MGLEventKeyEnabledTelemetry: @([[strongSelf class] isEnabled])}; + + if ([MGLAccountManager accessToken] == nil) { + return; + } + [strongSelf.apiClient postEvent:turnstileEventAttributes completionHandler:^(NSError * _Nullable error) { + if (error) { + [strongSelf pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription: @"Network error", + @"error": error}]; + return; + } + [strongSelf writeEventToLocalDebugLog:turnstileEventAttributes]; + }]; + }); +} + ++ (void)pushEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary { + [[MGLMapboxEvents sharedManager] pushEvent:event withAttributes:attributeDictionary]; +} + +- (void)pushEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary { + if (!event) { + return; + } + + if ([event isEqualToString:MGLEventTypeMapLoad]) { + [self pushTurnstileEvent]; + } + + if ([self isPaused]) { + return; + } + + MGLMapboxEventAttributes *fullyFormedEvent = [self fullyFormedEventForEvent:event withAttributes:attributeDictionary]; + if (fullyFormedEvent) { + [self.eventQueue addObject:fullyFormedEvent]; + [self writeEventToLocalDebugLog:fullyFormedEvent]; + // Has Flush Limit Been Reached? + if (self.eventQueue.count >= MGLMaximumEventsPerFlush) { + [self flush]; + } else if (self.eventQueue.count == 1) { + // If this is first new event on queue start timer, + [self startTimer]; + } + } else { + [self pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription: @"Unknown event", + @"eventName": event, + @"event.attributes": attributeDictionary}]; + } +} + +#pragma mark Events + +- (MGLMapboxEventAttributes *)fullyFormedEventForEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary { + if ([event isEqualToString:MGLEventTypeMapLoad]) { + return [self mapLoadEventWithAttributes:attributeDictionary]; + } else if ([event isEqualToString:MGLEventTypeMapTap]) { + return [self mapClickEventWithAttributes:attributeDictionary]; + } else if ([event isEqualToString:MGLEventTypeMapDragEnd]) { + return [self mapDragEndEventWithAttributes:attributeDictionary]; + } else if ([event isEqualToString:MGLEventTypeLocation]) { + return [self locationEventWithAttributes:attributeDictionary]; + } + return nil; +} + +- (MGLMapboxEventAttributes *)locationEventWithAttributes:(MGLMapboxEventAttributes *)attributeDictionary { + MGLMutableMapboxEventAttributes *attributes = [@{MGLEventKeyEvent: MGLEventTypeLocation, + MGLEventKeySource: MGLEventSource, + MGLEventKeySessionId: self.instanceID, + MGLEventKeyOperatingSystem: self.data.iOSVersion} mutableCopy]; + [self addApplicationStateToAttributes:attributes]; + return [self eventForAttributes:attributes attributeDictionary:attributeDictionary]; +} + +- (MGLMapboxEventAttributes *)mapLoadEventWithAttributes:(MGLMapboxEventAttributes *)attributeDictionary { + MGLMutableMapboxEventAttributes *attributes = [@{MGLEventKeyEvent: MGLEventTypeMapLoad, + MGLEventKeyCreated: [self.rfc3339DateFormatter stringFromDate:[NSDate date]], + MGLEventKeyVendorID: self.data.vendorId, + MGLEventKeyModel: self.data.model, + MGLEventKeyOperatingSystem: self.data.iOSVersion, + MGLEventKeyResolution: @(self.data.scale), + MGLEventKeyAccessibilityFontScale: @([self contentSizeScale]), + MGLEventKeyOrientation: [self deviceOrientation], + MGLEventKeyBatteryLevel: @([self batteryLevel]), + MGLEventKeyWifi: @([[MGLReachability reachabilityForLocalWiFi] isReachableViaWiFi])} mutableCopy]; + [self addBatteryStateToAttributes:attributes]; + return [self eventForAttributes:attributes attributeDictionary:attributeDictionary]; +} + +- (MGLMapboxEventAttributes *)mapClickEventWithAttributes:(MGLMapboxEventAttributes *)attributeDictionary { + MGLMutableMapboxEventAttributes *attributes = [self interactionEvent]; + attributes[MGLEventKeyEvent] = MGLEventTypeMapTap; + return [self eventForAttributes:attributes attributeDictionary:attributeDictionary]; +} + +- (MGLMapboxEventAttributes *)mapDragEndEventWithAttributes:(MGLMapboxEventAttributes *)attributeDictionary { + MGLMutableMapboxEventAttributes *attributes = [self interactionEvent]; + attributes[MGLEventKeyEvent] = MGLEventTypeMapDragEnd; + return [self eventForAttributes:attributes attributeDictionary:attributeDictionary]; +} + +- (MGLMutableMapboxEventAttributes *)interactionEvent { + MGLMutableMapboxEventAttributes *attributes = [@{MGLEventKeyCreated: [self.rfc3339DateFormatter stringFromDate:[NSDate date]], + MGLEventKeyOrientation: [self deviceOrientation], + MGLEventKeyBatteryLevel: @([self batteryLevel]), + MGLEventKeyWifi: @([[MGLReachability reachabilityForLocalWiFi] isReachableViaWiFi])} mutableCopy]; + [self addBatteryStateToAttributes:attributes]; + return attributes; +} + +- (MGLMapboxEventAttributes *)eventForAttributes:(MGLMutableMapboxEventAttributes *)attributes attributeDictionary:(MGLMapboxEventAttributes *)attributeDictionary { + [attributes addEntriesFromDictionary:attributeDictionary]; + return [attributes copy]; +} + // Called implicitly from public use of +flush. // - (void)postEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events { @@ -400,7 +482,7 @@ const NSTimeInterval MGLFlushInterval = 60; } - (NSInteger)batteryLevel { - return [[NSNumber numberWithFloat:100 * [UIDevice currentDevice].batteryLevel] integerValue]; + return [[NSNumber numberWithFloat:roundf(100 * [UIDevice currentDevice].batteryLevel)] integerValue]; } - (NSString *)deviceOrientation { @@ -437,24 +519,16 @@ const NSTimeInterval MGLFlushInterval = 60; } - (NSString *)applicationState { - NSString *result; - switch ([UIApplication sharedApplication].applicationState) { case UIApplicationStateActive: - result = @"Active"; - break; + return MGLApplicationStateForeground; case UIApplicationStateInactive: - result = @"Inactive"; - break; + return MGLApplicationStateInactive; case UIApplicationStateBackground: - result = @"Background"; - break; + return MGLApplicationStateBackground; default: - result = @"Default - Unknown"; - break; + return MGLApplicationStateUnknown; } - - return result; } - (NSInteger)contentSizeScale { @@ -491,20 +565,27 @@ const NSTimeInterval MGLFlushInterval = 60; return result; } -+ (BOOL)checkPushEnabled { - BOOL blockResult; - if ([[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) { - // iOS 8+ - blockResult = [[UIApplication sharedApplication] isRegisteredForRemoteNotifications]; - } else { - // iOS 7 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; - blockResult = (types == UIRemoteNotificationTypeNone) ? NO : YES; -#pragma clang diagnostic pop +- (void)addBatteryStateToAttributes:(MGLMutableMapboxEventAttributes *)attributes { + UIDeviceBatteryState batteryState = [[UIDevice currentDevice] batteryState]; + switch (batteryState) { + case UIDeviceBatteryStateCharging: + case UIDeviceBatteryStateFull: + attributes[MGLEventKeyPluggedIn] = @(YES); + break; + case UIDeviceBatteryStateUnplugged: + attributes[MGLEventKeyPluggedIn] = @(NO); + break; + default: + // do nothing + break; + } +} + +- (void)addApplicationStateToAttributes:(MGLMutableMapboxEventAttributes *)attributes { + NSString *currentApplicationState = [self applicationState]; + if (![currentApplicationState isEqualToString:MGLApplicationStateUnknown]) { + attributes[MGLEventKeyApplicationState] = currentApplicationState; } - return blockResult; } + (void)ensureMetricsOptoutExists { @@ -543,14 +624,14 @@ const NSTimeInterval MGLFlushInterval = 60; - (void)locationManager:(MGLLocationManager *)locationManager didUpdateLocations:(NSArray *)locations { for (CLLocation *loc in locations) { - [MGLMapboxEvents pushEvent:MGLEventTypeLocation - withAttributes:@{MGLEventKeyLatitude: @(loc.coordinate.latitude), - MGLEventKeyLongitude: @(loc.coordinate.longitude), - MGLEventKeySpeed: @(loc.speed), - MGLEventKeyCourse: @(loc.course), - MGLEventKeyAltitude: @(round(loc.altitude)), - MGLEventKeyHorizontalAccuracy: @(round(loc.horizontalAccuracy)), - MGLEventKeyVerticalAccuracy: @(round(loc.verticalAccuracy))}]; + double accuracy = 10000000; + double lat = floor(loc.coordinate.latitude * accuracy) / accuracy; + double lng = floor(loc.coordinate.longitude * accuracy) / accuracy; + NSString *formattedDate = [self.rfc3339DateFormatter stringFromDate:loc.timestamp]; + [MGLMapboxEvents pushEvent:MGLEventTypeLocation withAttributes:@{MGLEventKeyCreated: formattedDate, + MGLEventKeyLatitude: @(lat), + MGLEventKeyLongitude: @(lng), + MGLEventKeyAltitude: @(round(loc.altitude))}]; } } @@ -634,8 +715,7 @@ const NSTimeInterval MGLFlushInterval = 60; - (NSString *)stringForDebugEvent:(MGLMapboxEventAttributes *)event { // redact potentially sensitive location details from system console log - if ([event[@"event"] isEqualToString:MGLEventTypeLocation] || - [event[@"event"] isEqualToString:MGLEventTypeVisit]) { + if ([event[@"event"] isEqualToString:MGLEventTypeLocation]) { MGLMutableMapboxEventAttributes *evt = [MGLMutableMapboxEventAttributes dictionaryWithDictionary:event]; [evt setObject:@"" forKey:@"lat"]; [evt setObject:@"" forKey:@"lng"];