Skip to content

Commit 1fc6160

Browse files
authored
Support the movement info in v2 SDK iOS (#4749)
1 parent 3796cd6 commit 1fc6160

File tree

6 files changed

+255
-1
lines changed

6 files changed

+255
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
* The extension for AVAudioSession is no longer supported and has been deprecated, which affects the following methods:
1212
* `AVAudioSession.tryDuckAudio()`
1313
* `AVAudioSession.tryUnduckAudio()`
14+
15+
### Other changes
16+
17+
* Added movement type reports to Telemetry.
1418

1519
## v2.19.0
1620

MapboxNavigation.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127
2C856451297AE0A6006EFCBB /* NavigationEventsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C856450297AE0A6006EFCBB /* NavigationEventsManager.swift */; };
128128
2C856454297B1884006EFCBB /* NavigationCommonEventsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C856453297B1884006EFCBB /* NavigationCommonEventsManagerTests.swift */; };
129129
2C856456297B3862006EFCBB /* EventsDatasourceSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C856455297B3862006EFCBB /* EventsDatasourceSpy.swift */; };
130+
2C8BF7AE2D6C99FC00C60BD4 /* NavigationMovementMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8BF7AD2D6C99FC00C60BD4 /* NavigationMovementMonitor.swift */; };
131+
2C8BF7B02D6C9A0900C60BD4 /* NavigationMovementMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8BF7AF2D6C9A0900C60BD4 /* NavigationMovementMonitorTests.swift */; };
130132
2C9006CA291927850012CCEA /* RoutingProviderSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9006C7291927850012CCEA /* RoutingProviderSpy.swift */; };
131133
2C9006CB291927850012CCEA /* CoreNavigatorSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9006C8291927850012CCEA /* CoreNavigatorSpy.swift */; };
132134
2C9006CC291927850012CCEA /* DummyRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9006C9291927850012CCEA /* DummyRoute.swift */; };
@@ -820,6 +822,8 @@
820822
2C856450297AE0A6006EFCBB /* NavigationEventsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationEventsManager.swift; sourceTree = "<group>"; };
821823
2C856453297B1884006EFCBB /* NavigationCommonEventsManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationCommonEventsManagerTests.swift; sourceTree = "<group>"; };
822824
2C856455297B3862006EFCBB /* EventsDatasourceSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsDatasourceSpy.swift; sourceTree = "<group>"; };
825+
2C8BF7AD2D6C99FC00C60BD4 /* NavigationMovementMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMovementMonitor.swift; sourceTree = "<group>"; };
826+
2C8BF7AF2D6C9A0900C60BD4 /* NavigationMovementMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMovementMonitorTests.swift; sourceTree = "<group>"; };
823827
2C9006C7291927850012CCEA /* RoutingProviderSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutingProviderSpy.swift; sourceTree = "<group>"; };
824828
2C9006C8291927850012CCEA /* CoreNavigatorSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreNavigatorSpy.swift; sourceTree = "<group>"; };
825829
2C9006C9291927850012CCEA /* DummyRoute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyRoute.swift; sourceTree = "<group>"; };
@@ -1563,6 +1567,7 @@
15631567
2C484CD82979AD5C00EAAE78 /* Telemetry */ = {
15641568
isa = PBXGroup;
15651569
children = (
1570+
2C8BF7AF2D6C9A0900C60BD4 /* NavigationMovementMonitorTests.swift */,
15661571
2CEC5C5729F71D9A001B5EC2 /* NavigationSessionManagerTests.swift */,
15671572
2C856453297B1884006EFCBB /* NavigationCommonEventsManagerTests.swift */,
15681573
162039CE216C348500875F5C /* NavigationEventsManagerTests.swift */,
@@ -1592,6 +1597,7 @@
15921597
2C856447297AD64C006EFCBB /* Telemetry */ = {
15931598
isa = PBXGroup;
15941599
children = (
1600+
2C8BF7AD2D6C99FC00C60BD4 /* NavigationMovementMonitor.swift */,
15951601
2C4D0A2D29F6DC900063BF52 /* NavigationSessionManager.swift */,
15961602
2C18A12D2988729C00750CEE /* EventStep.swift */,
15971603
2C18A12B29886F8C00750CEE /* EventFixLocation.swift */,
@@ -3589,6 +3595,7 @@
35893595
2B01E4B6274671550002A5F7 /* MapboxRoutingProvider.swift in Sources */,
35903596
2B28E22127EB48C60029E4C1 /* RerouteControllerDelegate.swift in Sources */,
35913597
8D4CF9C621349FFB009C3FEE /* NavigationServiceDelegate.swift in Sources */,
3598+
2C8BF7AE2D6C99FC00C60BD4 /* NavigationMovementMonitor.swift in Sources */,
35923599
2BF398C3274FE99A000C9A72 /* HandlerFactory.swift in Sources */,
35933600
118D883626F8CA0700B2ED7B /* ActiveNavigationFeedbackType.swift in Sources */,
35943601
11B3D6D626A60EBD0057C6F4 /* ActiveNavigationEventDetails.swift in Sources */,
@@ -3624,6 +3631,7 @@
36243631
162039CF216C348500875F5C /* NavigationEventsManagerTests.swift in Sources */,
36253632
2C14C97C298BCF4F0052D122 /* DeviceTest.swift in Sources */,
36263633
2C9DDBE22934C006007F8CFD /* MapboxCoreNavigationTests.swift in Sources */,
3634+
2C8BF7B02D6C9A0900C60BD4 /* NavigationMovementMonitorTests.swift in Sources */,
36273635
2C9006D9291927EE0012CCEA /* Dictionary+Equality.swift in Sources */,
36283636
E2842798265B907C003F86E4 /* UnimplementedLoggingTests.swift in Sources */,
36293637
DAD903AF23E3DCC80057CF1F /* DateTests.swift in Sources */,

Sources/MapboxCoreNavigation/NavigationService.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ public class MapboxNavigationService: NSObject, NavigationService {
272272
BillingHandler.shared.sessionState(uuid: routeController.sessionUUID) != .running {
273273
BillingHandler.shared.resumeBillingSession(with: routeController.sessionUUID)
274274
}
275+
NavigationMovementMonitor.shared.currentProfile = router.routeProgress.routeOptions.profileIdentifier
275276
}
276277

277278
/**
@@ -298,6 +299,7 @@ public class MapboxNavigationService: NSObject, NavigationService {
298299
BillingHandler.shared.sessionState(uuid: routeController.sessionUUID) == .running {
299300
BillingHandler.shared.pauseBillingSession(with: routeController.sessionUUID)
300301
}
302+
NavigationMovementMonitor.shared.currentProfile = nil
301303
}
302304

303305
public func endNavigation(feedback: EndOfRouteFeedback? = nil) {

Sources/MapboxCoreNavigation/Telemetry/NavigationEventsManager.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ open class NavigationEventsManager {
5151
accessToken possibleToken: String? = nil) {
5252
if NavigationTelemetryConfiguration.useNavNativeTelemetryEvents {
5353

54-
let navNativeEventsManager = NavigationNativeEventsManager.init(navigator: Navigator.shared)
54+
let navNativeEventsManager = NavigationNativeEventsManager(navigator: Navigator.shared)
5555
self.commonEventsManager = nil
5656
self.navNativeEventsManager = navNativeEventsManager
5757
self.actualEventsManager = navNativeEventsManager
@@ -64,6 +64,8 @@ open class NavigationEventsManager {
6464
self.commonEventsManager = commonEventsManager
6565
self.actualEventsManager = commonEventsManager
6666
}
67+
// Initialize shared instance
68+
_ = NavigationMovementMonitor.shared
6769
}
6870

6971
init(activeNavigationDataSource: ActiveNavigationEventsManagerDataSource? = nil,
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import Foundation
2+
@_implementationOnly import MapboxCommon_Private
3+
import MapboxDirections
4+
5+
final class NavigationMovementMonitor: MovementMonitorInterface {
6+
static let shared: NavigationMovementMonitor = {
7+
let movementMonitor = NavigationMovementMonitor()
8+
MovementMonitorFactory.setUserDefinedForCustom(movementMonitor)
9+
return movementMonitor
10+
}()
11+
12+
private var observers: [any MovementModeObserver] = []
13+
private var customMovementInfo: MovementInfo? = nil
14+
15+
private var _currentProfile: ProfileIdentifier? = nil
16+
private let queue: DispatchQueue
17+
18+
init(queue: DispatchQueue = DispatchQueue(
19+
label: "com.mapbox.NavigationMovementMonitor",
20+
attributes: .concurrent
21+
)) {
22+
self.queue = queue
23+
}
24+
25+
var currentProfile: ProfileIdentifier? {
26+
get {
27+
queue.sync {
28+
_currentProfile
29+
}
30+
}
31+
set {
32+
queue.async(flags: .barrier) { [weak self] in
33+
guard let self else { return }
34+
35+
self._currentProfile = newValue
36+
self.notify(with: self.movementInfo)
37+
}
38+
}
39+
}
40+
41+
func getMovementInfo(forCallback callback: @escaping MovementInfoCallback) {
42+
queue.async { [weak self] in
43+
guard let self else { return }
44+
45+
callback(.init(value: self.movementInfo))
46+
}
47+
}
48+
49+
func setMovementInfoForMode(_ movementInfo: MovementInfo) {
50+
queue.async(flags: .barrier) { [weak self] in
51+
guard let self else { return }
52+
53+
self.customMovementInfo = movementInfo
54+
self.notify(with: movementInfo)
55+
}
56+
}
57+
58+
func registerObserver(for observer: any MovementModeObserver) {
59+
queue.async(flags: .barrier) { [weak self] in
60+
self?.observers.append(observer)
61+
}
62+
}
63+
64+
func unregisterObserver(for observer: any MovementModeObserver) {
65+
queue.async(flags: .barrier) { [weak self] in
66+
self?.observers.removeAll(where: { $0 === observer })
67+
}
68+
}
69+
70+
private func notify(with movementInfo: MovementInfo) {
71+
let currentObservers = observers
72+
for currentObserver in currentObservers {
73+
currentObserver.onMovementModeChanged(for: movementInfo)
74+
}
75+
}
76+
77+
private var movementInfo: MovementInfo {
78+
if let customMovementInfo {
79+
return customMovementInfo
80+
}
81+
let profile = _currentProfile
82+
83+
let movementModes: [NSNumber: NSNumber]
84+
if let movementMode = profile?.movementMode {
85+
movementModes = [movementMode.rawValue as NSNumber: 100]
86+
} else if profile != nil {
87+
movementModes = [MovementMode.inVehicle.rawValue as NSNumber: 50]
88+
} else {
89+
movementModes = [MovementMode.unknown.rawValue as NSNumber: 50]
90+
}
91+
return MovementInfo(movementMode: movementModes, movementProvider: .SDK)
92+
}
93+
}
94+
95+
extension ProfileIdentifier {
96+
var movementMode: MovementMode? {
97+
switch self {
98+
case .automobile, .automobileAvoidingTraffic:
99+
return .inVehicle
100+
case .cycling:
101+
return .cycling
102+
case .walking:
103+
return .onFoot
104+
default:
105+
return nil
106+
}
107+
}
108+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
@_implementationOnly import MapboxCommon_Private
2+
@testable import MapboxCoreNavigation
3+
import XCTest
4+
5+
final class NavigationMovementMonitorTests: XCTestCase {
6+
private var monitor: NavigationMovementMonitor!
7+
private var observer1: MovementModeObserverSpy!
8+
private var observer2: MovementModeObserverSpy!
9+
private var testQueue: DispatchQueue!
10+
11+
override func setUp() {
12+
super.setUp()
13+
14+
testQueue = DispatchQueue(label: "test")
15+
monitor = NavigationMovementMonitor(queue: testQueue)
16+
observer1 = MovementModeObserverSpy()
17+
observer2 = MovementModeObserverSpy()
18+
}
19+
20+
func testSetMovementInfo() {
21+
let movementInfo = MovementInfo(movementMode: [1: 10, 2: 90], movementProvider: .unknown)
22+
monitor.registerObserver(for: observer1)
23+
monitor.registerObserver(for: observer2)
24+
monitor.setMovementInfoForMode(movementInfo)
25+
26+
testQueue.sync {}
27+
XCTAssertEqual(observer1.movementInfo, movementInfo)
28+
XCTAssertEqual(observer2.movementInfo, movementInfo)
29+
}
30+
31+
func testUnregisterObserver() {
32+
let movementInfo = MovementInfo(movementMode: [1: 10, 2: 90], movementProvider: .unknown)
33+
monitor.registerObserver(for: observer1)
34+
monitor.registerObserver(for: observer2)
35+
monitor.setMovementInfoForMode(movementInfo)
36+
37+
let movementInfo2 = MovementInfo(movementMode: [1: 100], movementProvider: .SDK)
38+
monitor.unregisterObserver(for: observer1)
39+
40+
monitor.setMovementInfoForMode(movementInfo2)
41+
42+
testQueue.sync {}
43+
XCTAssertEqual(observer1.movementInfo, movementInfo)
44+
XCTAssertEqual(observer2.movementInfo, movementInfo2)
45+
}
46+
47+
func testGetMovementInfo() {
48+
var movementInfo: MovementInfo?
49+
monitor.getMovementInfo { expected in
50+
XCTAssertFalse(expected.isError())
51+
XCTAssertTrue(expected.isValue())
52+
movementInfo = expected.value
53+
}
54+
testQueue.sync {}
55+
let expectedValue = MovementInfo(
56+
movementMode: [MovementMode.unknown.rawValue as NSNumber: 50],
57+
movementProvider: .SDK
58+
)
59+
XCTAssertEqual(movementInfo?.movementMode, expectedValue.movementMode)
60+
XCTAssertEqual(movementInfo?.movementProvider, expectedValue.movementProvider)
61+
62+
monitor.currentProfile = .automobileAvoidingTraffic
63+
monitor.getMovementInfo { expected in
64+
XCTAssertFalse(expected.isError())
65+
XCTAssertTrue(expected.isValue())
66+
movementInfo = expected.value
67+
}
68+
testQueue.sync {}
69+
let expectedValue2 = MovementInfo(
70+
movementMode: [MovementMode.inVehicle.rawValue as NSNumber: 100],
71+
movementProvider: .SDK
72+
)
73+
XCTAssertEqual(movementInfo?.movementMode, expectedValue2.movementMode)
74+
XCTAssertEqual(movementInfo?.movementProvider, expectedValue2.movementProvider)
75+
}
76+
77+
func testNotifyAboutProfileChange() {
78+
monitor.registerObserver(for: observer1)
79+
80+
monitor.currentProfile = .cycling
81+
XCTAssertEqual(monitor.currentProfile, .cycling)
82+
testQueue.sync {}
83+
XCTAssertEqual(observer1.movementInfo?.movementMode, [MovementMode.cycling.rawValue as NSNumber: 100])
84+
XCTAssertEqual(observer1.movementInfo?.movementProvider, .SDK)
85+
86+
monitor.currentProfile = .walking
87+
XCTAssertEqual(monitor.currentProfile, .walking)
88+
testQueue.sync {}
89+
XCTAssertEqual(observer1.movementInfo?.movementMode, [MovementMode.onFoot.rawValue as NSNumber: 100])
90+
91+
monitor.currentProfile = .automobile
92+
XCTAssertEqual(monitor.currentProfile, .automobile)
93+
testQueue.sync {}
94+
XCTAssertEqual(observer1.movementInfo?.movementMode, [MovementMode.inVehicle.rawValue as NSNumber: 100])
95+
96+
monitor.currentProfile = .automobileAvoidingTraffic
97+
XCTAssertEqual(monitor.currentProfile, .automobileAvoidingTraffic)
98+
testQueue.sync {}
99+
XCTAssertEqual(observer1.movementInfo?.movementMode, [MovementMode.inVehicle.rawValue as NSNumber: 100])
100+
101+
monitor.currentProfile = nil
102+
XCTAssertNil(monitor.currentProfile)
103+
testQueue.sync {}
104+
XCTAssertEqual(observer1.movementInfo?.movementMode, [MovementMode.unknown.rawValue as NSNumber: 50])
105+
106+
monitor.currentProfile = .init(rawValue: "custom")
107+
testQueue.sync {}
108+
XCTAssertEqual(observer1.movementInfo?.movementMode, [MovementMode.inVehicle.rawValue as NSNumber: 50])
109+
}
110+
}
111+
112+
private final class MovementModeObserverSpy: MovementModeObserver {
113+
var error: String?
114+
var movementInfo: MovementInfo?
115+
116+
func onMovementModeChanged(for movementInfo: MovementInfo) {
117+
self.movementInfo = movementInfo
118+
}
119+
120+
func onMovementModeError(forError error: String) {
121+
self.error = error
122+
}
123+
}
124+
125+
extension MovementInfo {
126+
fileprivate static func equals(_ lhs: MovementInfo, _ rhs: MovementInfo) -> Bool {
127+
lhs.movementMode == rhs.movementMode &&
128+
lhs.movementProvider == rhs.movementProvider
129+
}
130+
}

0 commit comments

Comments
 (0)