Skip to content

Commit ce47aaf

Browse files
committed
Fix ReplayRouteSession route state
1 parent e1ae50f commit ce47aaf

File tree

3 files changed

+157
-37
lines changed

3 files changed

+157
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Mapbox welcomes participation and contributions from everyone.
55
## Unreleased
66
#### Features
77
#### Bug fixes and improvements
8+
- Fixed issues in `ReplayRouteSession`. The routes observer was never unregistered. Alternative route selection resets at the beginning. DropInUi changing Portrait and Landscape modes resets at the beginning. [#6675](https://github.com/mapbox/mapbox-navigation-android/pull/6675)
89

910
## Mapbox Navigation SDK 2.10.0-beta.2 - 01 December, 2022
1011
### Changelog

libnavigation-core/src/main/java/com/mapbox/navigation/core/replay/route/ReplayRouteSession.kt

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.mapbox.android.core.permissions.PermissionsManager
99
import com.mapbox.api.directions.v5.DirectionsCriteria
1010
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
1111
import com.mapbox.navigation.base.route.NavigationRoute
12+
import com.mapbox.navigation.base.trip.model.RouteProgress
1213
import com.mapbox.navigation.core.MapboxNavigation
1314
import com.mapbox.navigation.core.directions.session.RoutesObserver
1415
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
@@ -17,8 +18,10 @@ import com.mapbox.navigation.core.replay.MapboxReplayer
1718
import com.mapbox.navigation.core.replay.history.ReplayEventBase
1819
import com.mapbox.navigation.core.replay.history.ReplayEventUpdateLocation
1920
import com.mapbox.navigation.core.replay.history.ReplayEventsObserver
21+
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
2022
import com.mapbox.navigation.utils.internal.logW
21-
import java.util.Collections
23+
import com.mapbox.turf.TurfConstants
24+
import java.util.*
2225

2326
/**
2427
* Used to create a replay trip session. Continue to use [MapboxNavigation.setNavigationRoutes] to
@@ -58,11 +61,23 @@ class ReplayRouteSession : MapboxNavigationObserver {
5861
private lateinit var replayRouteMapper: ReplayRouteMapper
5962
private var mapboxNavigation: MapboxNavigation? = null
6063
private var lastLocationEvent: ReplayEventUpdateLocation? = null
61-
private var routesObserver: RoutesObserver? = null
62-
private var currentRouteId: String? = null
64+
private var currentRoute: NavigationRoute? = null
65+
66+
private val routeProgressObserver = RouteProgressObserver { routeProgress ->
67+
if (currentRoute != routeProgress.navigationRoute) {
68+
currentRoute = routeProgress.navigationRoute
69+
onRouteChanged(routeProgress)
70+
}
71+
}
72+
73+
private val routesObserver = RoutesObserver { result ->
74+
if (result.navigationRoutes.isEmpty()) {
75+
mapboxNavigation?.resetReplayLocation()
76+
}
77+
}
6378

6479
private val replayEventsObserver = ReplayEventsObserver { events ->
65-
if (isLastEventPlayed(events)) {
80+
if (currentRoute != null && isLastEventPlayed(events)) {
6681
pushMorePoints()
6782
}
6883
}
@@ -92,17 +107,10 @@ class ReplayRouteSession : MapboxNavigationObserver {
92107
this.mapboxNavigation = mapboxNavigation
93108
mapboxNavigation.stopTripSession()
94109
mapboxNavigation.startReplayTripSession()
95-
96-
routesObserver = RoutesObserver { result ->
97-
if (result.navigationRoutes.isEmpty()) {
98-
currentRouteId = null
99-
mapboxNavigation.resetReplayLocation()
100-
} else if (result.navigationRoutes.first().id != currentRouteId) {
101-
onRouteChanged(result.navigationRoutes.first())
102-
}
103-
}.also { mapboxNavigation.registerRoutesObserver(it) }
110+
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
111+
mapboxNavigation.registerRoutesObserver(routesObserver)
104112
mapboxNavigation.mapboxReplayer.registerObserver(replayEventsObserver)
105-
mapboxNavigation.resetReplayLocation()
113+
mapboxNavigation.mapboxReplayer.play()
106114
}
107115

108116
private fun MapboxNavigation.resetReplayLocation() {
@@ -123,14 +131,18 @@ class ReplayRouteSession : MapboxNavigationObserver {
123131
}
124132

125133
override fun onDetached(mapboxNavigation: MapboxNavigation) {
126-
this.mapboxNavigation = null
134+
mapboxNavigation.unregisterRoutesObserver(routesObserver)
135+
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
127136
mapboxNavigation.mapboxReplayer.unregisterObserver(replayEventsObserver)
128137
mapboxNavigation.mapboxReplayer.stop()
129138
mapboxNavigation.mapboxReplayer.clearEvents()
130139
mapboxNavigation.stopTripSession()
140+
this.mapboxNavigation = null
141+
this.currentRoute = null
131142
}
132143

133-
private fun onRouteChanged(navigationRoute: NavigationRoute) {
144+
private fun onRouteChanged(routeProgress: RouteProgress) {
145+
val navigationRoute = routeProgress.navigationRoute
134146
val mapboxReplayer = mapboxNavigation?.mapboxReplayer ?: return
135147
mapboxReplayer.clearEvents()
136148
mapboxReplayer.play()
@@ -144,9 +156,9 @@ class ReplayRouteSession : MapboxNavigationObserver {
144156
}
145157
return
146158
}
147-
currentRouteId = navigationRoute.id
159+
val distanceTraveled = routeProgress.distanceTraveled.toDouble()
148160
polylineDecodeStream = ReplayPolylineDecodeStream(geometry, 6)
149-
mapboxNavigation?.resetTripSession()
161+
polylineDecodeStream.decode(distanceTraveled, TurfConstants.UNIT_METERS)
150162
pushMorePoints()
151163
}
152164

libnavigation-core/src/test/java/com/mapbox/navigation/core/replay/route/ReplayRouteSessionTest.kt

Lines changed: 126 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@ import com.mapbox.geojson.Point
1010
import com.mapbox.geojson.utils.PolylineUtils
1111
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
1212
import com.mapbox.navigation.base.options.NavigationOptions
13+
import com.mapbox.navigation.base.route.NavigationRoute
14+
import com.mapbox.navigation.base.trip.model.RouteProgress
1315
import com.mapbox.navigation.core.MapboxNavigation
1416
import com.mapbox.navigation.core.directions.session.RoutesObserver
1517
import com.mapbox.navigation.core.directions.session.RoutesUpdatedResult
1618
import com.mapbox.navigation.core.replay.MapboxReplayer
1719
import com.mapbox.navigation.core.replay.history.ReplayEventBase
1820
import com.mapbox.navigation.core.replay.history.ReplayEventUpdateLocation
1921
import com.mapbox.navigation.core.replay.history.ReplayEventsObserver
22+
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
2023
import com.mapbox.navigation.testing.LoggingFrontendTestRule
24+
import com.mapbox.turf.TurfConstants
25+
import com.mapbox.turf.TurfMeasurement
2126
import io.mockk.every
2227
import io.mockk.just
2328
import io.mockk.mockk
@@ -47,9 +52,13 @@ class ReplayRouteSessionTest {
4752
private val options: NavigationOptions = mockk {
4853
every { applicationContext } returns context
4954
}
55+
private val routesObserver = slot<RoutesObserver>()
56+
private val routeProgressObserver = slot<RouteProgressObserver>()
5057
private val mapboxNavigation: MapboxNavigation = mockk(relaxed = true) {
5158
every { mapboxReplayer } returns replayer
5259
every { navigationOptions } returns options
60+
every { registerRoutesObserver(capture(routesObserver)) } just runs
61+
every { registerRouteProgressObserver(capture(routeProgressObserver)) } just runs
5362
}
5463
private val bestLocationEngine: LocationEngine = mockk {
5564
every { getLastLocation(any()) } just runs
@@ -166,16 +175,14 @@ class ReplayRouteSessionTest {
166175

167176
@Test
168177
fun `onAttached - should push the initial batch of events`() {
169-
val routesObserver = slot<RoutesObserver>()
170-
every { mapboxNavigation.registerRoutesObserver(capture(routesObserver)) } just runs
171178
sut.setOptions(
172179
ReplayRouteSessionOptions.Builder()
173180
.decodeMinDistance(1.0)
174181
.build()
175182
)
176183

177184
sut.onAttached(mapboxNavigation)
178-
routesObserver.captured.onRoutesChanged(mockActiveNavigationRoutes())
185+
routeProgressObserver.captured.onRouteProgressChanged(mockRouteProgress(0.0))
179186

180187
val pushedEvents = slot<List<ReplayEventBase>>()
181188
verify { replayer.pushEvents(capture(pushedEvents)) }
@@ -193,24 +200,27 @@ class ReplayRouteSessionTest {
193200
replayEventsObserver.captured.replayEvents(firstArg())
194201
replayer
195202
}
196-
val routesUpdatedResult = mockActiveNavigationRoutes()
203+
val routeProgress = mockRouteProgress(0.0)
197204

198205
sut.setOptions(
199206
ReplayRouteSessionOptions.Builder()
200207
.decodeMinDistance(0.001)
201208
.build()
202209
)
203210
sut.onAttached(mapboxNavigation)
204-
routesObserver.captured.onRoutesChanged(routesUpdatedResult)
211+
routeProgressObserver.captured.onRouteProgressChanged(routeProgress)
205212

206213
// Verify every point in the geometry was simulated
207214
val pushedPoints = pushedEvents.flatten().toList().map {
208215
val location = (it as ReplayEventUpdateLocation).location
209216
Point.fromLngLat(location.lon, location.lat)
210217
}
211-
val geometry = routesUpdatedResult.navigationRoutes.first().directionsRoute.geometry()!!
218+
val geometry = routeProgress.navigationRoute.directionsRoute.geometry()!!
212219
val geometryPoints = PolylineUtils.decode(geometry, 6)
213-
assertTrue(pushedPoints.size > geometryPoints.size)
220+
assertTrue(
221+
"${pushedPoints.size} > ${geometryPoints.size}",
222+
pushedPoints.size > geometryPoints.size
223+
)
214224
assertTrue(
215225
geometryPoints.all { lhs ->
216226
pushedPoints.firstOrNull { rhs -> lhs.equals(rhs) } != null
@@ -242,6 +252,9 @@ class ReplayRouteSessionTest {
242252

243253
sut.setOptions(ReplayRouteSessionOptions.Builder().locationResetEnabled(true).build())
244254
sut.onAttached(mapboxNavigation)
255+
routesObserver.captured.onRoutesChanged(
256+
mockk { every { navigationRoutes } returns emptyList() }
257+
)
245258
locationCallbackSlot.captured.onSuccess(
246259
mockk {
247260
every { lastLocation } returns mockk(relaxed = true) {
@@ -263,21 +276,115 @@ class ReplayRouteSessionTest {
263276
assertEquals(-2.0, capturedLocation.location.lon, 0.0)
264277
}
265278

266-
private fun mockActiveNavigationRoutes(): RoutesUpdatedResult = mockk {
279+
@Test
280+
fun `onAttached registered listeners should be unregistered onDetached`() {
281+
val progressObserver = slot<RouteProgressObserver>()
282+
val routesObserver = slot<RoutesObserver>()
283+
val replayEventsObserver = slot<ReplayEventsObserver>()
284+
every { mapboxNavigation.registerRoutesObserver(capture(routesObserver)) } just runs
285+
every {
286+
mapboxNavigation.registerRouteProgressObserver(capture(progressObserver))
287+
} just runs
288+
every { replayer.registerObserver(capture(replayEventsObserver)) } just runs
289+
290+
sut.onAttached(mapboxNavigation)
291+
sut.onDetached(mapboxNavigation)
292+
293+
verifyOrder {
294+
mapboxNavigation.registerRouteProgressObserver(any())
295+
mapboxNavigation.registerRoutesObserver(any())
296+
replayer.registerObserver(any())
297+
mapboxNavigation.unregisterRoutesObserver(routesObserver.captured)
298+
mapboxNavigation.unregisterRouteProgressObserver(progressObserver.captured)
299+
replayer.unregisterObserver(replayEventsObserver.captured)
300+
}
301+
}
302+
303+
@Test
304+
fun `onAttached - should skip to the current routeProgress distanceTraveled`() {
305+
val progressObserver = slot<RouteProgressObserver>()
306+
val routesObserver = slot<RoutesObserver>()
307+
every {
308+
mapboxNavigation.registerRouteProgressObserver(capture(progressObserver))
309+
} just runs
310+
every { mapboxNavigation.registerRoutesObserver(capture(routesObserver)) } just runs
311+
val activeRoutes = mockActiveRoutesUpdatedResult()
312+
val primaryRoute = activeRoutes.navigationRoutes.first()
313+
314+
sut.onAttached(mapboxNavigation)
315+
progressObserver.captured.onRouteProgressChanged(
316+
mockk {
317+
every { navigationRoute } returns primaryRoute
318+
every { distanceTraveled } returns 100.0f
319+
}
320+
)
321+
routesObserver.captured.onRoutesChanged(activeRoutes)
322+
323+
val pushedEvents = slot<List<ReplayEventBase>>()
324+
verify { replayer.pushEvents(capture(pushedEvents)) }
325+
verifyDistanceTraveled(pushedEvents.captured, primaryRoute, 100.0)
326+
}
327+
328+
@Test
329+
fun `onAttached - should skip to short routeProgress distanceTraveled`() {
330+
val progressObserver = slot<RouteProgressObserver>()
331+
val routesObserver = slot<RoutesObserver>()
332+
every {
333+
mapboxNavigation.registerRouteProgressObserver(capture(progressObserver))
334+
} just runs
335+
every { mapboxNavigation.registerRoutesObserver(capture(routesObserver)) } just runs
336+
val activeRoutes = mockActiveRoutesUpdatedResult()
337+
val primaryRoute = activeRoutes.navigationRoutes.first()
338+
339+
sut.onAttached(mapboxNavigation)
340+
progressObserver.captured.onRouteProgressChanged(
341+
mockk {
342+
every { navigationRoute } returns primaryRoute
343+
every { distanceTraveled } returns 40.0f
344+
}
345+
)
346+
routesObserver.captured.onRoutesChanged(activeRoutes)
347+
348+
val pushedEvents = slot<List<ReplayEventBase>>()
349+
verify { replayer.pushEvents(capture(pushedEvents)) }
350+
verifyDistanceTraveled(pushedEvents.captured, primaryRoute, 40.0)
351+
}
352+
353+
private fun verifyDistanceTraveled(
354+
pushedEvents: List<ReplayEventBase>,
355+
primaryRoute: NavigationRoute,
356+
distanceTraveled: Double
357+
) {
358+
val geometry = primaryRoute.directionsRoute.geometry()!!
359+
val fullRoute = PolylineUtils.decode(geometry, 6)
360+
val expected = TurfMeasurement.along(fullRoute, distanceTraveled, TurfConstants.UNIT_METERS)
361+
val firstReplayLocation = (pushedEvents.first() as ReplayEventUpdateLocation).location
362+
val firstReplayPoint = Point.fromLngLat(firstReplayLocation.lon, firstReplayLocation.lat)
363+
val distanceToPoint = TurfMeasurement.distance(expected, firstReplayPoint)
364+
365+
assertTrue("1.0 > $distanceToPoint", 1.0 > distanceToPoint)
366+
}
367+
368+
private fun mockActiveRoutesUpdatedResult(): RoutesUpdatedResult = mockk {
369+
every { navigationRoutes } returns listOf(mockNavigationRoute())
370+
}
371+
372+
private fun mockRouteProgress(traveled: Double): RouteProgress = mockk {
373+
every { navigationRoute } returns mockNavigationRoute()
374+
every { distanceTraveled } returns traveled.toFloat()
375+
}
376+
377+
private fun mockNavigationRoute(): NavigationRoute = mockk {
267378
val geometry = "_kmbgAppafhFwXaOuC}ApAoEbNqe@jAaEhEcOtAwEdAoD`DaLnCiJ|M_e@`Je[rAyEnEgO" +
268379
"tGiUxByHlDjBp@^zKdG`Ah@`HtDx@d@rGlDl@\\pAp@dAl@p@^nItEpQvJfAh@fDjB`D`Br@`@nKpFbDhB" +
269380
"~KlGtDvBvAwE|EqPzFeSvHaXtA{ElAiE|@_D"
270-
every { navigationRoutes } returns listOf(
271-
mockk {
272-
every { id } returns "test-navigation-route-id"
273-
every { routeOptions }
274-
every { directionsRoute } returns mockk {
275-
every { routeOptions() } returns mockk {
276-
every { geometries() } returns "polyline6"
277-
every { geometry() } returns geometry
278-
}
279-
}
381+
every { id } returns "test-navigation-route-id"
382+
every { routeOptions }
383+
every { directionsRoute } returns mockk {
384+
every { routeOptions() } returns mockk {
385+
every { geometries() } returns "polyline6"
386+
every { geometry() } returns geometry
280387
}
281-
)
388+
}
282389
}
283390
}

0 commit comments

Comments
 (0)