diff --git a/CHANGELOG.md b/CHANGELOG.md index 95bb9fde62..2b8255968d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ * Fixed an issue with route callouts being slightly displaced. ([#4427](https://github.com/mapbox/mapbox-navigation-ios/pull/4427)) +### Other changes + +* Added support for Native Telemetry `Navigator.startNavigationSession()` and `Navigator.stopNavigationSession()` for correct report of navigation session type to telemetry. ([#4435](https://github.com/mapbox/mapbox-navigation-ios/pull/4435)) + ## v2.12.0 ### Packaging diff --git a/Cartfile.resolved b/Cartfile.resolved index 0606704201..d0d554a1c2 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,5 +1,5 @@ binary "https://api.mapbox.com/downloads/v2/carthage/mapbox-common/MapboxCommon.json" "23.4.0" -binary "https://api.mapbox.com/downloads/v2/carthage/mobile-navigation-native/MapboxNavigationNative.xcframework.json" "130.0.0" +binary "https://api.mapbox.com/downloads/v2/carthage/mobile-navigation-native/MapboxNavigationNative.xcframework.json" "131.0.0" github "mapbox/mapbox-directions-swift" "v2.11.0-rc.1" github "mapbox/mapbox-events-ios" "v1.0.10" github "mapbox/turf-swift" "v2.6.1" diff --git a/MapboxNavigation-SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MapboxNavigation-SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8f39bec726..989566f3f9 100644 --- a/MapboxNavigation-SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/MapboxNavigation-SPM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", "state": { "branch": null, - "revision": "35f9e770f54ce62dd8526470f14c6e137cef3eea", - "version": "2.1.1" + "revision": "3b123999de19bf04905bc1dfdb76f817b0f2cc00", + "version": "2.1.2" } }, { @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", "state": { "branch": null, - "revision": "c21f7bab5ca8eee0a9998bbd17ca1d0eb45d4688", - "version": "2.1.0" + "revision": "a23ded2c91df9156628a6996ab4f347526f17b6b", + "version": "2.1.2" } }, { diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index 40e5a65111..b621e1d5ed 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -104,6 +104,7 @@ 2C36D9022952256400EE37DF /* RouteProgressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C36D8FF2952256400EE37DF /* RouteProgressTests.swift */; }; 2C4093B5292661FD0099FA0E /* RouteParserSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4093B4292661FD0099FA0E /* RouteParserSpy.swift */; }; 2C484CDA2979AD6F00EAAE78 /* EventsMetadataProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C484CD92979AD6F00EAAE78 /* EventsMetadataProviderTests.swift */; }; + 2C4D0A2E29F6DC900063BF52 /* NavigationSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4D0A2D29F6DC900063BF52 /* NavigationSessionManager.swift */; }; 2C585157292521C20077A558 /* MapboxCoreNavigation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5ADFBC91DDCC7840011824B /* MapboxCoreNavigation.framework */; }; 2C58515F292523670077A558 /* RouteControllerIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C58515E292523670077A558 /* RouteControllerIntegrationTests.swift */; }; 2C585164292530AF0077A558 /* TestHelper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 35CDA85E2190F2A30072B675 /* TestHelper.framework */; }; @@ -151,6 +152,8 @@ 2CE89C2B299E6C2A000A2E34 /* InterchangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE89C2A299E6C2A000A2E34 /* InterchangeTests.swift */; }; 2CE89C2D299E6D09000A2E34 /* JunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE89C2C299E6D09000A2E34 /* JunctionTests.swift */; }; 2CE89C2F299E6E08000A2E34 /* RoadObjectKindTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE89C2E299E6E08000A2E34 /* RoadObjectKindTests.swift */; }; + 2CEC5C5629F71714001B5EC2 /* NavigationSessionManagerSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEC5C5529F71714001B5EC2 /* NavigationSessionManagerSpy.swift */; }; + 2CEC5C5829F71D9A001B5EC2 /* NavigationSessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEC5C5729F71D9A001B5EC2 /* NavigationSessionManagerTests.swift */; }; 2CF3F8CB2926D3E600BB2B9C /* URLCacheSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF3F8CA2926D3E600BB2B9C /* URLCacheSpy.swift */; }; 2CF3F8CD2926D5DE00BB2B9C /* BimodalImageCacheSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF3F8CC2926D5DE00BB2B9C /* BimodalImageCacheSpy.swift */; }; 2CF5F5CA298155EC00B34C8C /* NavigationNativeEventsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF5F5C82981500E00B34C8C /* NavigationNativeEventsManagerTests.swift */; }; @@ -803,6 +806,7 @@ 2C36D8FF2952256400EE37DF /* RouteProgressTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteProgressTests.swift; sourceTree = ""; }; 2C4093B4292661FD0099FA0E /* RouteParserSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteParserSpy.swift; sourceTree = ""; }; 2C484CD92979AD6F00EAAE78 /* EventsMetadataProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsMetadataProviderTests.swift; sourceTree = ""; }; + 2C4D0A2D29F6DC900063BF52 /* NavigationSessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationSessionManager.swift; sourceTree = ""; }; 2C585153292521C20077A558 /* MapboxCoreNavigationIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MapboxCoreNavigationIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 2C58515E292523670077A558 /* RouteControllerIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteControllerIntegrationTests.swift; sourceTree = ""; }; 2C58516229252FE20077A558 /* MapboxCoreNavigationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapboxCoreNavigationIntegrationTests.swift; sourceTree = ""; }; @@ -844,6 +848,8 @@ 2CE89C2A299E6C2A000A2E34 /* InterchangeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterchangeTests.swift; sourceTree = ""; }; 2CE89C2C299E6D09000A2E34 /* JunctionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JunctionTests.swift; sourceTree = ""; }; 2CE89C2E299E6E08000A2E34 /* RoadObjectKindTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoadObjectKindTests.swift; sourceTree = ""; }; + 2CEC5C5529F71714001B5EC2 /* NavigationSessionManagerSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationSessionManagerSpy.swift; sourceTree = ""; }; + 2CEC5C5729F71D9A001B5EC2 /* NavigationSessionManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationSessionManagerTests.swift; sourceTree = ""; }; 2CF3F8CA2926D3E600BB2B9C /* URLCacheSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLCacheSpy.swift; sourceTree = ""; }; 2CF3F8CC2926D5DE00BB2B9C /* BimodalImageCacheSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BimodalImageCacheSpy.swift; sourceTree = ""; }; 2CF5F5C82981500E00B34C8C /* NavigationNativeEventsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationNativeEventsManagerTests.swift; sourceTree = ""; }; @@ -1571,6 +1577,7 @@ 2C484CD82979AD5C00EAAE78 /* Telemetry */ = { isa = PBXGroup; children = ( + 2CEC5C5729F71D9A001B5EC2 /* NavigationSessionManagerTests.swift */, 2C856453297B1884006EFCBB /* NavigationCommonEventsManagerTests.swift */, 162039CE216C348500875F5C /* NavigationEventsManagerTests.swift */, 2C484CD92979AD6F00EAAE78 /* EventsMetadataProviderTests.swift */, @@ -1599,6 +1606,7 @@ 2C856447297AD64C006EFCBB /* Telemetry */ = { isa = PBXGroup; children = ( + 2C4D0A2D29F6DC900063BF52 /* NavigationSessionManager.swift */, 2C18A12D2988729C00750CEE /* EventStep.swift */, 2C18A12B29886F8C00750CEE /* EventFixLocation.swift */, 2C856450297AE0A6006EFCBB /* NavigationEventsManager.swift */, @@ -2221,6 +2229,7 @@ B47C1ACE261FD0A30078546C /* TestHelper */ = { isa = PBXGroup; children = ( + 2CEC5C5529F71714001B5EC2 /* NavigationSessionManagerSpy.swift */, 2CA61049297EF6D9003E49A7 /* System */, 2C2880A2291AB7240063E5B7 /* NavNative */, 2C9006C9291927850012CCEA /* DummyRoute.swift */, @@ -3487,6 +3496,7 @@ E23A76C62715E76A0098C23C /* ReplayLocationManager+TestSupport.swift in Sources */, B47C1B59261FD3B70078546C /* DummyLocationManager.swift in Sources */, B456A8EC2620D26B00FD86D8 /* LeakTest.swift in Sources */, + 2CEC5C5629F71714001B5EC2 /* NavigationSessionManagerSpy.swift in Sources */, B47C1B1E261FD3290078546C /* CoreLocation.swift in Sources */, E2F08C70269DB17C002EFDC5 /* AccessToken.swift in Sources */, 2C9006CA291927850012CCEA /* RoutingProviderSpy.swift in Sources */, @@ -3575,6 +3585,7 @@ 118D883826F8CA0700B2ED7B /* PassiveNavigationFeedbackType.swift in Sources */, 2BBED92F265E2C7D00F90032 /* NativeHandlersFactory.swift in Sources */, 4303A3992332CD6200B5737D /* UnimplementedLogging.swift in Sources */, + 2C4D0A2E29F6DC900063BF52 /* NavigationSessionManager.swift in Sources */, 2C295ACA299BF76000FC74E8 /* Junction.swift in Sources */, 2E6656F9264EC912009463EE /* Result+Expected.swift in Sources */, 8A7B69922947AA0F00FFC3F5 /* AmenityType.swift in Sources */, @@ -3657,6 +3668,7 @@ E23D8B6B269D7EE90094CEFA /* TilesetDescriptorFactoryTests.swift in Sources */, 2C36D9002952256400EE37DF /* RouteLegProgressTests.swift in Sources */, 2CA6105429802ACF003E49A7 /* MonitorConnectivityTypeProviderTests.swift in Sources */, + 2CEC5C5829F71D9A001B5EC2 /* NavigationSessionManagerTests.swift in Sources */, 2C36D9012952256400EE37DF /* RouteStepProgressTests.swift in Sources */, 359A8AED1FA78D3000BDB486 /* DistanceFormatterTests.swift in Sources */, 5A43FC8B24B488DC00BF7943 /* PassiveLocationManagerTests.swift in Sources */, diff --git a/Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift b/Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift index 8807338484..7f3e8a7fae 100644 --- a/Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift +++ b/Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift @@ -116,7 +116,7 @@ final class Navigator: CoreNavigator { completion(.failure(NavigatorError.failedToUpdateRoutes(reason: "Unexpected internal response"))) } } - }, alternativeRoutesSetupHandler: {[weak self] routes, completion in + }, alternativeRoutesSetupHandler: { [weak self] routes, completion in self?.navigator.setAlternativeRoutesForRoutes(routes, callback: { result in if result.isValue(), let alternatives = result.value as? [RouteAlternative] { @@ -152,7 +152,7 @@ final class Navigator: CoreNavigator { static var isSharedInstanceCreated: Bool { _navigator != nil } - + private static weak var _navigator: Navigator? /** @@ -195,6 +195,7 @@ final class Navigator: CoreNavigator { */ func restartNavigator(forcing version: String? = nil) { unsubscribeNavigator() + let previousNavigationSessionState = navigator.storeNavigationSession() navigator.shutdown() let factory = NativeHandlersFactory(tileStorePath: NavigationSettings.shared.tileStoreConfiguration.navigatorLocation.tileStoreURL?.path ?? "", @@ -211,7 +212,8 @@ final class Navigator: CoreNavigator { roadObjectStore.native = navigator.roadObjectStore() roadObjectMatcher.native = MapboxNavigationNative.RoadObjectMatcher(cache: cacheHandle) rerouteController = RerouteController(navigator, config: NativeHandlersFactory.configHandle()) - + + navigator.restoreNavigationSession(for: previousNavigationSessionState) subscribeNavigator() setupAlternativesControllerIfNeeded() } diff --git a/Sources/MapboxCoreNavigation/PassiveLocationManager.swift b/Sources/MapboxCoreNavigation/PassiveLocationManager.swift index 93738a427f..4b57e9b4d5 100644 --- a/Sources/MapboxCoreNavigation/PassiveLocationManager.swift +++ b/Sources/MapboxCoreNavigation/PassiveLocationManager.swift @@ -62,6 +62,8 @@ open class PassiveLocationManager: NSObject { }() private let navigatorType: CoreNavigator.Type + + private let navigationSessionManager: NavigationSessionManager /** The underlying navigator that performs map matching. @@ -260,10 +262,12 @@ open class PassiveLocationManager: NSObject { eventsManagerType: NavigationEventsManager.Type?, userInfo: [String: String?]?, datasetProfileIdentifier: ProfileIdentifier?, - navigatorType: CoreNavigator.Type) { + navigatorType: CoreNavigator.Type, + navigationSessionManager: NavigationSessionManager) { self.navigatorType = navigatorType self.directions = directions self.systemLocationManager = systemLocationManager + self.navigationSessionManager = navigationSessionManager super.init() @@ -294,6 +298,7 @@ open class PassiveLocationManager: NSObject { self.navigatorType = Navigator.self self.directions = directions self.systemLocationManager = systemLocationManager ?? NavigationLocationManager() + self.navigationSessionManager = NavigationSessionManagerImp.shared super.init() @@ -326,10 +331,12 @@ open class PassiveLocationManager: NSObject { subscribeNotifications() BillingHandler.shared.beginBillingSession(for: .freeDrive, uuid: sessionUUID) + navigationSessionManager.reportStartNavigation() } deinit { BillingHandler.shared.stopBillingSession(with: sessionUUID) + navigationSessionManager.reportStopNavigation() eventsManager.withBackupDataSource(active: nil, passive: self) { if self.rawLocation != nil { self.eventsManager.sendPassiveNavigationStop() diff --git a/Sources/MapboxCoreNavigation/RouteController.swift b/Sources/MapboxCoreNavigation/RouteController.swift index 617c9bdaab..b019e91074 100644 --- a/Sources/MapboxCoreNavigation/RouteController.swift +++ b/Sources/MapboxCoreNavigation/RouteController.swift @@ -242,6 +242,7 @@ open class RouteController: NSObject { private let navigatorType: CoreNavigator.Type private let routeParserType: RouteParser.Type + private let navigationSessionManager: NavigationSessionManager var navigator: MapboxNavigationNative.Navigator { return sharedNavigator.navigator @@ -369,6 +370,7 @@ open class RouteController: NSObject { public func finishRouting() { guard !hasFinishedRouting else { return } hasFinishedRouting = true + navigationSessionManager.reportStopNavigation() removeRoutes(completion: nil) BillingHandler.shared.stopBillingSession(with: sessionUUID) unsubscribeNotifications() @@ -658,6 +660,7 @@ open class RouteController: NSObject { self.navigatorType = Navigator.self self.routeParserType = RouteParser.self + self.navigationSessionManager = NavigationSessionManagerImp.shared self.indexedRouteResponse = indexedRouteResponse self.dataSource = source @@ -681,7 +684,8 @@ open class RouteController: NSObject { customRoutingProvider: RoutingProvider?, dataSource source: RouterDataSource, navigatorType: CoreNavigator.Type, - routeParserType: RouteParser.Type) { + routeParserType: RouteParser.Type, + navigationSessionManager: NavigationSessionManager) { Self.checkUniqueInstance() self.navigatorType = navigatorType @@ -690,6 +694,7 @@ open class RouteController: NSObject { self.dataSource = source let options = indexedRouteResponse.validatedRouteOptions self.routeProgress = RouteProgress(route: indexedRouteResponse.currentRoute!, options: options) + self.navigationSessionManager = navigationSessionManager super.init() @@ -714,6 +719,7 @@ open class RouteController: NSObject { fromLegIndex: 0, reason: .startNewRoute) { [weak self] _ in self?.isInitialized = true + self?.navigationSessionManager.reportStartNavigation() } } diff --git a/Sources/MapboxCoreNavigation/Telemetry/NavigationSessionManager.swift b/Sources/MapboxCoreNavigation/Telemetry/NavigationSessionManager.swift new file mode 100644 index 0000000000..18224a09de --- /dev/null +++ b/Sources/MapboxCoreNavigation/Telemetry/NavigationSessionManager.swift @@ -0,0 +1,54 @@ +import Foundation +import MapboxNavigationNative + +protocol NavigationSessionManager { + static var shared: Self { get } + + func reportStartNavigation() + func reportStopNavigation() +} + +final class NavigationSessionManagerImp: NavigationSessionManager { + private let lock: NSLock = .init() + + private var sessionCount: Int = 0 + + private let navigatorType: CoreNavigator.Type + + private var navigator: MapboxNavigationNative.Navigator { + return navigatorType.shared.navigator + } + + func reportStartNavigation() { + guard NavigationTelemetryConfiguration.useNavNativeTelemetryEvents else { return } + + var shouldStart = false + lock { + shouldStart = sessionCount == 0 + sessionCount += 1 + } + if shouldStart { + navigator.startNavigationSession() + } + } + + func reportStopNavigation() { + guard NavigationTelemetryConfiguration.useNavNativeTelemetryEvents else { return } + + var shouldStop = false + lock { + shouldStop = sessionCount == 1 + sessionCount = max(sessionCount - 1, 0) + } + if shouldStop { + navigator.stopNavigationSession() + } + } + + /// Shared manager instance. There is no other instances of `NavigationSessionManager`. + static let shared: NavigationSessionManagerImp = .init() + + init(navigatorType: CoreNavigator.Type = Navigator.self) { + self.navigatorType = navigatorType + } +} diff --git a/Sources/TestHelper/NavNative/NativeNavigatorSpy.swift b/Sources/TestHelper/NavNative/NativeNavigatorSpy.swift index ac9f3cdae3..e8711a857f 100644 --- a/Sources/TestHelper/NavNative/NativeNavigatorSpy.swift +++ b/Sources/TestHelper/NavNative/NativeNavigatorSpy.swift @@ -19,6 +19,9 @@ public class NativeNavigatorSpy: MapboxNavigationNative.Navigator { public var rerouteController: RerouteControllerInterface! public var rerouteDetector: RerouteDetectorInterface! + public var startNavigationSessionCalled = false + public var stopNavigationSessionCalled = false + public init() { let factory = NativeHandlersFactory(tileStorePath: "", credentials: DirectionsSpy.shared.credentials) super.init(config: NativeHandlersFactory.configHandle(), @@ -70,4 +73,12 @@ public class NativeNavigatorSpy: MapboxNavigationNative.Navigator { @_implementationOnly public override func getRerouteDetector() -> RerouteDetectorInterface { return rerouteDetector ?? RerouteDetectorSpy() } + + public override func startNavigationSession() { + startNavigationSessionCalled = true + } + + public override func stopNavigationSession() { + stopNavigationSessionCalled = true + } } diff --git a/Sources/TestHelper/NavigationSessionManagerSpy.swift b/Sources/TestHelper/NavigationSessionManagerSpy.swift new file mode 100644 index 0000000000..d2723032d7 --- /dev/null +++ b/Sources/TestHelper/NavigationSessionManagerSpy.swift @@ -0,0 +1,22 @@ +import Foundation +@testable import MapboxCoreNavigation + +public final class NavigationSessionManagerSpy: NavigationSessionManager { + public static var shared: NavigationSessionManagerSpy = .init() + + public var reportStartNavigationCalled = false + public var reportStopNavigationCalled = false + + public func reportStartNavigation() { + reportStartNavigationCalled = true + } + + public func reportStopNavigation() { + reportStopNavigationCalled = true + } + + public func reset() { + reportStartNavigationCalled = false + reportStopNavigationCalled = false + } +} diff --git a/Tests/MapboxCoreNavigationIntegrationTests/NativeTelemetryIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/NativeTelemetryIntegrationTests.swift index 3708c1fd6b..26b2877d7c 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/NativeTelemetryIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/NativeTelemetryIntegrationTests.swift @@ -42,7 +42,11 @@ class NativeTelemetryIntegrationTests: TestCase { private var volumeLevel: Int { Int(AVAudioSession.sharedInstance().outputVolume * 100) } private var batteryPluggedIn: Bool { [.charging, .full].contains(UIDevice.current.batteryState) } + private var geometry: String! + override func setUp() { + super.setUp() + NavigationTelemetryConfiguration.useNavNativeTelemetryEvents = true directions = .mocked configureEventsObserver() @@ -55,6 +59,13 @@ class NativeTelemetryIntegrationTests: TestCase { routeOptions.includesAlternativeRoutes = false let jsonFileName = "multileg-route" routeResponse = Fixture.routeResponse(from: jsonFileName, options: routeOptions) + let responseData = Fixture.JSONFromFileNamed(name: "multileg-route") + let json = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments) as? [String: Any] + let routesData = json?["routes"] as? [[String: Any]] + let geometryRawValue = (routesData?[0]["geometry"] as? String)? + .trimmingCharacters(in: .whitespacesAndNewlines) + .replacingOccurrences(of: "\\", with: "\\\\") ?? "" + geometry = "\"" + geometryRawValue + "\"" route = routeResponse.routes!.first! indexedRouteResponse = IndexedRouteResponse(routeResponse: routeResponse, routeIndex: 0) alternateRouteResponse = Fixture.routeResponse(from: jsonFileName, options: routeOptions) @@ -80,12 +91,9 @@ class NativeTelemetryIntegrationTests: TestCase { locationManager.speedMultiplier = 10 navigator = .shared - - super.setUp() } override func tearDown() { - NavigationTelemetryConfiguration.useNavNativeTelemetryEvents = false Navigator.shared.rerouteController.reroutesProactively = true navigationService = nil passiveLocationManager = nil @@ -93,12 +101,13 @@ class NativeTelemetryIntegrationTests: TestCase { UserDefaults.resetStandardUserDefaults() eventsAPI.unregisterObserver(for: telemetryObserver) telemetryObserver = nil + NavigationSessionManagerImp.shared.reportStopNavigation() + NavigationTelemetryConfiguration.useNavNativeTelemetryEvents = false super.tearDown() } - // TODO: skip until new NN startTripSession/stopTripSession supported NAVIOS-983 - func skip_testStartFreeDrive() { + func testStartFreeDrive() { let firstLocation = locationManager.locations.first! telemetryObserver.expectedEvents = [ freeDriveEvent(eventType: "start", coordinate: firstLocation.coordinate) @@ -109,15 +118,11 @@ class NativeTelemetryIntegrationTests: TestCase { wait(for: [telemetryObserver.expectation], timeout: expectationsTimeout) } - func skip_testStartActiveNavigation() { + func testStartActiveNavigation() { updateLocation() let firstLocation = locationManager.locations.first! configureActiveNavigation() - - // TODO: do not skip events after fix in NN - telemetryObserver.shouldSkipEventsCount = 2 - var activeAttributesKeys = activeNoValueCheckAttributesKeys activeAttributesKeys.insert("locationsAfter") telemetryObserver.expectedEvents = [ @@ -125,17 +130,13 @@ class NativeTelemetryIntegrationTests: TestCase { state: "navigation_started", routeProgress: navigationService.routeProgress, coordinate: firstLocation.coordinate), - activeNavigationEvent(eventName: "navigation.depart", - routeProgress: navigationService.routeProgress, - coordinate: firstLocation.coordinate, - noValueCheckAttributesKeys: activeAttributesKeys), ] startActiveNavigation() - wait(for: [telemetryObserver.expectation], timeout: 5) + wait(for: [telemetryObserver.expectation], timeout: expectationsTimeout) } - func skip_testStartActiveNavigationAfterFreeRide() { + func testStartActiveNavigationAfterFreeRide() { let firstLocation = locationManager.locations.first! telemetryObserver.expectedEvents = [ freeDriveEvent(eventType: "start", coordinate: firstLocation.coordinate) @@ -160,7 +161,7 @@ class NativeTelemetryIntegrationTests: TestCase { wait(for: [telemetryObserver.expectation], timeout: expectationsTimeout) } - func skip_testFinishRoute() { + func testFinishRoute() { let firstLocation = locationManager.locations.first! telemetryObserver.expectedEvents = [ freeDriveEvent(eventType: "start", coordinate: firstLocation.coordinate) @@ -181,15 +182,6 @@ class NativeTelemetryIntegrationTests: TestCase { state: "navigation_started", routeProgress: navigationService.routeProgress, coordinate: firstLocation.coordinate), - activeNavigationEvent(eventName: "navigation.depart", - routeProgress: navigationService.routeProgress, - coordinate: firstLocation.coordinate, - noValueCheckAttributesKeys: activeAttributesKeys), -// TODO: enable after fix in NN -// activeNavigationEvent(eventName: "navigation.arrive", -// routeProgress: navigationService.routeProgress, -// coordinate: firstLocation.coordinate, -// noValueCheckAttributesKeys: activeAttributesKeys) ] startActiveNavigation() @@ -200,10 +192,10 @@ class NativeTelemetryIntegrationTests: TestCase { return false } - wait(for: [telemetryObserver.expectation, navigationFinished], timeout: 5) + wait(for: [telemetryObserver.expectation, navigationFinished], timeout: expectationsTimeout) } - func skip_testStartFreeRideAfterActiveNavigation() { + func testStartFreeRideAfterActiveNavigation() { let firstLocation = locationManager.locations.first! telemetryObserver.expectedEvents = [ freeDriveEvent(eventType: "start", coordinate: firstLocation.coordinate) @@ -256,7 +248,7 @@ class NativeTelemetryIntegrationTests: TestCase { static func ==(lhs: NativeTelemetryIntegrationTests.Event, rhs: NativeTelemetryIntegrationTests.Event) -> Bool { lhs.eventName == rhs.eventName && - lhs.attributes.count == rhs.attributes.count && + lhs.attributes == rhs.attributes && lhs.noValueCheckAttributesKeys == rhs.noValueCheckAttributesKeys && lhs.approximateValueCheckAttributes.count == rhs.approximateValueCheckAttributes.count && lhs.approximateValueCheckAttributes.allSatisfy { key, value in @@ -312,7 +304,8 @@ class NativeTelemetryIntegrationTests: TestCase { } guard actualEvents.count < expectedEvents.count else { - return XCTFail("Event \(eventName) was not expected") + expectation.fulfill() + return } print("\(eventName) event revieved. Details \(event)") @@ -331,7 +324,6 @@ class NativeTelemetryIntegrationTests: TestCase { fileprivate var navigationBaseAttributes: [String: Any] { [ - "connectivity": "WiFi", "sdkIdentifier": "mapbox-navigation-ios", "eventVersion": 2.4, "version": 2.4, @@ -355,14 +347,21 @@ class NativeTelemetryIntegrationTests: TestCase { "createdMonotime", "driverModeStartTimestamp", "driverModeStartTimestampMonotime", - "driverModeId" + "driverModeId", + "connectivity", ] } private var activeNoValueCheckAttributesKeys: Set { let attributes: Set = [ "originalRequestIdentifier", - "requestIdentifier" + "requestIdentifier", + "connectivity", + "distanceRemaining", + "durationRemaining", + // TODO: fix flaky check of voiceIndex & bannerIndex + "voiceIndex", + "bannerIndex" ] return attributes.union(passiveNoValueCheckAttributesKeys) } @@ -459,32 +458,31 @@ class NativeTelemetryIntegrationTests: TestCase { let location = CLLocation(coordinate: coordinate) var attributes: [String: Any] = [ "originalStepCount": originalRoute.legs.map({$0.steps.count}).reduce(0, +), - "stepCount": routeProgress.currentLeg.steps.count, - "voiceIndex": 0, - "bannerIndex": 0, + "stepCount": routeProgress.route.legs.reduce(0) { $0 + $1.steps.count }, "driverMode": "trip", "lat": coordinate.latitude, "lng": coordinate.longitude, "profile": "driving-traffic", - "originalGeometry": Polyline(coordinates: originalRoute.shape!.coordinates).encodedPolyline, + "originalGeometry": geometry ?? "", "originalGeometryFormat": "precision_6", - "geometry": Polyline(coordinates: route.shape!.coordinates).encodedPolyline, + "geometry": geometry ?? "", "geometryFormat": "precision_6", "stepIndex": routeProgress.currentLegProgress.stepIndex, "legIndex": routeProgress.legIndex, "legCount": routeProgress.route.legs.count, "estimatedDuration": Int(route.expectedTravelTime), - "estimatedDistance": route.distance, + "estimatedDistance": Int(route.distance), "rerouteCount": rerouteCount ] if let state = state { attributes["state"] = state } let approximateValueCheckAttributes = [ + "absoluteDistanceToDestination": (location.distance(from: CLLocation(latitude: lastCoordinate.latitude, longitude: lastCoordinate.longitude)), 10.0), "distanceCompleted": (routeProgress.distanceTraveled, 10.0), - "distanceRemaining": (routeProgress.distanceRemaining, 10.0), - "durationRemaining": (routeProgress.durationRemaining, 1.0), - "absoluteDistanceToDestination": (location.distance(from: CLLocation(latitude: lastCoordinate.latitude, longitude: lastCoordinate.longitude)), 10.0) + // TODO: return value check after NN fix +// "distanceRemaining": (routeProgress.distanceRemaining, 10.0), +// "durationRemaining": (routeProgress.durationRemaining, 1.0), ] let attributesKeys = noValueCheckAttributesKeys ?? activeNoValueCheckAttributesKeys return event(with: eventName, @@ -538,3 +536,17 @@ extension NativeTelemetryIntegrationTests.Event { noValueCheckAttributesKeys: Set(noValueCheckAttributesKeys)) } } + +private func ==(lhs: [String: Any], rhs: [String: Any] ) -> Bool { + guard Set(lhs.keys) == Set(rhs.keys) else { return false } + return lhs.keys.allSatisfy { key in + let leftValue = lhs[key]! + let rightValue = rhs[key]! + let result = NSDictionary(dictionary: [key: leftValue]).isEqual(to: [key: rightValue]) + + if (!result) { + print("Found diff between events \(lhs["event"] ?? "").\(lhs["state"] ?? "")) with key=\(key), actual \(leftValue), expected \(rightValue)") + } + return result + } +} diff --git a/Tests/MapboxCoreNavigationTests/PassiveLocationManagerTests.swift b/Tests/MapboxCoreNavigationTests/PassiveLocationManagerTests.swift index b2ce89d37f..1f9d5f3aae 100644 --- a/Tests/MapboxCoreNavigationTests/PassiveLocationManagerTests.swift +++ b/Tests/MapboxCoreNavigationTests/PassiveLocationManagerTests.swift @@ -11,6 +11,7 @@ class PassiveLocationManagerTests: TestCase { private var eventsManagerType: NavigationEventsManagerSpy! private var navigatorSpy: CoreNavigatorSpy! private var locationManagerSpy: NavigationLocationManagerSpy! + private var navigationSessionManagerSpy: NavigationSessionManagerSpy! private var passiveLocationManager: PassiveLocationManager! private var delegate: PassiveLocationManagerDelegateSpy! @@ -30,12 +31,14 @@ class PassiveLocationManagerTests: TestCase { locationManagerSpy = .init() directionsSpy = .init() navigatorSpy = CoreNavigatorSpy.shared + navigationSessionManagerSpy = NavigationSessionManagerSpy.shared passiveLocationManager = PassiveLocationManager(directions: directionsSpy, systemLocationManager: locationManagerSpy, eventsManagerType: NavigationEventsManagerSpy.self, userInfo: [:], datasetProfileIdentifier: .cycling, - navigatorType: CoreNavigatorSpy.self) + navigatorType: CoreNavigatorSpy.self, + navigationSessionManager: navigationSessionManagerSpy) delegate = PassiveLocationManagerDelegateSpy() passiveLocationManager.delegate = delegate } @@ -44,6 +47,7 @@ class PassiveLocationManagerTests: TestCase { super.tearDown() PassiveLocationManager.historyDirectoryURL = nil HistoryRecorder._recreateHistoryRecorder() + NavigationSessionManagerSpy.shared.reset() } func testHandleDidUpdateLocations() { @@ -111,8 +115,10 @@ class PassiveLocationManagerTests: TestCase { } func testStartNavigation() { + navigationSessionManagerSpy.reset() passiveLocationManager.startUpdatingLocation() XCTAssertTrue(locationManagerSpy.startUpdatingLocationCalled) + XCTAssertFalse(navigationSessionManagerSpy.reportStartNavigationCalled, "Should not report start twice") } func testDidNoThrowIfDidUpdateNilLocation() { @@ -372,14 +378,16 @@ class PassiveLocationManagerTests: TestCase { XCTAssertTrue(locationManagerSpy.delegate === passiveLocationManager) } - func testSetDatasetProfileIdentifier() { + func testInitialize() { _ = PassiveLocationManager(directions: directionsSpy, systemLocationManager: locationManagerSpy, eventsManagerType: NavigationEventsManagerSpy.self, userInfo: [:], datasetProfileIdentifier: .walking, - navigatorType: CoreNavigatorSpy.self) + navigatorType: CoreNavigatorSpy.self, + navigationSessionManager: navigationSessionManagerSpy) XCTAssertEqual(CoreNavigatorSpy.datasetProfileIdentifier, .walking) + XCTAssertTrue(navigationSessionManagerSpy.reportStartNavigationCalled) } func testCreateDefaultManager() { diff --git a/Tests/MapboxCoreNavigationTests/RouteControllerTests.swift b/Tests/MapboxCoreNavigationTests/RouteControllerTests.swift index 02309e2bfe..2c57c594cb 100644 --- a/Tests/MapboxCoreNavigationTests/RouteControllerTests.swift +++ b/Tests/MapboxCoreNavigationTests/RouteControllerTests.swift @@ -10,7 +10,8 @@ import MapboxNavigationNative class RouteControllerTests: TestCase { private let expectationsTimeout = 0.5 - + + private var navigationSessionManagerSpy: NavigationSessionManagerSpy! private var locationManagerSpy: NavigationLocationManagerSpy! private var navigatorSpy: CoreNavigatorSpy! private var nativeNavigatorSpy: NativeNavigatorSpy! @@ -75,6 +76,7 @@ class RouteControllerTests: TestCase { indexedRouteResponse = IndexedRouteResponse(routeResponse: routeResponse, routeIndex: 0) routeProgress = .init(route: routeResponse.routes![0], options: options) nativeRoute = TestRouteProvider.createRoute(routeResponse: makeRouteResponse()) + navigationSessionManagerSpy = NavigationSessionManagerSpy.shared singleRouteResponse = makeSingleRouteResponse() multilegRouteResponse = makeMultilegRouteResponse() @@ -89,6 +91,7 @@ class RouteControllerTests: TestCase { RouteParserSpy.returnedRoutes = nil RouteParserSpy.returnedError = nil MapboxRoutingProvider.__testRoutesStub = nil + NavigationSessionManagerSpy.shared.reset() super.tearDown() } @@ -156,12 +159,14 @@ class RouteControllerTests: TestCase { customRoutingProvider: routingProvider, dataSource: dataSource, navigatorType: CoreNavigatorSpy.self, - routeParserType: RouteParserSpy.self) + routeParserType: RouteParserSpy.self, + navigationSessionManager: navigationSessionManagerSpy) XCTAssertTrue(navigatorSpy.setRoutesCalled) XCTAssertEqual(navigatorSpy.passedUuid, controller.sessionUUID) XCTAssertEqual(navigatorSpy.passedLegIndex, UInt32(routeController.routeProgress.legIndex)) XCTAssertEqual(navigatorSpy.passedReason, .startNewRoute) + XCTAssertTrue(navigationSessionManagerSpy.reportStartNavigationCalled) } func testReturnLocation() { @@ -700,6 +705,7 @@ class RouteControllerTests: TestCase { XCTFail() return } + navigationSessionManagerSpy.reset() routeController.updateRoute(with: response, routeOptions: routeOptions, isProactive: false, completion: nil) XCTAssertTrue(navigatorSpy.setRoutesCalled) @@ -713,6 +719,7 @@ class RouteControllerTests: TestCase { XCTAssertTrue(routeController.routeProgress.route === response.currentRoute) XCTAssertEqual(routeController.routeProgress.routeOptions, routeOptions) + XCTAssertFalse(navigationSessionManagerSpy.reportStartNavigationCalled, "Should not report start twice") } func testUpdateRouteIfShouldNotStartNewBillingSession() { @@ -1367,6 +1374,8 @@ class RouteControllerTests: TestCase { func testDoNotUpdateRouteIfFinishedRouting() { let newResponse = IndexedRouteResponse(routeResponse: singleRouteResponse, routeIndex: 0) routeController.finishRouting() + + XCTAssertTrue(navigationSessionManagerSpy.reportStopNavigationCalled) let expectation = expectation(description: "Should not call callback") expectation.isInverted = true routeController.updateRoute(with: newResponse, routeOptions: options) { _ in @@ -1659,7 +1668,8 @@ class RouteControllerTests: TestCase { customRoutingProvider: routingProvider, dataSource: dataSource, navigatorType: CoreNavigatorSpy.self, - routeParserType: RouteParserSpy.self) + routeParserType: RouteParserSpy.self, + navigationSessionManager: navigationSessionManagerSpy) controller.delegate = delegate navigatorSpy.reset() return controller diff --git a/Tests/MapboxCoreNavigationTests/Telemetry/NavigationSessionManagerTests.swift b/Tests/MapboxCoreNavigationTests/Telemetry/NavigationSessionManagerTests.swift new file mode 100644 index 0000000000..98cfb07100 --- /dev/null +++ b/Tests/MapboxCoreNavigationTests/Telemetry/NavigationSessionManagerTests.swift @@ -0,0 +1,56 @@ +@testable @_spi(MapboxInternal) import MapboxCoreNavigation +@testable import TestHelper +import XCTest + +final class NavigationSessionManagerTests: TestCase { + private var manager: NavigationSessionManager! + private var coreNavigator: CoreNavigatorSpy! + private var navigator: NativeNavigatorSpy! + + override func setUp() { + super.setUp() + + coreNavigator = CoreNavigatorSpy.shared + navigator = coreNavigator.navigatorSpy + manager = NavigationSessionManagerImp(navigatorType: CoreNavigatorSpy.self) + NavigationTelemetryConfiguration.useNavNativeTelemetryEvents = true + } + + override func tearDown() { + NavigationTelemetryConfiguration.useNavNativeTelemetryEvents = false + super.tearDown() + } + + func testDoNotStartAndStopIfNonNativeTelemetry() { + NavigationTelemetryConfiguration.useNavNativeTelemetryEvents = false + manager.reportStopNavigation() + manager.reportStartNavigation() + XCTAssertFalse(navigator.startNavigationSessionCalled) + XCTAssertFalse(navigator.stopNavigationSessionCalled) + } + + func testStopSessionIfNotStartedIfNativeTelemetry() { + manager.reportStopNavigation() + XCTAssertFalse(navigator.stopNavigationSessionCalled, "Should not report stop if not started") + + manager.reportStartNavigation() + XCTAssertTrue(navigator.startNavigationSessionCalled) + manager.reportStopNavigation() + XCTAssertTrue(navigator.stopNavigationSessionCalled) + } + + func testStartAndStopSessionIfTwoSessionStartedIfNativeTelemetry() { + manager.reportStartNavigation() + XCTAssertTrue(navigator.startNavigationSessionCalled, "Should report start if not started") + + navigator.startNavigationSessionCalled = false + manager.reportStartNavigation() + XCTAssertFalse(navigator.startNavigationSessionCalled, "Should not report start if already started") + + manager.reportStopNavigation() + XCTAssertFalse(navigator.stopNavigationSessionCalled, "Should not report stop if more session running") + + manager.reportStopNavigation() + XCTAssertTrue(navigator.stopNavigationSessionCalled, "Should report stop if no more session running") + } +}