-
Notifications
You must be signed in to change notification settings - Fork 311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use the pre-existing mapView
location provider when simulation mode turned off.
#3393
Conversation
d0b700f
to
b0f1939
Compare
71ba424
to
dc6ca74
Compare
9fd3495
to
3d75d0a
Compare
@@ -857,6 +856,8 @@ extension CarPlayManager: CarPlayNavigationViewControllerDelegate { | |||
if let passiveLocationProvider = navigationMapView?.mapView.location.locationProvider as? PassiveLocationProvider { | |||
passiveLocationProvider.locationManager.resumeTripSession() | |||
} | |||
|
|||
self.carPlayNavigationViewController = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to nil out the CarPlayNavigationViewController
in CarPlayManager
when the CarPlayNavigationViewController
dismissed and exit navigation
let progress = router.routeProgress | ||
delegate?.navigationService(self, willBeginSimulating: progress, becauseOf: intent) | ||
announceSimulatingDidChange(simulatingUpdate: .willBeginSimulating) | ||
guard !isSimulating else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because when CarPlay is used and under simulated mode, the navigationService.simulate(intent:)
would be called twice. When we choose to start navigation from mobile, then the previous guard !isSimulating else { return }
before the notification would block sending notification to the CarPlay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@1ec5, could you please take a look at this one when you have a chance? Since NavigationService
is shared between CarPlay and iOS, when CarPlay starts active-guidance navigation we pass it to iOS. This means that CarPlay will not really know when navigation starts or finishes as delegate object will be overwritten by iOS.
I wonder if there are any pitfalls, which we might've missed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because when CarPlay is used and under simulated mode, the
navigationService.simulate(intent:)
would be called twice.
I’m not sure I understand… if there’s only one NavigationService, then shouldn’t simulate(intent:)
only be called once, since there’s only one poorGPSTimer
?
mapbox-navigation-ios/Sources/MapboxCoreNavigation/NavigationService.swift
Lines 309 to 314 in 95d73db
_poorGPSTimer = DispatchTimer(countdown: poorGPSPatience.dispatchInterval) { [weak self] in | |
guard let self = self, | |
self.simulationMode == .onPoorGPS || | |
(self.simulationMode == .inTunnels && self.isInTunnel(at: self.router.location!, along: self.routeProgress)) else { return } | |
self.simulate(intent: .poorGPS) | |
} |
Would a more straightforward fix be for this didSet
to bail if the new value is equal to the current value?
mapbox-navigation-ios/Sources/MapboxCoreNavigation/NavigationService.swift
Lines 150 to 162 in 95d73db
public var simulationMode: SimulationMode { | |
didSet { | |
switch simulationMode { | |
case .always: | |
simulate() | |
case .onPoorGPS, .inTunnels: | |
poorGPSTimer.arm() | |
case .never: | |
poorGPSTimer.disarm() | |
endSimulation(intent: .manual) | |
} | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the issue is that when the navigation starts in mobile with simulation mode, then the mobile would trigger the navigationService to start in simulation mode once. And then the CarPlay would also call the navigationService to start when the viewDidLoad
, but this time it would not notify the simulating starts because it's already in simulation mode (the mobile and carPlay share the same service).
So the didSet is triggered when the navigationService property change. But in the test, the CarPlay only starts the navigation in simulation only when the viewDidLoad, and then the navigation service start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In principle, CarPlayNavigationViewController should be sharing the location manager with RouteController, just like the two classes share a NavigationService. There shouldn’t be two independent location managers to keep in sync; NavigationService doesn’t have the ability to respond to two different location managers at the same time. I don’t disagree with the notification being added, but it seems like a band-aid atop a more fundamental architectural issue that we should prioritize post-v2.0.0.
d038735
to
258aa6d
Compare
b0b7981
to
d22f87b
Compare
45bc86b
to
0d881bc
Compare
|
||
The user info dictionary contains the key `MapboxNavigationService.NotificationUserInfoKey.simulatingUpdateKey` and `MapboxNavigationService.NotificationUserInfoKey.simulatedSpeedMultiplierKey`. | ||
*/ | ||
static let navigationServiceSimulatingDidChange: Notification.Name = .init(rawValue: "NavigationServiceSimulatingDidChange") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith service: NavigationService)
is no longer needed and can be removed as it was called not when navigation actually started, but when CarPlayNavigationViewController
was actually presented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith service: NavigationService)
should be replaced by carPlayManager(self, didPresent: carPlayNavigationViewController)
. Because the navigation really begin after the navigation service start. But because right now it's associated with the billing. I think it's safer to separate it into another ticket.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate regarding billing? I can see that we only handle func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith service: NavigationService)
delegate method once on client side, and this delegate handling code can be moved to func carPlayManager(_ carPlayManager: CarPlayManager, didPresent navigationViewController: CarPlayNavigationViewController)
, but for this we'll have to make CarPlayManager.navigationService
public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact the carPlayManager(_: didBeginNavigationWith:)
should be called in carPlayNavigationViewController
after the navigation service start and view loaded. And the carPlayManager(_: didPresent: )
should also be called here because it's the place when the CarPlay really present the navigation view. And it doesn't need to make CarPlayManager.navigationService
public, we could reach this through carPlayManager.carPlayNavigationViewController?.navigationService
. And then the billing should start when the view is presented, which requires some change in tests.
let progress = router.routeProgress | ||
delegate?.navigationService(self, willBeginSimulating: progress, becauseOf: intent) | ||
announceSimulatingDidChange(simulatingUpdate: .willBeginSimulating) | ||
guard !isSimulating else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@1ec5, could you please take a look at this one when you have a chance? Since NavigationService
is shared between CarPlay and iOS, when CarPlay starts active-guidance navigation we pass it to iOS. This means that CarPlay will not really know when navigation starts or finishes as delegate object will be overwritten by iOS.
I wonder if there are any pitfalls, which we might've missed.
a5944f8
to
e0a133d
Compare
e0a133d
to
84cf2cd
Compare
84cf2cd
to
bbddc4f
Compare
let progress = router.routeProgress | ||
delegate?.navigationService(self, willBeginSimulating: progress, becauseOf: intent) | ||
announceSimulatingDidChange(simulatingUpdate: .willBeginSimulating) | ||
guard !isSimulating else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because when CarPlay is used and under simulated mode, the
navigationService.simulate(intent:)
would be called twice.
I’m not sure I understand… if there’s only one NavigationService, then shouldn’t simulate(intent:)
only be called once, since there’s only one poorGPSTimer
?
mapbox-navigation-ios/Sources/MapboxCoreNavigation/NavigationService.swift
Lines 309 to 314 in 95d73db
_poorGPSTimer = DispatchTimer(countdown: poorGPSPatience.dispatchInterval) { [weak self] in | |
guard let self = self, | |
self.simulationMode == .onPoorGPS || | |
(self.simulationMode == .inTunnels && self.isInTunnel(at: self.router.location!, along: self.routeProgress)) else { return } | |
self.simulate(intent: .poorGPS) | |
} |
Would a more straightforward fix be for this didSet
to bail if the new value is equal to the current value?
mapbox-navigation-ios/Sources/MapboxCoreNavigation/NavigationService.swift
Lines 150 to 162 in 95d73db
public var simulationMode: SimulationMode { | |
didSet { | |
switch simulationMode { | |
case .always: | |
simulate() | |
case .onPoorGPS, .inTunnels: | |
poorGPSTimer.arm() | |
case .never: | |
poorGPSTimer.disarm() | |
endSimulation(intent: .manual) | |
} | |
} | |
} |
private func announceSimulationDidChange(simulationState: SimulationState) { | ||
let userInfo: [NotificationUserInfoKey: Any] = [ | ||
NotificationUserInfoKey.simulationStateKey: simulationState, | ||
NotificationUserInfoKey.simulatedSpeedMultiplierKey: _simulationSpeedMultiplier |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we send this value if state
is notInSimulation
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we do in the NavigationService.endSimulation(intent:)
to send this value if state is notInSimulation
, incase the carPlay could not be notified during the active navigation, such as when the user drives off the tunnel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure to test basic interactions in the following scenarios:
- Route simulation enabled
- Route simulation disabled
- Start navigation on the phone, then connect to CarPlay
- Connect to CarPlay, then start navigation on the phone
- Connect to CarPlay, then start navigation on CarPlay
- Start navigation on CarPlay with the application in the background on the phone
let progress = router.routeProgress | ||
delegate?.navigationService(self, willBeginSimulating: progress, becauseOf: intent) | ||
announceSimulatingDidChange(simulatingUpdate: .willBeginSimulating) | ||
guard !isSimulating else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In principle, CarPlayNavigationViewController should be sharing the location manager with RouteController, just like the two classes share a NavigationService. There shouldn’t be two independent location managers to keep in sync; NavigationService doesn’t have the ability to respond to two different location managers at the same time. I don’t disagree with the notification being added, but it seems like a band-aid atop a more fundamental architectural issue that we should prioritize post-v2.0.0.
9c3f142
to
58eb289
Compare
58eb289
to
149fb97
Compare
269f9b5
to
23d3d53
Compare
Cherry-picked to the release-v2.0 branch in 9fe469e. |
Description
This pr is to fix #3392 and #3276 .
Implementation
To fix the #3392 , the pre-existing
locationProvider
of themapView
during active navigation is stored when simulating start ormapView
set up. When the simulation mode turned off, we use it for location update and override the simulated location provider with it.To fix #3276, the notification of
navigationServiceSimulatingDidChange
is added by theMapboxNavigationService
to notify users of the simulating status change. And in CarPlay we observe this notification to listen to the simulating status change, and behave the same as mobileNavigationViewController
to update thelocationProvider
of themapView
based on the simulating status. In this way, the two background location updates would not result in the unmatched behavior or the Navigator rerouting during simulation mode.Besides, the
simulationSpeedMultiplier
of thenavigationService
change also leads to the update of thesimulatedLocationProvider
for themapView
to allow thelocationProvider
of themapView
be synced with the locationManager during active navigation.Removed the
carPlayManager(_: didBeginNavigationWith:)
delegate method in CarPlayManager and move it toCarPlayNavigationViewController
. In fact the navigation service starts in CarPlay when the navigation view loaded inCarPlayNavigationViewController
.