Skip to content

Commit 970bd39

Browse files
committed
Fix ReplayRouteSession route state
1 parent e1ae50f commit 970bd39

File tree

4 files changed

+174
-51
lines changed

4 files changed

+174
-51
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/ReplayPolylineDecodeStream.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,21 @@ class ReplayPolylineDecodeStream(
9494
}
9595
return points
9696
}
97+
98+
/**
99+
* Skip the next [minDistance] of the geometry. Similar to decode except that this does not
100+
* hold any points in memory.
101+
*/
102+
fun skip(
103+
minDistance: Double,
104+
@TurfConstants.TurfUnitCriteria units: String = TurfConstants.UNIT_KILOMETERS
105+
) {
106+
var travelled = 0.0
107+
current?.let { if (minDistance > 0.0 && hasNext()) next() }
108+
while (travelled < minDistance && hasNext()) {
109+
val previous = current
110+
val next = next()
111+
travelled += previous?.let { TurfMeasurement.distance(previous, next, units) } ?: 0.0
112+
}
113+
}
97114
}

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.skip(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 & 33 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,45 +200,34 @@ 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
217227
}
218228
)
219229
}
220230

221-
@Test
222-
fun `onAttached - should request gps location when resetLocationEnabled is true`() {
223-
every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true
224-
sut.setOptions(ReplayRouteSessionOptions.Builder().locationResetEnabled(true).build())
225-
sut.onAttached(mapboxNavigation)
226-
227-
verifyOrder {
228-
mapboxNavigation.stopTripSession()
229-
mapboxNavigation.startReplayTripSession()
230-
bestLocationEngine.getLastLocation(any())
231-
replayer.play()
232-
}
233-
}
234-
235231
@Test
236232
fun `onAttached - should push gps location when route is not set`() {
237233
val locationCallbackSlot = slot<LocationEngineCallback<LocationEngineResult>>()
@@ -242,6 +238,9 @@ class ReplayRouteSessionTest {
242238

243239
sut.setOptions(ReplayRouteSessionOptions.Builder().locationResetEnabled(true).build())
244240
sut.onAttached(mapboxNavigation)
241+
routesObserver.captured.onRoutesChanged(
242+
mockk { every { navigationRoutes } returns emptyList() }
243+
)
245244
locationCallbackSlot.captured.onSuccess(
246245
mockk {
247246
every { lastLocation } returns mockk(relaxed = true) {
@@ -263,21 +262,115 @@ class ReplayRouteSessionTest {
263262
assertEquals(-2.0, capturedLocation.location.lon, 0.0)
264263
}
265264

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

0 commit comments

Comments
 (0)