Skip to content

Commit 73e4931

Browse files
RingerJKdzinad
authored andcommitted
2.9 fix banner and voice instructions removed on navigator#update leg index
* [cp] NavigatorObserver: fix race condition on invoking TripSession#updateLegIndex * [cp] NavigatorObserver: fix race condition on invoking TripSession#updateLegIndex for voice instructions * [cp] MapboxTripSession: add logs voice and banner instructions * [cp] minor fixes
1 parent e8cf8a3 commit 73e4931

5 files changed

Lines changed: 140 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Mapbox welcomes participation and contributions from everyone.
55
## Unreleased
66
#### Features
77
#### Bug fixes and improvements
8+
- Fixed an issue where `RouteProgress#BannerInstructions` could've become `null` when `MapboxNavigation#updateLegIndex` was called. [#6717](https://github.com/mapbox/mapbox-navigation-android/pull/6717)
9+
- Fixed an issue where `RouteProgress#VoiceInstructions` could've become `null` when `MapboxNavigation#updateLegIndex` was called. [#6717](https://github.com/mapbox/mapbox-navigation-android/pull/6717)
810

911
## Mapbox Navigation SDK 2.9.4 - 08 December, 2022
1012
### Changelog
Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,58 @@
11
package com.mapbox.navigation.core.trip.session
22

33
import com.mapbox.api.directions.v5.models.BannerInstructions
4+
import com.mapbox.navigation.utils.internal.ifNonNull
45

