Skip to content

Commit

Permalink
start replaying route on route updates if not playing now
Browse files Browse the repository at this point in the history
  • Loading branch information
dzinad committed Jun 23, 2023
1 parent 63f2408 commit 5048fd7
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 26 deletions.
1 change: 1 addition & 0 deletions changelog/unreleased/bugfixes/dd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fixed an issue where RouteReplaySession might not have started playing a route if it was created approximately at the same time when routes were set to `MapboxNavigation`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.mapbox.navigation.instrumentation_tests.core

import android.location.Location
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.base.route.RouteRefreshOptions
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.MapboxNavigationProvider
import com.mapbox.navigation.core.directions.session.RoutesExtra
import com.mapbox.navigation.core.replay.route.ReplayRouteSession
import com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions
import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider
import com.mapbox.navigation.instrumentation_tests.utils.routes.RoutesProvider.toNavigationRoutes
import com.mapbox.navigation.testing.ui.BaseCoreNoCleanUpTest
import com.mapbox.navigation.testing.ui.utils.MapboxNavigationRule
import com.mapbox.navigation.testing.ui.utils.coroutines.routeProgressUpdates
import com.mapbox.navigation.testing.ui.utils.coroutines.routesUpdates
import com.mapbox.navigation.testing.ui.utils.coroutines.sdkTest
import com.mapbox.navigation.testing.ui.utils.coroutines.setNavigationRoutesAndWaitForUpdate
import com.mapbox.navigation.testing.ui.utils.getMapboxAccessTokenFromResources
import com.mapbox.navigation.testing.ui.utils.runOnMainSync
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.TimeUnit

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class ReplayRouteSessionTest : BaseCoreNoCleanUpTest() {

@get:Rule
val mapboxNavigationRule = MapboxNavigationRule()
private lateinit var mapboxNavigation: MapboxNavigation
private lateinit var replayRouteSession: ReplayRouteSession

override fun setupMockLocation(): Location {
return mockLocationUpdatesRule.generateLocationUpdate {
this.latitude = 0.0
this.longitude = 0.0
}
}

@Before
fun setUp() {
runOnMainSync {
replayRouteSession = ReplayRouteSession()
replayRouteSession.setOptions(
ReplayRouteSessionOptions.Builder().locationResetEnabled(false).build()
)
}
}

@After
fun tearDown() {
runOnMainSync {
replayRouteSession.onDetached(mapboxNavigation)
}
}

@Test
fun routeIsPlayedIfNoLocationUpdatesHappenedBefore() = sdkTest {
mapboxNavigation = MapboxNavigationProvider.create(
NavigationOptions.Builder(context)
.accessToken(getMapboxAccessTokenFromResources(context))
.build()
)
val routes = RoutesProvider.dc_very_short(context)

replayRouteSession.onAttached(mapboxNavigation)
mapboxNavigation.setNavigationRoutesAndWaitForUpdate(routes.toNavigationRoutes())

val routeProgressUpdates = mapboxNavigation.routeProgressUpdates().take(5).toList()
val lastUpdate = routeProgressUpdates.last()
val firstUpdate = routeProgressUpdates.first()
assertTrue(
lastUpdate.distanceTraveled > firstUpdate.distanceTraveled
)
}

@Test
fun routeIsPlayedFromCurrentPositionAfterRefresh() = sdkTest {
mapboxNavigation = MapboxNavigationProvider.create(
NavigationOptions.Builder(context)
.accessToken(getMapboxAccessTokenFromResources(context))
.routeRefreshOptions(routeRefreshOptions(3000))
.build()
)
val routes = RoutesProvider.dc_short_with_alternative_reroute(context).toNavigationRoutes()

replayRouteSession.onAttached(mapboxNavigation)
mapboxNavigation.setNavigationRoutesAndWaitForUpdate(routes)
mapboxNavigation.routeProgressUpdates()
.filter { it.currentRouteGeometryIndex >= 4 }
.first()

mapboxNavigation.routeProgressUpdates().filter {
it.currentRouteGeometryIndex == 4
}.first()

mapboxNavigation.routesUpdates()
.filter { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_REFRESH }
.first()

val routeProgressUpdates = mapboxNavigation.routeProgressUpdates().take(2).toList()
assertTrue(routeProgressUpdates.all { it.currentRouteGeometryIndex >= 4 })
}

private fun routeRefreshOptions(intervalMillis: Long): RouteRefreshOptions {
val routeRefreshOptions = RouteRefreshOptions.Builder()
.intervalMillis(TimeUnit.SECONDS.toMillis(30))
.build()
RouteRefreshOptions::class.java.getDeclaredField("intervalMillis").apply {
isAccessible = true
set(routeRefreshOptions, intervalMillis)
}
return routeRefreshOptions
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,8 @@ class MapboxReplayer {
unregisterObservers()
clearEvents()
}

internal fun isPlaying(): Boolean {
return replayEventSimulator.isPlaying()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal class ReplayEventSimulator(
private var simulatorTimeOffset: Double = 0.0
private var simulatorTimeScale: Double = 1.0

private var currentJob: Job? = null
private var pivotIndex = 0
private var clearingPlayedEvents = false

Expand All @@ -40,6 +41,8 @@ internal class ReplayEventSimulator(
simulateEvents(replayEventsCallback)
}
}
}.also {
currentJob = it
}
}

Expand Down Expand Up @@ -92,6 +95,10 @@ internal class ReplayEventSimulator(
resetSimulatorClock()
}

fun isPlaying(): Boolean {
return currentJob?.isActive == true && !isDonePlayingEvents()
}

private fun resetSimulatorClock() {
simulatorTimeOffset = timeSeconds()
historyTimeOffset = if (isDonePlayingEvents()) {
Expand Down Expand Up @@ -129,7 +136,7 @@ internal class ReplayEventSimulator(
}

private fun isDonePlayingEvents(): Boolean {
return pivotIndex >= replayEvents.events.size
return (pivotIndex >= replayEvents.events.size)
}

private fun timeSeconds(): Double {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.mapbox.android.core.permissions.PermissionsManager
import com.mapbox.api.directions.v5.DirectionsCriteria
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.route.NavigationRoute
import com.mapbox.navigation.base.trip.model.RouteProgress
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.directions.session.RoutesObserver
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
Expand Down Expand Up @@ -65,7 +64,7 @@ class ReplayRouteSession : MapboxNavigationObserver {
private val routeProgressObserver = RouteProgressObserver { routeProgress ->
if (currentRoute?.id != routeProgress.navigationRoute.id) {
currentRoute = routeProgress.navigationRoute
onRouteChanged(routeProgress)
onRouteChanged(routeProgress.navigationRoute, routeProgress.currentRouteGeometryIndex)
}
}

Expand All @@ -74,6 +73,14 @@ class ReplayRouteSession : MapboxNavigationObserver {
mapboxNavigation?.resetReplayLocation()
currentRoute = null
polylineDecodeStream = null
} else if (mapboxNavigation?.mapboxReplayer?.isPlaying() != true) {
// In order to get route progress updates, we need location updates.
// If we don't have any location updates, we don't get route progress updates
// and we'll never start navigating the route.
// If we have location updates, we'll update the route from RouteProgressObserver,
// because it has more information, e. g. current route geometry index.
currentRoute = result.navigationRoutes.first()
onRouteChanged(result.navigationRoutes.first(), 0)
}
}

Expand Down Expand Up @@ -143,8 +150,7 @@ class ReplayRouteSession : MapboxNavigationObserver {
this.currentRoute = null
}

private fun onRouteChanged(routeProgress: RouteProgress) {
val navigationRoute = routeProgress.navigationRoute
private fun onRouteChanged(navigationRoute: NavigationRoute, currentIndex: Int) {
val mapboxReplayer = mapboxNavigation?.mapboxReplayer ?: return
mapboxReplayer.clearEvents()
mapboxReplayer.play()
Expand All @@ -162,7 +168,7 @@ class ReplayRouteSession : MapboxNavigationObserver {

// Skip up to the current geometry index. There is some imprecision here because the
// distance traveled is not equal to a route index.
polylineDecodeStream?.skip(routeProgress.currentRouteGeometryIndex)
polylineDecodeStream?.skip(currentIndex)

pushMorePoints()
}
Expand Down
Loading

0 comments on commit 5048fd7

Please sign in to comment.