5-
internal class BannerInstructionEvent {
6+
internal class BannerInstructionEvent private constructor() {
67

7-
var latestInstructionIndex: Int? = null
8+
var latestInstructionWrapper: LatestInstructionWrapper? = null
89
private set
910

11+
val latestInstructionIndex: Int?
12+
get() = latestInstructionWrapper?.latestInstructionIndex
13+
14+
val latestBannerInstructions: BannerInstructions?
15+
get() = latestInstructionWrapper?.latestBannerInstructions
16+
1017
var bannerInstructions: BannerInstructions? = null
1118
private set
1219

13-
var latestBannerInstructions: BannerInstructions? = null
14-
private set
20+
companion object {
21+
operator fun invoke(): BannerInstructionEvent = BannerInstructionEvent()
22+
}
1523

1624
fun isOccurring(bannerInstructions: BannerInstructions?, instructionIndex: Int?): Boolean {
1725
return updateCurrentBanner(bannerInstructions, instructionIndex)
1826
}
1927

20-
fun invalidateLatestBannerInstructions() {
21-
latestBannerInstructions = null
22-
latestInstructionIndex = null
28+
fun invalidateLatestBannerInstructions(latestInstructionWrapper: LatestInstructionWrapper?) {
29+
if (latestInstructionWrapper == this.latestInstructionWrapper) {
30+
this.latestInstructionWrapper = null
31+
}
2332
}
2433

2534
private fun updateCurrentBanner(banner: BannerInstructions?, instructionIndex: Int?): Boolean {
2635
bannerInstructions = banner
2736
if (bannerInstructions != null && bannerInstructions!! != latestBannerInstructions) {
28-
latestBannerInstructions = bannerInstructions
29-
latestInstructionIndex = instructionIndex
37+
latestInstructionWrapper =
38+
LatestInstructionWrapper.createOrNull(instructionIndex, bannerInstructions)
3039
return true
3140
}
3241
return false
3342
}
43+
44+
data class LatestInstructionWrapper(
45+
val latestInstructionIndex: Int,
46+
val latestBannerInstructions: BannerInstructions
47+
) {
48+
companion object {
49+
fun createOrNull(
50+
latestInstructionIndex: Int?,
51+
latestBannerInstructions: BannerInstructions?
52+
): LatestInstructionWrapper? =
53+
ifNonNull(latestInstructionIndex, latestBannerInstructions) { idx, instruction ->
54+
LatestInstructionWrapper(idx, instruction)
55+
}
56+
}
57+
}
3458
}

libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/MapboxTripSession.kt

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ internal class MapboxTripSession(
167167
this@MapboxTripSession.primaryRoute = newPrimaryRoute
168168
roadObjects = newPrimaryRoute?.upcomingRoadObjects ?: emptyList()
169169
isOffRoute = false
170-
invalidateLatestInstructions()
170+
invalidateLatestInstructions(
171+
bannerInstructionEvent.latestInstructionWrapper,
172+
lastVoiceInstruction
173+
)
171174
routeProgress = null
172175
}.mapValue {
173176
it.alternatives
@@ -192,7 +195,9 @@ internal class MapboxTripSession(
192195
private val fallbackVersionsObservers = CopyOnWriteArraySet<FallbackVersionsObserver>()
193196

194197
private val bannerInstructionEvent = BannerInstructionEvent()
195-
private var lastVoiceInstruction: VoiceInstructions? = null
198+
199+
@VisibleForTesting
200+
internal var lastVoiceInstruction: VoiceInstructions? = null
196201

197202
private var state: TripSessionState = TripSessionState.STOPPED
198203
set(value) {
@@ -328,6 +333,11 @@ internal class MapboxTripSession(
328333
"state: ${status.routeState}",
329334
LOG_CATEGORY
330335
)
336+
logD(
337+
"navigatorObserver#onStatus; banner instruction: [${status.bannerInstruction}]," +
338+
" voice instruction: [${status.voiceInstruction}]",
339+
LOG_CATEGORY
340+
)
331341

332342
val tripStatus = status.getTripStatusFrom(primaryRoute)
333343
val enhancedLocation = tripStatus.navigationStatus.location.toLocation()
@@ -356,12 +366,13 @@ internal class MapboxTripSession(
356366
)
357367
}
358368
val remainingWaypoints = calculateRemainingWaypoints(tripStatus)
369+
val latestBannerInstructionsWrapper = bannerInstructionEvent.latestInstructionWrapper
359370
val routeProgress = getRouteProgressFrom(
360371
tripStatus.route,
361372
tripStatus.navigationStatus,
362373
remainingWaypoints,
363-
bannerInstructionEvent.latestBannerInstructions,
364-
bannerInstructionEvent.latestInstructionIndex,
374+
latestBannerInstructionsWrapper?.latestBannerInstructions,
375+
latestBannerInstructionsWrapper?.latestInstructionIndex,
365376
lastVoiceInstruction
366377
).also {
367378
if (it == null) {
@@ -603,10 +614,26 @@ internal class MapboxTripSession(
603614
var legIndexUpdated = false
604615
updateLegIndexJob = mainJobController.scope.launch {
605616
try {
617+
fun msg(state: String, append: String = ""): String =
618+
"update to new leg $state. Leg index: $legIndex, route id: " +
619+
"${primaryRoute?.id} + $append"
620+
621+
logD(LOG_CATEGORY, msg("started"))
622+
val latestInstructionWrapper = bannerInstructionEvent.latestInstructionWrapper
623+
val lastVoiceInstruction = lastVoiceInstruction
606624
legIndexUpdated = navigator.updateLegIndex(legIndex)
607625
if (legIndexUpdated) {
608-
invalidateLatestInstructions()
626+
invalidateLatestInstructions(latestInstructionWrapper, lastVoiceInstruction)
609627
}
628+
logD(
629+
msg(
630+
"finished",
631+
"(is leg updated: $legIndexUpdated; " +
632+
"latestInstructionWrapper: [$latestInstructionWrapper]; " +
633+
"lastVoiceInstruction: [$lastVoiceInstruction])"
634+
),
635+
LOG_CATEGORY,
636+
)
610637
} finally {
611638
callback.onLegIndexUpdatedCallback(legIndexUpdated)
612639
}
@@ -713,9 +740,19 @@ internal class MapboxTripSession(
713740
}
714741
}
715742

716-
private fun invalidateLatestInstructions() {
717-
bannerInstructionEvent.invalidateLatestBannerInstructions()
718-
lastVoiceInstruction = null
743+
/**
744+
* Invalidate latest banner and voice instruction. To get the latest banner instruction wrapper call
745+
* [BannerInstructionEvent.latestInstructionWrapper], to get the latest voice instruction
746+
* call [lastVoiceInstruction]
747+
*/
748+
private fun invalidateLatestInstructions(
749+
latestInstructionWrapper: BannerInstructionEvent.LatestInstructionWrapper?,
750+
voiceInstruction: VoiceInstructions?,
751+
) {
752+
bannerInstructionEvent.invalidateLatestBannerInstructions(latestInstructionWrapper)
753+
if (lastVoiceInstruction == voiceInstruction) {
754+
lastVoiceInstruction = null
755+
}
719756
}
720757

721758
private fun checkBannerInstructionEvent(

libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/session/BannerInstructionEventTest.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,31 @@ class BannerInstructionEventTest {
9696
val anyBannerInstructions: BannerInstructions = mockk()
9797
bannerInstructionEvent.isOccurring(anyBannerInstructions, 0)
9898

99-
bannerInstructionEvent.invalidateLatestBannerInstructions()
99+
bannerInstructionEvent
100+
.invalidateLatestBannerInstructions(bannerInstructionEvent.latestInstructionWrapper)
100101

101102
assertNull(bannerInstructionEvent.latestBannerInstructions)
102103
assertNull(bannerInstructionEvent.latestInstructionIndex)
104+
assertNull(bannerInstructionEvent.latestInstructionWrapper)
105+
}
106+
107+
@Test
108+
fun invalidateNonExistingLatestBannerInstructions() {
109+
val bannerInstructionEvent = BannerInstructionEvent()
110+
val anyBannerInstructions: BannerInstructions = mockk()
111+
bannerInstructionEvent.isOccurring(anyBannerInstructions, 0)
112+
113+
bannerInstructionEvent
114+
.invalidateLatestBannerInstructions(mockk())
115+
116+
assertEquals(
117+
BannerInstructionEvent.LatestInstructionWrapper(0, anyBannerInstructions),
118+
bannerInstructionEvent.latestInstructionWrapper
119+
)
120+
assertEquals(
121+
anyBannerInstructions,
122+
bannerInstructionEvent.latestBannerInstructions
123+
)
124+
assertEquals(0, bannerInstructionEvent.latestInstructionIndex,)
103125
}
104126
}

libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/session/MapboxTripSessionTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import io.mockk.Runs
4747
import io.mockk.clearMocks
4848
import io.mockk.coEvery
4949
import io.mockk.coVerify
50+
import io.mockk.coVerifyOrder
5051
import io.mockk.every
5152
import io.mockk.just
5253
import io.mockk.mockk
@@ -130,6 +131,7 @@ class MapboxTripSessionTest {
130131
private val navigationStatus: NavigationStatus = mockk(relaxed = true)
131132
private val routeProgress: RouteProgress = mockk()
132133
private val threadController = spyk<ThreadController>()
134+
private val bannerInstructionEvent = spyk(BannerInstructionEvent())
133135

134136
private val parentJob = SupervisorJob()
135137
private val testScope = CoroutineScope(parentJob + coroutineRule.testDispatcher)
@@ -148,11 +150,13 @@ class MapboxTripSessionTest {
148150
mockkObject(MapboxNativeNavigatorImpl)
149151
mockkStatic("com.mapbox.navigation.core.navigator.NavigatorMapper")
150152
mockkStatic("com.mapbox.navigation.core.navigator.LocationEx")
153+
mockkObject(BannerInstructionEvent.Companion)
151154
mockkObject(RoadObjectFactory)
152155
every { location.toFixLocation() } returns fixLocation
153156
every { fixLocation.toLocation() } returns location
154157
every { keyFixPoints.toLocations() } returns keyPoints
155158
every { threadController.getMainScopeAndRootJob() } returns JobControl(parentJob, testScope)
159+
every { BannerInstructionEvent.invoke() } returns bannerInstructionEvent
156160
navigationOptions = NavigationOptions.Builder(context).build()
157161
tripSession = buildTripSession()
158162

@@ -1634,12 +1638,45 @@ class MapboxTripSessionTest {
16341638
}
16351639
}
16361640

1641+
/*
1642+
navigator.updateLegIndex(legIndex) might force invoking NavigatorObserver and one adds
1643+
new latest banner instructions to BannerInstructionEvent. That's why requires pre-save
1644+
latest banner instructions to remove legacy instructions only
1645+
*/
1646+
@Test
1647+
fun `updateLegIndex latest banner and voice instructions pre-save to later clean up them`() =
1648+
coroutineRule.runBlockingTest {
1649+
val idx = -1
1650+
val mockLatestInstructionWrapper =
1651+
mockk<BannerInstructionEvent.LatestInstructionWrapper>()
1652+
every {
1653+
bannerInstructionEvent.latestInstructionWrapper
1654+
} returns mockLatestInstructionWrapper
1655+
coEvery { navigator.updateLegIndex(idx) } coAnswers {
1656+
true
1657+
}
1658+
val mockVoiceInstruction = mockk<VoiceInstructions>()
1659+
1660+
tripSession.lastVoiceInstruction = mockVoiceInstruction
1661+
tripSession.updateLegIndex(idx, mockk(relaxUnitFun = true))
1662+
1663+
coVerifyOrder {
1664+
bannerInstructionEvent.latestInstructionWrapper
1665+
navigator.updateLegIndex(idx)
1666+
bannerInstructionEvent
1667+
.invalidateLatestBannerInstructions(mockLatestInstructionWrapper)
1668+
tripSession.lastVoiceInstruction = null
1669+
}
1670+
assertNull(tripSession.lastVoiceInstruction)
1671+
}
1672+
16371673
@After
16381674
fun cleanUp() {
16391675
unmockkObject(MapboxNativeNavigatorImpl)
16401676
unmockkStatic("com.mapbox.navigation.core.navigator.NavigatorMapper")
16411677
unmockkStatic("com.mapbox.navigation.core.navigator.LocationEx")
16421678
unmockkObject(RoadObjectFactory)
1679+
unmockkObject(BannerInstructionEvent.Companion)
16431680
}
16441681

16451682
private fun mockLocation(): Location = mockk(relaxed = true)

0 commit comments

Comments
 (0)