diff --git a/changelog/unreleased/features/6794.md b/changelog/unreleased/features/6794.md new file mode 100644 index 00000000000..01923b534f4 --- /dev/null +++ b/changelog/unreleased/features/6794.md @@ -0,0 +1 @@ +- Added `MapboxTripStarter` to simplify the solution for managing the trip session and replaying routes. This also makes it possible to share the replay state between drop-in-ui and android-auto. diff --git a/libnavigation-core/api/current.txt b/libnavigation-core/api/current.txt index a11b0c34ac1..a44335082fc 100644 --- a/libnavigation-core/api/current.txt +++ b/libnavigation-core/api/current.txt @@ -32,6 +32,7 @@ package com.mapbox.navigation.core { method public com.mapbox.navigation.core.trip.session.TripSessionState getTripSessionState(); method public Integer? getZLevel(); method public boolean isDestroyed(); + method @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public boolean isReplayEnabled(); method public boolean isRunningForegroundService(); method @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI @kotlin.jvm.Throws(exceptionClasses=IllegalArgumentException::class) public void moveRoutesFromPreviewToNavigator() throws java.lang.IllegalArgumentException; method public void navigateNextRouteLeg(com.mapbox.navigation.core.trip.session.LegIndexUpdatedCallback callback); @@ -1021,6 +1022,27 @@ package com.mapbox.navigation.core.telemetry.events { } +package com.mapbox.navigation.core.trip { + + @com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class MapboxTripStarter implements com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver { + method public static com.mapbox.navigation.core.trip.MapboxTripStarter create(); + method public com.mapbox.navigation.core.trip.MapboxTripStarter enableMapMatching(); + method public com.mapbox.navigation.core.trip.MapboxTripStarter enableReplayRoute(com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions? options = null); + method public static com.mapbox.navigation.core.trip.MapboxTripStarter getRegisteredInstance(); + method public com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions getReplayRouteSessionOptions(); + method public void onAttached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation); + method public void onDetached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation); + method public com.mapbox.navigation.core.trip.MapboxTripStarter refreshLocationPermissions(); + field public static final com.mapbox.navigation.core.trip.MapboxTripStarter.Companion Companion; + } + + public static final class MapboxTripStarter.Companion { + method public com.mapbox.navigation.core.trip.MapboxTripStarter create(); + method public com.mapbox.navigation.core.trip.MapboxTripStarter getRegisteredInstance(); + } + +} + package com.mapbox.navigation.core.trip.session { public fun interface BannerInstructionsObserver { diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt index cd80d8a5ce9..5d536be3dfa 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/MapboxNavigation.kt @@ -593,6 +593,15 @@ class MapboxNavigation @VisibleForTesting internal constructor( @ExperimentalPreviewMapboxNavigationAPI val mapboxReplayer: MapboxReplayer by lazy { tripSessionLocationEngine.mapboxReplayer } + /** + * True when [startReplayTripSession] has been called. + * Will be false after [stopTripSession] is called. + */ + @ExperimentalPreviewMapboxNavigationAPI + fun isReplayEnabled(): Boolean { + return tripSessionLocationEngine.isReplayEnabled + } + /** * Starts listening for location updates and enters an `Active Guidance` state if there's a primary route available * or a `Free Drive` state otherwise. diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/MapboxTripStarter.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/MapboxTripStarter.kt new file mode 100644 index 00000000000..1ca87c4df7e --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/MapboxTripStarter.kt @@ -0,0 +1,203 @@ +package com.mapbox.navigation.core.trip + +import android.annotation.SuppressLint +import com.mapbox.android.core.permissions.PermissionsManager +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.core.MapboxNavigation +import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp +import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver +import com.mapbox.navigation.core.replay.route.ReplayRouteSession +import com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions +import com.mapbox.navigation.core.trip.MapboxTripStarter.Companion.getRegisteredInstance +import com.mapbox.navigation.utils.internal.logI +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +/** + * The [MapboxTripStarter] makes it simpler to switch between a trip session and replay. + * + * This is not able to observe when location permissions change, so you may need to refresh the + * state with [refreshLocationPermissions]. Location permissions are not required for replay. + * + * There should be one instance of this class at a time. For example, an app Activity and car + * Session will need to use the same instance. That will be done automatically if you use + * [getRegisteredInstance]. + */ +@ExperimentalPreviewMapboxNavigationAPI +class MapboxTripStarter internal constructor() : MapboxNavigationObserver { + + private val tripType = MutableStateFlow( + MapboxTripStarterType.MapMatching + ) + private val replayRouteSessionOptions = MutableStateFlow( + ReplayRouteSessionOptions.Builder().build() + ) + private val isLocationPermissionGranted = MutableStateFlow(false) + private var replayRouteTripSession: ReplayRouteSession? = null + private var mapboxNavigation: MapboxNavigation? = null + + private lateinit var coroutineScope: CoroutineScope + + /** + * Signals that the [mapboxNavigation] instance is ready for use. + * + * @param mapboxNavigation + */ + override fun onAttached(mapboxNavigation: MapboxNavigation) { + this.mapboxNavigation = mapboxNavigation + coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + + // Initialize the options to be aware of the location permissions + val context = mapboxNavigation.navigationOptions.applicationContext + val granted = PermissionsManager.areLocationPermissionsGranted(context) + isLocationPermissionGranted.value = granted + + // Observe changes to state + observeStateFlow(mapboxNavigation).launchIn(coroutineScope) + } + + /** + * Signals that the [mapboxNavigation] instance is being detached. + * + * @param mapboxNavigation + */ + override fun onDetached(mapboxNavigation: MapboxNavigation) { + coroutineScope.cancel() + onTripDisabled(mapboxNavigation) + this.mapboxNavigation = null + } + + /** + * [enableMapMatching] will not work unless location permissions have been granted. Refresh + * the location permissions after they are granted to ensure the trip session will start. + */ + fun refreshLocationPermissions() = apply { + mapboxNavigation?.navigationOptions?.applicationContext?.let { context -> + val granted = PermissionsManager.areLocationPermissionsGranted(context) + isLocationPermissionGranted.value = granted + } + } + + /** + * This is the default mode for the [MapboxTripStarter]. This can be used to disable + * [enableReplayRoute]. Make sure location permissions have been accepted or this will have no + * effect on the experience. + */ + fun enableMapMatching() = apply { + if (!isLocationPermissionGranted.value) { + refreshLocationPermissions() + } + tripType.value = MapboxTripStarterType.MapMatching + } + + /** + * Get the current [ReplayRouteSessionOptions]. This can be used with [enableReplayRoute] to + * make minor adjustments to the current options. + */ + fun getReplayRouteSessionOptions(): ReplayRouteSessionOptions = replayRouteSessionOptions.value + + /** + * Enables a mode where the primary route is simulated by an artificial driver. Set the route + * with [MapboxNavigation.setNavigationRoutes]. Can be used with [getReplayRouteSessionOptions] + * to make minor adjustments to the current options. + * + * @param options optional options to use for route replay. + */ + fun enableReplayRoute( + options: ReplayRouteSessionOptions? = null + ) = apply { + options?.let { options -> replayRouteSessionOptions.value = options } + tripType.value = MapboxTripStarterType.ReplayRoute + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun observeStateFlow(mapboxNavigation: MapboxNavigation): Flow<*> { + return tripType.flatMapLatest { tripType -> + when (tripType) { + MapboxTripStarterType.ReplayRoute -> + replayRouteSessionOptions.onEach { options -> + onReplayTripEnabled(mapboxNavigation, options) + } + MapboxTripStarterType.MapMatching -> + isLocationPermissionGranted.onEach { granted -> + onMapMatchingEnabled(mapboxNavigation, granted) + } + } + } + } + + /** + * Internally called when the trip type has been set to replay route. + * + * @param mapboxNavigation + * @param options parameters for the [ReplayRouteSession] + */ + private fun onReplayTripEnabled( + mapboxNavigation: MapboxNavigation, + options: ReplayRouteSessionOptions + ) { + replayRouteTripSession?.onDetached(mapboxNavigation) + replayRouteTripSession = ReplayRouteSession().also { + it.setOptions(options) + it.onAttached(mapboxNavigation) + } + } + + /** + * Internally called when the trip type has been set to map matching. + * + * @param mapboxNavigation + * @param granted true when location permissions are accepted, false otherwise + */ + @SuppressLint("MissingPermission") + private fun onMapMatchingEnabled(mapboxNavigation: MapboxNavigation, granted: Boolean) { + if (granted) { + replayRouteTripSession?.onDetached(mapboxNavigation) + replayRouteTripSession = null + mapboxNavigation.startTripSession() + } else { + logI(LOG_CATEGORY) { + "startTripSession was not called. Accept location permissions and call " + + "mapboxTripStarter.refreshLocationPermissions()" + } + onTripDisabled(mapboxNavigation) + } + } + + /** + * Internally called when the trip session needs to be stopped. + * + * @param mapboxNavigation + */ + private fun onTripDisabled(mapboxNavigation: MapboxNavigation) { + replayRouteTripSession?.onDetached(mapboxNavigation) + replayRouteTripSession = null + mapboxNavigation.stopTripSession() + } + + companion object { + private const val LOG_CATEGORY = "MapboxTripStarter" + + /** + * Construct an instance without registering to [MapboxNavigationApp]. + */ + @JvmStatic + fun create() = MapboxTripStarter() + + /** + * Get the registered instance or create one and register it to [MapboxNavigationApp]. + */ + @JvmStatic + fun getRegisteredInstance(): MapboxTripStarter = MapboxNavigationApp + .getObservers(MapboxTripStarter::class) + .firstOrNull() ?: MapboxTripStarter().also { MapboxNavigationApp.registerObserver(it) } + } +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/MapboxTripStarterType.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/MapboxTripStarterType.kt new file mode 100644 index 00000000000..0bc5784c670 --- /dev/null +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/MapboxTripStarterType.kt @@ -0,0 +1,17 @@ +package com.mapbox.navigation.core.trip + +/** + * Specifies a trip type for the [MapboxTripStarter]. + */ +internal sealed class MapboxTripStarterType { + + /** + * The [MapboxTripStarter] will use the best device location for a trip session. + */ + object MapMatching : MapboxTripStarterType() + + /** + * The [MapboxTripStarter] will enable replay for the navigation routes. + */ + object ReplayRoute : MapboxTripStarterType() +} diff --git a/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/TripSessionLocationEngine.kt b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/TripSessionLocationEngine.kt index 9dec05a609c..4a72eca90d8 100644 --- a/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/TripSessionLocationEngine.kt +++ b/libnavigation-core/src/main/java/com/mapbox/navigation/core/trip/session/TripSessionLocationEngine.kt @@ -31,6 +31,8 @@ internal class TripSessionLocationEngine constructor( ) { val mapboxReplayer: MapboxReplayer by lazy { MapboxReplayer() } + var isReplayEnabled = false + private set private val replayLocationEngine: LocationEngine by lazy { replayLocationEngineProvider.invoke(mapboxReplayer) @@ -66,6 +68,7 @@ internal class TripSessionLocationEngine constructor( } else { navigationOptions.locationEngine } + this.isReplayEnabled = isReplayEnabled activeLocationEngine?.requestLocationUpdates( navigationOptions.locationEngineRequest, locationEngineCallback, @@ -75,6 +78,7 @@ internal class TripSessionLocationEngine constructor( } fun stopLocationUpdates() { + isReplayEnabled = false onRawLocationUpdate = { } activeLocationEngine?.removeLocationUpdates(locationEngineCallback) activeLocationEngine = null diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/MapboxTripStarterTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/MapboxTripStarterTest.kt new file mode 100644 index 00000000000..45c4f2ce608 --- /dev/null +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/MapboxTripStarterTest.kt @@ -0,0 +1,232 @@ +package com.mapbox.navigation.core.trip + +import com.mapbox.android.core.permissions.PermissionsManager +import com.mapbox.common.LoggingLevel +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.core.MapboxNavigation +import com.mapbox.navigation.core.directions.session.RoutesObserver +import com.mapbox.navigation.core.trip.session.TripSessionState +import com.mapbox.navigation.testing.LoggingFrontendTestRule +import com.mapbox.navigation.testing.MainCoroutineRule +import com.mapbox.navigation.utils.internal.LoggerFrontend +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.runs +import io.mockk.slot +import io.mockk.unmockkAll +import io.mockk.verify +import io.mockk.verifyOrder +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +@OptIn(ExperimentalPreviewMapboxNavigationAPI::class, ExperimentalCoroutinesApi::class) +class MapboxTripStarterTest { + + private val infoLogSlot = slot() + private val logger: LoggerFrontend = mockk { + every { getLogLevel() } returns LoggingLevel.INFO + every { logI(capture(infoLogSlot), any()) } just runs + } + + @get:Rule + val logRule = LoggingFrontendTestRule(logger) + + @get:Rule + val coroutineRule = MainCoroutineRule() + + private val sut = MapboxTripStarter() + + @Before + fun setup() { + mockkStatic(PermissionsManager::class) + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true + } + + @After + fun teardown() { + unmockkAll() + } + + @Test + fun `onAttached will startTripSession when location permissions are granted`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true + + val mapboxNavigation = mockMapboxNavigation() + sut.onAttached(mapboxNavigation) + + verify(exactly = 1) { mapboxNavigation.startTripSession() } + } + + @Test + fun `onAttached will not startTripSession when location permissions are disabled`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false + + val mapboxNavigation = mockMapboxNavigation() + sut.onAttached(mapboxNavigation) + + verify(exactly = 0) { mapboxNavigation.startTripSession() } + } + + @Test + fun `onAttached will not emit log when location permissions are granted`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true + + val mapboxNavigation = mockMapboxNavigation() + sut.onAttached(mapboxNavigation) + + verify(exactly = 1) { mapboxNavigation.startTripSession() } + verify(exactly = 0) { logger.logI(any(), any()) } + } + + @Test + fun `onAttached will emit log when location permissions are not granted`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false + + val mapboxNavigation = mockMapboxNavigation() + sut.onAttached(mapboxNavigation) + + verify(exactly = 0) { mapboxNavigation.startTripSession() } + assertTrue(infoLogSlot.captured.contains("startTripSession was not called")) + } + + @Test + fun `enableMapMatching will emit log when location permissions are not granted`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false + + val mapboxNavigation = mockMapboxNavigation() + sut.enableReplayRoute() + sut.onAttached(mapboxNavigation) + sut.enableMapMatching() + + verify(exactly = 0) { mapboxNavigation.startTripSession() } + assertTrue(infoLogSlot.captured.contains("startTripSession was not called")) + } + + @Test + fun `refreshLocationPermissions will startTripSession after onAttached`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false + + val mapboxNavigation = mockMapboxNavigation() + sut.onAttached(mapboxNavigation) + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true + sut.refreshLocationPermissions() + + verify(exactly = 1) { mapboxNavigation.startTripSession() } + } + + @Test + fun `enableReplayRoute will startReplayTripSession without location permissions`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false + + val mapboxNavigation = mockMapboxNavigation() + sut.enableReplayRoute() + sut.onAttached(mapboxNavigation) + + verify(exactly = 1) { mapboxNavigation.startReplayTripSession() } + } + + @Test + fun `enableReplayRoute will resetTripSession when the options change`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false + + val mapboxNavigation = mockMapboxNavigation() + sut.enableReplayRoute() + sut.onAttached(mapboxNavigation) + val nextOptions = sut.getReplayRouteSessionOptions().toBuilder() + .decodeMinDistance(Double.MAX_VALUE) + .build() + sut.enableReplayRoute(nextOptions) + + verifyOrder { + mapboxNavigation.startReplayTripSession() + mapboxNavigation.resetTripSession(any()) + mapboxNavigation.startReplayTripSession() + mapboxNavigation.resetTripSession(any()) + } + verify(exactly = 0) { mapboxNavigation.stopTripSession() } + } + + @Test + fun `enableMapMatching can be used to switch to regular trip session`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true + + val mapboxNavigation = mockMapboxNavigation() + sut.enableReplayRoute() + sut.onAttached(mapboxNavigation) + sut.enableMapMatching() + sut.onDetached(mapboxNavigation) + + verifyOrder { + mapboxNavigation.startReplayTripSession() + mapboxNavigation.startTripSession() + mapboxNavigation.stopTripSession() + } + } + + @Test + fun `setLocationPermissionGranted will not restart startReplayTripSession`() { + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false + + val mapboxNavigation = mockMapboxNavigation() + sut.enableReplayRoute() + sut.onAttached(mapboxNavigation) + every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true + sut.refreshLocationPermissions() + + verify(exactly = 1) { mapboxNavigation.startReplayTripSession() } + } + + @Test + fun `update will not stop a trip session that has been started`() { + val mapboxNavigation = mockMapboxNavigation() + every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STARTED + every { mapboxNavigation.isReplayEnabled() } returns false + + sut.onAttached(mapboxNavigation) + sut.enableReplayRoute() + + verify(exactly = 0) { mapboxNavigation.stopTripSession() } + verify(exactly = 1) { mapboxNavigation.startReplayTripSession() } + } + + @Test + fun `update before onAttached will not startTripSession`() { + val mapboxNavigation = mockMapboxNavigation() + + sut.enableReplayRoute() + sut.onAttached(mapboxNavigation) + + verify(exactly = 0) { mapboxNavigation.stopTripSession() } + verify(exactly = 0) { mapboxNavigation.startTripSession() } + verify(exactly = 1) { mapboxNavigation.startReplayTripSession() } + } + + private fun mockMapboxNavigation(): MapboxNavigation { + val mapboxNavigation = mockk(relaxed = true) + every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STOPPED + every { mapboxNavigation.startReplayTripSession() } answers { + every { mapboxNavigation.isReplayEnabled() } returns true + every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STARTED + } + every { mapboxNavigation.startTripSession() } answers { + every { mapboxNavigation.isReplayEnabled() } returns false + every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STARTED + } + every { mapboxNavigation.stopTripSession() } answers { + every { mapboxNavigation.isReplayEnabled() } returns false + every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STOPPED + } + every { mapboxNavigation.registerRoutesObserver(any()) } answers { + firstArg().onRoutesChanged( + mockk { every { navigationRoutes } returns emptyList() } + ) + } + return mapboxNavigation + } +} diff --git a/libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/session/TripSessionLocationEngineTest.kt b/libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/session/TripSessionLocationEngineTest.kt index abbfc99224d..76e7cdadab0 100644 --- a/libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/session/TripSessionLocationEngineTest.kt +++ b/libnavigation-core/src/test/java/com/mapbox/navigation/core/trip/session/TripSessionLocationEngineTest.kt @@ -14,6 +14,8 @@ import io.mockk.mockk import io.mockk.slot import io.mockk.verify import io.mockk.verifyOrder +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -144,6 +146,28 @@ class TripSessionLocationEngineTest { } } + @Test + fun `isReplayEnabled is true after replay is enabled`() { + sut.startLocationUpdates(true, mockk()) + + assertTrue(sut.isReplayEnabled) + } + + @Test + fun `isReplayEnabled is false when replay is disabled for location updates`() { + sut.startLocationUpdates(false, mockk()) + + assertFalse(sut.isReplayEnabled) + } + + @Test + fun `isReplayEnabled is false after stopLocationUpdates`() { + sut.startLocationUpdates(true, mockk()) + sut.stopLocationUpdates() + + assertFalse(sut.isReplayEnabled) + } + private fun mockLocationEngine(): LocationEngine { val locationEngine: LocationEngine = mockk(relaxUnitFun = true) val locationCallbackSlot = slot>() diff --git a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/SharedApp.kt b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/SharedApp.kt index 9c8bad7272f..8808858e33a 100644 --- a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/SharedApp.kt +++ b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/SharedApp.kt @@ -16,7 +16,6 @@ import com.mapbox.navigation.ui.app.internal.controller.TripSessionStarterStateC import com.mapbox.navigation.ui.app.internal.routefetch.RouteOptionsProvider import com.mapbox.navigation.ui.maps.internal.ui.RouteAlternativeComponent import com.mapbox.navigation.ui.maps.internal.ui.RouteAlternativeContract -import java.util.concurrent.atomic.AtomicBoolean object SharedApp { private var isSetup = false @@ -25,8 +24,6 @@ object SharedApp { val state get() = store.state.value val routeOptionsProvider: RouteOptionsProvider = RouteOptionsProvider() - private val ignoreTripSessionUpdates = AtomicBoolean(false) - private val navigationObservers: Array = arrayOf( RouteStateController(store), CameraStateController(store), @@ -45,7 +42,7 @@ object SharedApp { if (isSetup) return isSetup = true - MapboxNavigationApp.registerObserver(StateResetController(store, ignoreTripSessionUpdates)) + MapboxNavigationApp.registerObserver(StateResetController(store)) MapboxNavigationApp.registerObserver( RouteAlternativeComponent { routeAlternativeContract ?: RouteAlternativeComponentImpl(store) @@ -53,13 +50,4 @@ object SharedApp { ) MapboxNavigationApp.lifecycleOwner.attachCreated(*navigationObservers) } - - fun tripSessionTransaction(updateSession: () -> Unit) { - // Any changes to MapboxNavigation TripSession should be done within `tripSessionTransaction { }` block. - // This ensures that non of the registered TripSessionStateObserver accidentally execute cleanup logic - // when TripSession state changes. - ignoreTripSessionUpdates.set(true) - updateSession() - ignoreTripSessionUpdates.set(false) - } } diff --git a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/State.kt b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/State.kt index 3b1323f5f08..23a0abbaa66 100644 --- a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/State.kt +++ b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/State.kt @@ -7,7 +7,6 @@ import com.mapbox.navigation.ui.app.internal.camera.CameraState import com.mapbox.navigation.ui.app.internal.destination.Destination import com.mapbox.navigation.ui.app.internal.navigation.NavigationState import com.mapbox.navigation.ui.app.internal.routefetch.RoutePreviewState -import com.mapbox.navigation.ui.app.internal.tripsession.TripSessionStarterState /** * Navigation state for internal use. @@ -20,5 +19,4 @@ data class State constructor( val audio: AudioGuidanceState = AudioGuidanceState(), val routes: List = emptyList(), val previewRoutes: RoutePreviewState = RoutePreviewState.Empty, - val tripSession: TripSessionStarterState = TripSessionStarterState() ) diff --git a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/StateResetController.kt b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/StateResetController.kt index 462c49bf3d0..9cae92b239d 100644 --- a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/StateResetController.kt +++ b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/StateResetController.kt @@ -1,41 +1,14 @@ package com.mapbox.navigation.ui.app.internal.controller import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.trip.session.TripSessionState -import com.mapbox.navigation.core.trip.session.TripSessionStateObserver import com.mapbox.navigation.ui.app.internal.Store -import com.mapbox.navigation.ui.app.internal.endNavigation -import com.mapbox.navigation.ui.app.internal.extension.dispatch import com.mapbox.navigation.ui.base.lifecycle.UIComponent -import java.util.concurrent.atomic.AtomicBoolean internal class StateResetController( - private val store: Store, - private val ignoreTripSessionUpdates: AtomicBoolean + private val store: Store ) : UIComponent() { - - private var prevState: TripSessionState? = null - - private val tripSessionStateObserver = TripSessionStateObserver { newState -> - // we only reset Store state when TripSessionState switches from STARTED to STOPPED. - if (!ignoreTripSessionUpdates.get() && - prevState == TripSessionState.STARTED && - newState == TripSessionState.STOPPED - ) { - store.dispatch(endNavigation()) - } - prevState = newState - } - - override fun onAttached(mapboxNavigation: MapboxNavigation) { - super.onAttached(mapboxNavigation) - prevState = mapboxNavigation.getTripSessionState() - mapboxNavigation.registerTripSessionStateObserver(tripSessionStateObserver) - } - override fun onDetached(mapboxNavigation: MapboxNavigation) { super.onDetached(mapboxNavigation) - mapboxNavigation.unregisterTripSessionStateObserver(tripSessionStateObserver) // we reset Store state every time MapboxNavigation gets destroyed store.reset() diff --git a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateController.kt b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateController.kt index f3e6b246bfa..5c79f114529 100644 --- a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateController.kt +++ b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateController.kt @@ -1,44 +1,45 @@ package com.mapbox.navigation.ui.app.internal.controller import android.annotation.SuppressLint +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.core.trip.MapboxTripStarter import com.mapbox.navigation.ui.app.internal.Action import com.mapbox.navigation.ui.app.internal.State import com.mapbox.navigation.ui.app.internal.Store import com.mapbox.navigation.ui.app.internal.tripsession.TripSessionStarterAction -import com.mapbox.navigation.ui.app.internal.tripsession.TripSessionStarterState /** * The class is responsible to start and stop the `TripSession` for NavigationView. - * @param store defines the current screen state */ @SuppressLint("MissingPermission") +@OptIn(ExperimentalPreviewMapboxNavigationAPI::class) class TripSessionStarterStateController(store: Store) : StateController() { + init { store.register(this) } + private val tripStarter = MapboxTripStarter.getRegisteredInstance() + override fun process(state: State, action: Action): State { if (action is TripSessionStarterAction) { - return state.copy( - tripSession = processTripSessionAction(state.tripSession, action) - ) + processTripSessionAction(action) } return state } private fun processTripSessionAction( - state: TripSessionStarterState, action: TripSessionStarterAction - ): TripSessionStarterState { - return when (action) { - is TripSessionStarterAction.OnLocationPermission -> { - state.copy(isLocationPermissionGranted = action.granted) + ) { + when (action) { + is TripSessionStarterAction.RefreshLocationPermissions -> { + tripStarter.refreshLocationPermissions() } TripSessionStarterAction.EnableReplayTripSession -> { - state.copy(isReplayEnabled = true) + tripStarter.enableReplayRoute() } TripSessionStarterAction.EnableTripSession -> { - state.copy(isReplayEnabled = false) + tripStarter.enableMapMatching() } } } diff --git a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/tripsession/TripSessionStarterAction.kt b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/tripsession/TripSessionStarterAction.kt index a1528cb60c1..2f83d4aa202 100644 --- a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/tripsession/TripSessionStarterAction.kt +++ b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/tripsession/TripSessionStarterAction.kt @@ -1,16 +1,16 @@ package com.mapbox.navigation.ui.app.internal.tripsession +import com.mapbox.navigation.core.trip.MapboxTripStarter import com.mapbox.navigation.ui.app.internal.Action /** - * Defines actions responsible to mutate the [TripSessionStarterState]. + * Defines actions responsible to mutating the [MapboxTripStarter]. */ sealed class TripSessionStarterAction : Action { /** - * The action informs whether the location permissions have been granted. - * @param granted is set to true if location permissions were granted; false otherwise + * The action informs refreshes the internal state for location permissions. */ - data class OnLocationPermission(val granted: Boolean) : TripSessionStarterAction() + object RefreshLocationPermissions : TripSessionStarterAction() /** * The action enables trip session based on real gps updates. diff --git a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/tripsession/TripSessionStarterState.kt b/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/tripsession/TripSessionStarterState.kt deleted file mode 100644 index 96a56c2eefd..00000000000 --- a/libnavui-app/src/main/java/com/mapbox/navigation/ui/app/internal/tripsession/TripSessionStarterState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.mapbox.navigation.ui.app.internal.tripsession - -/** - * Defines the state for trip session - * @param isLocationPermissionGranted informs if location permissions are already granted - * @param isReplayEnabled is set to true if enabled; false otherwise - */ -data class TripSessionStarterState internal constructor( - val isLocationPermissionGranted: Boolean = false, - val isReplayEnabled: Boolean = false, -) diff --git a/libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/StateResetControllerTest.kt b/libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/StateResetControllerTest.kt index f06f4d2a976..c2bbb63a973 100644 --- a/libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/StateResetControllerTest.kt +++ b/libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/StateResetControllerTest.kt @@ -2,38 +2,25 @@ package com.mapbox.navigation.ui.app.internal.controller import com.mapbox.geojson.Point import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.trip.session.TripSessionState -import com.mapbox.navigation.core.trip.session.TripSessionStateObserver import com.mapbox.navigation.ui.app.internal.State import com.mapbox.navigation.ui.app.internal.destination.Destination -import com.mapbox.navigation.ui.app.internal.destination.DestinationAction import com.mapbox.navigation.ui.app.internal.navigation.NavigationState -import com.mapbox.navigation.ui.app.internal.navigation.NavigationStateAction -import com.mapbox.navigation.ui.app.internal.routefetch.RoutePreviewAction -import com.mapbox.navigation.ui.app.internal.routefetch.RoutesAction import com.mapbox.navigation.ui.app.testing.TestStore -import io.mockk.every import io.mockk.mockk -import io.mockk.slot import io.mockk.spyk -import io.mockk.verify import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import java.util.concurrent.atomic.AtomicBoolean internal class StateResetControllerTest { private lateinit var mapboxNavigation: MapboxNavigation private lateinit var store: TestStore private lateinit var sut: StateResetController - private lateinit var ignoreTripSessionUpdates: AtomicBoolean @Before fun setUp() { - mapboxNavigation = mockk(relaxed = true) { - every { getTripSessionState() } returns TripSessionState.STOPPED - } + mapboxNavigation = mockk(relaxed = true) store = spyk(TestStore()) store.setState( State( @@ -41,54 +28,7 @@ internal class StateResetControllerTest { navigation = NavigationState.ActiveNavigation ) ) - ignoreTripSessionUpdates = AtomicBoolean(false) - sut = StateResetController(store, ignoreTripSessionUpdates) - } - - @Test - @Suppress("MaxLineLength") - fun `onAttached, should dispatch actions that end navigation only when TripSessionState transitions from STARTED to STOPPED`() { - val observer = slot() - every { - mapboxNavigation.registerTripSessionStateObserver(capture(observer)) - } returns Unit - sut.onAttached(mapboxNavigation) - - // STOPPED -> STOPPED - observer.captured.onSessionStateChanged(TripSessionState.STOPPED) - verify(exactly = 0) { store.dispatch(any()) } - - // STOPPED -> STARTED - observer.captured.onSessionStateChanged(TripSessionState.STARTED) - verify(exactly = 0) { store.dispatch(any()) } - - // STARTED -> STOPPED - observer.captured.onSessionStateChanged(TripSessionState.STOPPED) - verify(exactly = 1) { - store.dispatch(RoutesAction.SetRoutes(emptyList())) - store.dispatch(RoutePreviewAction.Ready(emptyList())) - store.dispatch(DestinationAction.SetDestination(null)) - store.dispatch(NavigationStateAction.Update(NavigationState.FreeDrive)) - } - } - - @Test - @Suppress("MaxLineLength") - fun `onAttached, should NOT dispatch end navigation action when ignoreTripSessionUpdates is true`() { - ignoreTripSessionUpdates.set(true) - val observer = slot() - every { mapboxNavigation.registerTripSessionStateObserver(capture(observer)) } returns Unit - every { mapboxNavigation.getTripSessionState() } returns TripSessionState.STARTED - sut.onAttached(mapboxNavigation) - - // STARTED -> STOPPED - observer.captured.onSessionStateChanged(TripSessionState.STOPPED) - verify(exactly = 0) { - store.dispatch(RoutesAction.SetRoutes(emptyList())) - store.dispatch(RoutePreviewAction.Ready(emptyList())) - store.dispatch(DestinationAction.SetDestination(null)) - store.dispatch(NavigationStateAction.Update(NavigationState.FreeDrive)) - } + sut = StateResetController(store) } @Test diff --git a/libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateControllerTest.kt b/libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateControllerTest.kt index 841168beea7..6b8d7425216 100644 --- a/libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateControllerTest.kt +++ b/libnavui-app/src/test/java/com/mapbox/navigation/ui/app/internal/controller/TripSessionStarterStateControllerTest.kt @@ -1,12 +1,17 @@ package com.mapbox.navigation.ui.app.internal.controller import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI +import com.mapbox.navigation.core.trip.MapboxTripStarter import com.mapbox.navigation.testing.MainCoroutineRule +import com.mapbox.navigation.ui.app.internal.Store import com.mapbox.navigation.ui.app.internal.tripsession.TripSessionStarterAction -import com.mapbox.navigation.ui.app.testing.TestStore -import io.mockk.spyk +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkAll +import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.Assert.assertEquals +import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test @@ -17,33 +22,47 @@ class TripSessionStarterStateControllerTest { @get:Rule var coroutineRule = MainCoroutineRule() - private lateinit var testStore: TestStore + private lateinit var mapboxTripStarter: MapboxTripStarter private lateinit var sut: TripSessionStarterStateController + private val store: Store = mockk(relaxed = true) @Before fun setup() { - testStore = spyk(TestStore()) - sut = TripSessionStarterStateController(testStore) + mapboxTripStarter = mockk(relaxed = true) + + mockkObject(MapboxTripStarter.Companion) + every { MapboxTripStarter.getRegisteredInstance() } returns mapboxTripStarter + sut = TripSessionStarterStateController(store) + } + + @After + fun teardown() { + unmockkAll() + } + + @Test + fun `constructor will register to the store`() { + verify(exactly = 1) { store.register(sut) } } @Test fun `on OnLocationPermission action should update TripSessionStarterState`() { - testStore.dispatch(TripSessionStarterAction.OnLocationPermission(true)) + sut.process(mockk(), TripSessionStarterAction.RefreshLocationPermissions) - assertEquals(true, testStore.state.value.tripSession.isLocationPermissionGranted) + verify(exactly = 1) { mapboxTripStarter.refreshLocationPermissions() } } @Test fun `on EnableReplayTripSession action should update TripSessionStarterState`() { - testStore.dispatch(TripSessionStarterAction.EnableReplayTripSession) + sut.process(mockk(), TripSessionStarterAction.EnableReplayTripSession) - assertEquals(true, testStore.state.value.tripSession.isReplayEnabled) + verify(exactly = 1) { mapboxTripStarter.enableReplayRoute() } } @Test fun `on EnableTripSession action should update TripSessionStarterState`() { - testStore.dispatch(TripSessionStarterAction.EnableTripSession) + sut.process(mockk(), TripSessionStarterAction.EnableTripSession) - assertEquals(false, testStore.state.value.tripSession.isReplayEnabled) + verify(exactly = 1) { mapboxTripStarter.enableMapMatching() } } } diff --git a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/NavigationView.kt b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/NavigationView.kt index d90d735c332..d3d443b4ab4 100644 --- a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/NavigationView.kt +++ b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/NavigationView.kt @@ -38,7 +38,6 @@ import com.mapbox.navigation.dropin.internal.extensions.scalebarPlaceholderCoord import com.mapbox.navigation.dropin.internal.extensions.speedLimitCoordinator import com.mapbox.navigation.dropin.internal.extensions.toComponentActivity import com.mapbox.navigation.dropin.internal.extensions.toViewModelStoreOwner -import com.mapbox.navigation.dropin.internal.extensions.tripSessionComponent import com.mapbox.navigation.dropin.map.MapViewObserver import com.mapbox.navigation.dropin.navigationview.MapboxNavigationViewApi import com.mapbox.navigation.dropin.navigationview.NavigationViewContext @@ -131,7 +130,6 @@ class NavigationView @JvmOverloads constructor( attachCreated( analyticsComponent(), locationPermissionComponent(activity), - tripSessionComponent(), mapLayoutCoordinator(binding), backPressedComponent(activity), scalebarPlaceholderCoordinator(binding.scalebarLayout), diff --git a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/internal/extensions/NavigationViewContextEx.kt b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/internal/extensions/NavigationViewContextEx.kt index 9aad03ceb14..b173d368ddd 100644 --- a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/internal/extensions/NavigationViewContextEx.kt +++ b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/internal/extensions/NavigationViewContextEx.kt @@ -35,7 +35,6 @@ import com.mapbox.navigation.dropin.permission.LocationPermissionComponent import com.mapbox.navigation.dropin.roadname.RoadNameCoordinator import com.mapbox.navigation.dropin.speedlimit.SpeedLimitCoordinator import com.mapbox.navigation.dropin.tripprogress.TripProgressBinder -import com.mapbox.navigation.dropin.tripsession.TripSessionComponent import com.mapbox.navigation.ui.app.internal.camera.TargetCameraMode import com.mapbox.navigation.ui.app.internal.navigation.NavigationState import com.mapbox.navigation.ui.maps.internal.ui.BuildingHighlightComponent @@ -256,9 +255,6 @@ internal fun NavigationViewContext.analyticsComponent() = internal fun NavigationViewContext.locationPermissionComponent(activity: ComponentActivity) = LocationPermissionComponent(activity, store) -internal fun NavigationViewContext.tripSessionComponent() = - TripSessionComponent(lifecycleOwner.lifecycle, store) - internal fun NavigationViewContext.backPressedComponent(activity: ComponentActivity) = BackPressedComponent(activity.onBackPressedDispatcher, store, lifecycleOwner) diff --git a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/navigationview/MapboxNavigationViewApi.kt b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/navigationview/MapboxNavigationViewApi.kt index de2200cfa11..55b5c2d3e9f 100644 --- a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/navigationview/MapboxNavigationViewApi.kt +++ b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/navigationview/MapboxNavigationViewApi.kt @@ -3,8 +3,10 @@ package com.mapbox.navigation.dropin.navigationview import com.mapbox.bindgen.Expected import com.mapbox.bindgen.ExpectedFactory import com.mapbox.geojson.Point +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.base.internal.extensions.getDestination import com.mapbox.navigation.base.route.NavigationRoute +import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp import com.mapbox.navigation.dropin.NavigationViewApi import com.mapbox.navigation.dropin.NavigationViewApiError import com.mapbox.navigation.dropin.NavigationViewApiErrorTypes @@ -89,8 +91,9 @@ internal class MapboxNavigationViewApi( store.dispatch(startArrival(point, routes)) } + @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) override fun isReplayEnabled(): Boolean { - return store.state.value.tripSession.isReplayEnabled + return MapboxNavigationApp.current()?.isReplayEnabled() ?: false } override fun routeReplayEnabled(enabled: Boolean) { diff --git a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/permission/LocationPermissionComponent.kt b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/permission/LocationPermissionComponent.kt index 771ff02e82d..0693170c60f 100644 --- a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/permission/LocationPermissionComponent.kt +++ b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/permission/LocationPermissionComponent.kt @@ -1,7 +1,6 @@ package com.mapbox.navigation.dropin.permission import android.Manifest -import android.content.Context import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultCallback import androidx.activity.result.contract.ActivityResultContracts @@ -24,13 +23,8 @@ internal class LocationPermissionComponent( private val componentActivityRef = WeakReference(componentActivity) - private val callback = ActivityResultCallback { permissions: Map -> - val accessFineLocation = permissions[FINE_LOCATION_PERMISSIONS] - ?: false - val accessCoarseLocation = permissions[COARSE_LOCATION_PERMISSIONS] - ?: false - val granted = accessFineLocation || accessCoarseLocation - store.dispatch(TripSessionStarterAction.OnLocationPermission(granted)) + private val callback = ActivityResultCallback { _: Map -> + store.dispatch(TripSessionStarterAction.RefreshLocationPermissions) } private val launcher = try { @@ -61,7 +55,7 @@ internal class LocationPermissionComponent( // a coordinator and flowable binder. This issue was also difficult to reproduce on // all devices. Launching a coroutine to update the state is a temporary solution. coroutineScope.launch { - store.dispatch(TripSessionStarterAction.OnLocationPermission(true)) + store.dispatch(TripSessionStarterAction.RefreshLocationPermissions) } } else { val fragActivity = componentActivityRef.get() as? FragmentActivity @@ -70,7 +64,7 @@ internal class LocationPermissionComponent( } else { launcher?.launch(LOCATION_PERMISSIONS) } - notifyGrantedOnForegrounded(mapboxNavigation.navigationOptions.applicationContext) + notifyGrantedOnForegrounded() } } @@ -80,17 +74,10 @@ internal class LocationPermissionComponent( * location permissions through the app settings or * when developers request location permissions themselves. */ - private fun notifyGrantedOnForegrounded(applicationContext: Context) { + private fun notifyGrantedOnForegrounded() { coroutineScope.launch { componentActivityRef.get()?.repeatOnLifecycle(Lifecycle.State.RESUMED) { - if (!store.state.value.tripSession.isLocationPermissionGranted) { - val isGranted = PermissionsManager.areLocationPermissionsGranted( - applicationContext - ) - if (isGranted) { - store.dispatch(TripSessionStarterAction.OnLocationPermission(true)) - } - } + store.dispatch(TripSessionStarterAction.RefreshLocationPermissions) } } } diff --git a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/tripsession/TripSessionComponent.kt b/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/tripsession/TripSessionComponent.kt deleted file mode 100644 index 9b97fa2ab6e..00000000000 --- a/libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/tripsession/TripSessionComponent.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.mapbox.navigation.dropin.tripsession - -import android.annotation.SuppressLint -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI -import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.replay.route.ReplayRouteSession -import com.mapbox.navigation.core.trip.session.TripSessionState -import com.mapbox.navigation.ui.app.internal.SharedApp.tripSessionTransaction -import com.mapbox.navigation.ui.app.internal.Store -import com.mapbox.navigation.ui.app.internal.navigation.NavigationState -import com.mapbox.navigation.ui.app.internal.tripsession.TripSessionStarterState -import com.mapbox.navigation.ui.base.lifecycle.UIComponent -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.launch - -@OptIn(ExperimentalPreviewMapboxNavigationAPI::class) -@SuppressLint("MissingPermission") -internal class TripSessionComponent( - private val lifecycle: Lifecycle, - private val store: Store -) : UIComponent() { - - private var replayRouteTripSession: ReplayRouteSession? = null - - /** - * Signals that the [mapboxNavigation] instance is ready for use. - * @param mapboxNavigation - */ - override fun onAttached(mapboxNavigation: MapboxNavigation) { - super.onAttached(mapboxNavigation) - - coroutineScope.launch { - lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - flowStartReplaySession().collect { starterState -> - tripSessionTransaction { - if (starterState.isLocationPermissionGranted) { - if (starterState.isReplayEnabled) { - startReplayTripSession(mapboxNavigation) - } else { - startTripSession(mapboxNavigation) - } - } else { - mapboxNavigation.ensureTripSessionStopped() - } - } - } - } - } - } - - /** - * Signals that the [mapboxNavigation] instance is being detached. - * @param mapboxNavigation - */ - override fun onDetached(mapboxNavigation: MapboxNavigation) { - super.onDetached(mapboxNavigation) - tripSessionTransaction { - replayRouteTripSession?.onDetached(mapboxNavigation) - replayRouteTripSession = null - } - } - - private fun flowStartReplaySession(): Flow = combine( - store.select { it.navigation }, - store.select { it.tripSession } - ) { navigationState, tripSessionStarterState -> - if (navigationState !is NavigationState.ActiveNavigation) { - tripSessionStarterState.copy(isReplayEnabled = false) - } else { - tripSessionStarterState - } - }.distinctUntilChanged() - - private fun startTripSession(mapboxNavigation: MapboxNavigation) { - replayRouteTripSession?.onDetached(mapboxNavigation) - replayRouteTripSession = null - mapboxNavigation.startTripSession() - } - - private fun startReplayTripSession(mapboxNavigation: MapboxNavigation) { - replayRouteTripSession?.onDetached(mapboxNavigation) - replayRouteTripSession = ReplayRouteSession() - replayRouteTripSession?.onAttached(mapboxNavigation) - } - - private fun MapboxNavigation.ensureTripSessionStopped() { - if (getTripSessionState() != TripSessionState.STOPPED) { - stopTripSession() - } - } -} diff --git a/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/NavigationViewTest.kt b/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/NavigationViewTest.kt index 37c9112d369..042f4217f28 100644 --- a/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/NavigationViewTest.kt +++ b/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/NavigationViewTest.kt @@ -25,7 +25,6 @@ import com.mapbox.navigation.dropin.navigationview.NavigationViewListener import com.mapbox.navigation.dropin.permission.LocationPermissionComponent import com.mapbox.navigation.dropin.roadname.RoadNameCoordinator import com.mapbox.navigation.dropin.speedlimit.SpeedLimitCoordinator -import com.mapbox.navigation.dropin.tripsession.TripSessionComponent import com.mapbox.navigation.testing.LoggingFrontendTestRule import com.mapbox.navigation.ui.app.internal.SharedApp import io.mockk.CapturingSlot @@ -110,7 +109,6 @@ class NavigationViewTest { verifyRegisteredObserver(AnalyticsComponent::class) verifyRegisteredObserver(LocationPermissionComponent::class) - verifyRegisteredObserver(TripSessionComponent::class) verifyRegisteredObserver(MapLayoutCoordinator::class) verifyRegisteredObserver(BackPressedComponent::class) verifyRegisteredObserver(ScalebarPlaceholderCoordinator::class) diff --git a/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/navigationview/MapboxNavigationViewApiTest.kt b/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/navigationview/MapboxNavigationViewApiTest.kt index d0ba087fa91..6c8cfd47f88 100644 --- a/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/navigationview/MapboxNavigationViewApiTest.kt +++ b/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/navigationview/MapboxNavigationViewApiTest.kt @@ -1,7 +1,10 @@ package com.mapbox.navigation.dropin.navigationview import com.mapbox.geojson.Point +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.base.route.NavigationRoute +import com.mapbox.navigation.core.MapboxNavigation +import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp import com.mapbox.navigation.dropin.NavigationViewApiErrorTypes import com.mapbox.navigation.dropin.util.TestStore import com.mapbox.navigation.ui.app.internal.Reducer @@ -19,7 +22,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkObject import io.mockk.spyk -import io.mockk.unmockkObject +import io.mockk.unmockkAll import io.mockk.verify import org.junit.After import org.junit.Assert.assertEquals @@ -54,6 +57,9 @@ class MapboxNavigationViewApiTest { } ) + mockkObject(MapboxNavigationApp) + every { MapboxNavigationApp.current() } returns null + mockkObject(MapboxAudioGuidance.Companion) every { MapboxAudioGuidance.getRegisteredInstance() } returns audioGuidance @@ -62,7 +68,7 @@ class MapboxNavigationViewApiTest { @After fun cleanUp() { - unmockkObject(MapboxAudioGuidance.Companion) + unmockkAll() } @Test @@ -325,14 +331,15 @@ class MapboxNavigationViewApiTest { assertEquals(NavigationViewApiErrorTypes.InvalidRoutesInfo, result.error?.type) } + @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) @Test fun `isReplayEnabled should return true when replay trip session is enabled `() { + val mapboxNavigation = mockk() + every { MapboxNavigationApp.current() } returns mapboxNavigation + + every { mapboxNavigation.isReplayEnabled() } returns false assertFalse(sut.isReplayEnabled()) - testStore.updateState { - it.copy( - tripSession = it.tripSession.copy(isReplayEnabled = true) - ) - } + every { mapboxNavigation.isReplayEnabled() } returns true assertTrue(sut.isReplayEnabled()) } diff --git a/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/permission/LocationPermissionComponentTest.kt b/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/permission/LocationPermissionComponentTest.kt index c5550492adc..3c6eb0904b2 100644 --- a/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/permission/LocationPermissionComponentTest.kt +++ b/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/permission/LocationPermissionComponentTest.kt @@ -12,7 +12,6 @@ import com.mapbox.android.core.permissions.PermissionsManager import com.mapbox.navigation.core.MapboxNavigation import com.mapbox.navigation.dropin.util.TestStore import com.mapbox.navigation.testing.MainCoroutineRule -import com.mapbox.navigation.ui.app.internal.State import com.mapbox.navigation.ui.app.internal.tripsession.TripSessionStarterAction import io.mockk.every import io.mockk.mockk @@ -75,9 +74,7 @@ class LocationPermissionComponentTest { locationPermissionComponent.onAttached(mockMapboxNavigation()) verify { - testStore.dispatch( - TripSessionStarterAction.OnLocationPermission(true) - ) + testStore.dispatch(TripSessionStarterAction.RefreshLocationPermissions) } } @@ -92,9 +89,7 @@ class LocationPermissionComponentTest { locationPermissionComponent.onAttached(mockMapboxNavigation()) verify(exactly = 0) { - testStore.dispatch( - TripSessionStarterAction.OnLocationPermission(false) - ) + testStore.dispatch(TripSessionStarterAction.RefreshLocationPermissions) } } @@ -127,9 +122,7 @@ class LocationPermissionComponentTest { callbackSlot.captured.onActivityResult(permissions) verify { - testStore.dispatch( - TripSessionStarterAction.OnLocationPermission(true) - ) + testStore.dispatch(TripSessionStarterAction.RefreshLocationPermissions) } } @@ -149,9 +142,7 @@ class LocationPermissionComponentTest { callbackSlot.captured.onActivityResult(permissions) verify { - testStore.dispatch( - TripSessionStarterAction.OnLocationPermission(false) - ) + testStore.dispatch(TripSessionStarterAction.RefreshLocationPermissions) } } @@ -175,13 +166,6 @@ class LocationPermissionComponentTest { componentActivity, testStore ) - testStore.setState( - State( - tripSession = mockk { - every { isLocationPermissionGranted } returns false - } - ) - ) every { PermissionsManager.areLocationPermissionsGranted(any()) } returns false locationPermissionComponent.onAttached(mockMapboxNavigation()) @@ -189,9 +173,7 @@ class LocationPermissionComponentTest { testLifecycle.lifecycleRegistry.currentState = Lifecycle.State.RESUMED verify { - testStore.dispatch( - TripSessionStarterAction.OnLocationPermission(true) - ) + testStore.dispatch(TripSessionStarterAction.RefreshLocationPermissions) } } diff --git a/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/tripsession/TripSessionComponentTest.kt b/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/tripsession/TripSessionComponentTest.kt deleted file mode 100644 index 0b77e1ff4a2..00000000000 --- a/libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/tripsession/TripSessionComponentTest.kt +++ /dev/null @@ -1,250 +0,0 @@ -package com.mapbox.navigation.dropin.tripsession - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry -import com.mapbox.android.core.location.LocationEngineProvider -import com.mapbox.android.core.permissions.PermissionsManager -import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI -import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp -import com.mapbox.navigation.dropin.util.TestStore -import com.mapbox.navigation.testing.MainCoroutineRule -import com.mapbox.navigation.ui.app.internal.State -import com.mapbox.navigation.ui.app.internal.navigation.NavigationState -import com.mapbox.navigation.ui.app.internal.tripsession.TripSessionStarterState -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.mockkStatic -import io.mockk.runs -import io.mockk.spyk -import io.mockk.unmockkAll -import io.mockk.verify -import io.mockk.verifyOrder -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -@OptIn(ExperimentalCoroutinesApi::class, ExperimentalPreviewMapboxNavigationAPI::class) -@RunWith(RobolectricTestRunner::class) -class TripSessionComponentTest { - - @get:Rule - var coroutineRule = MainCoroutineRule() - - private lateinit var testLifecycle: TestLifecycleOwner - private lateinit var testStore: TestStore - private lateinit var mapboxNavigation: MapboxNavigation - private lateinit var sut: TripSessionComponent - - @Before - fun setup() { - mockkObject(MapboxNavigationApp) - mapboxNavigation = mockk(relaxed = true) - every { MapboxNavigationApp.current() } returns mapboxNavigation - - testStore = spyk(TestStore()) - testLifecycle = TestLifecycleOwner() - sut = TripSessionComponent(testLifecycle.lifecycle, testStore) - - mockkStatic(PermissionsManager::class) - every { PermissionsManager.areLocationPermissionsGranted(any()) } returns true - mockkStatic(LocationEngineProvider::class) - every { LocationEngineProvider.getBestLocationEngine(any()) } returns mockk { - every { getLastLocation(any()) } just runs - } - } - - @After - fun teardown() { - unmockkAll() - } - - @Test - fun `startTripSession if location permissions are granted`() = - runBlockingTest { - testStore.setState( - State( - tripSession = tripSessionStarterState( - isLocationPermissionGranted = false, - isReplayEnabled = false, - ) - ) - ) - sut.onAttached(mapboxNavigation) - testLifecycle.moveToState(Lifecycle.State.STARTED) - - testStore.setState( - State( - tripSession = tripSessionStarterState( - isLocationPermissionGranted = true, - isReplayEnabled = false, - ) - ) - ) - - verify { mapboxNavigation.startTripSession() } - } - - @Test - fun `onDetached does not stopTripSession for a regular session`() = - runBlockingTest { - testStore.setState( - State( - tripSession = tripSessionStarterState( - isLocationPermissionGranted = true, - isReplayEnabled = false, - ) - ) - ) - sut.onAttached(mapboxNavigation) - testLifecycle.moveToState(Lifecycle.State.STARTED) - - sut.onDetached(mapboxNavigation) - - verify(exactly = 1) { mapboxNavigation.startTripSession() } - verify(exactly = 0) { mapboxNavigation.stopTripSession() } - } - - @Test - fun `EnableTripSession will not stopTripSession`() = - runBlockingTest { - testStore.setState( - State( - navigation = NavigationState.ActiveNavigation, - tripSession = tripSessionStarterState( - isLocationPermissionGranted = true, - isReplayEnabled = true, - ) - ) - ) - sut.onAttached(mapboxNavigation) - testLifecycle.moveToState(Lifecycle.State.STARTED) - - testStore.updateState { - it.copy( - tripSession = tripSessionStarterState( - isLocationPermissionGranted = true, - isReplayEnabled = false, - ) - ) - } - - verifyOrder { - mapboxNavigation.startReplayTripSession() - mapboxNavigation.startTripSession() - } - verify(exactly = 0) { mapboxNavigation.stopTripSession() } - } - - @Test - fun `EnableReplayTripSession will startReplayTripSession`() = - runBlockingTest { - testStore.setState( - State( - navigation = NavigationState.ActiveNavigation, - tripSession = tripSessionStarterState( - isLocationPermissionGranted = false, - isReplayEnabled = false, - ) - ) - ) - sut.onAttached(mapboxNavigation) - testLifecycle.moveToState(Lifecycle.State.STARTED) - - testStore.updateState { - it.copy( - tripSession = tripSessionStarterState( - isLocationPermissionGranted = true, - isReplayEnabled = true, - ) - ) - } - - verify { mapboxNavigation.startReplayTripSession() } - } - - @Test - fun `EnableReplayTripSession will not startReplayTripSession without location permissions`() = - runBlockingTest { - testStore.setState( - State( - navigation = NavigationState.ActiveNavigation, - tripSession = tripSessionStarterState( - isLocationPermissionGranted = false, - isReplayEnabled = false, - ) - ) - ) - sut.onAttached(mapboxNavigation) - testLifecycle.moveToState(Lifecycle.State.STARTED) - - testStore.updateState { - it.copy( - tripSession = tripSessionStarterState( - isLocationPermissionGranted = false, - isReplayEnabled = true, - ) - ) - } - - verify(exactly = 0) { mapboxNavigation.startReplayTripSession() } - } - - @Test - fun `EnableReplayTripSession will only startReplayTripSession for ActiveGuidance`() = - runBlockingTest { - testStore.setState( - State( - navigation = NavigationState.FreeDrive, - tripSession = tripSessionStarterState( - isLocationPermissionGranted = true, - isReplayEnabled = true, - ) - ) - ) - sut.onAttached(mapboxNavigation) - testLifecycle.moveToState(Lifecycle.State.STARTED) - - testStore.setNavigationState(NavigationState.DestinationPreview) - testStore.setNavigationState(NavigationState.RoutePreview) - testStore.setNavigationState(NavigationState.Arrival) - - verify(exactly = 0) { mapboxNavigation.startReplayTripSession() } - testStore.setNavigationState(NavigationState.ActiveNavigation) - verify(exactly = 1) { mapboxNavigation.startReplayTripSession() } - } - - private fun TestStore.setNavigationState(navState: NavigationState) { - setState( - state.value.copy( - navigation = navState - ) - ) - } - - private fun tripSessionStarterState( - isLocationPermissionGranted: Boolean, - isReplayEnabled: Boolean - ): TripSessionStarterState { - return testStore.state.value.tripSession.copy(isLocationPermissionGranted, isReplayEnabled) - } - - private class TestLifecycleOwner : LifecycleOwner { - val lifecycleRegistry = LifecycleRegistry(this) - .also { it.currentState = Lifecycle.State.INITIALIZED } - - override fun getLifecycle(): Lifecycle = lifecycleRegistry - - fun moveToState(state: Lifecycle.State) { - lifecycleRegistry.currentState = state - } - } -} diff --git a/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/RoadObjectsActivity.kt b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/RoadObjectsActivity.kt index 94f7fc7c1c0..eddadca9dba 100644 --- a/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/RoadObjectsActivity.kt +++ b/qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/view/RoadObjectsActivity.kt @@ -15,6 +15,7 @@ import com.mapbox.maps.plugin.animation.CameraAnimationsPlugin import com.mapbox.maps.plugin.animation.MapAnimationOptions import com.mapbox.maps.plugin.animation.camera import com.mapbox.maps.plugin.locationcomponent.location +import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI import com.mapbox.navigation.base.formatter.DistanceFormatterOptions import com.mapbox.navigation.base.options.NavigationOptions import com.mapbox.navigation.base.trip.model.RouteProgress @@ -25,12 +26,9 @@ import com.mapbox.navigation.base.trip.model.roadobject.reststop.RestStopType import com.mapbox.navigation.base.trip.model.roadobject.tollcollection.TollCollection import com.mapbox.navigation.base.trip.model.roadobject.tollcollection.TollCollectionType import com.mapbox.navigation.core.MapboxNavigation -import com.mapbox.navigation.core.MapboxNavigationProvider import com.mapbox.navigation.core.directions.session.RoutesObserver -import com.mapbox.navigation.core.replay.MapboxReplayer -import com.mapbox.navigation.core.replay.ReplayLocationEngine -import com.mapbox.navigation.core.replay.route.ReplayProgressObserver import com.mapbox.navigation.core.replay.route.ReplayRouteMapper +import com.mapbox.navigation.core.trip.MapboxTripStarter import com.mapbox.navigation.core.trip.session.LocationMatcherResult import com.mapbox.navigation.core.trip.session.LocationObserver import com.mapbox.navigation.core.trip.session.RouteProgressObserver @@ -57,8 +55,6 @@ class RoadObjectsActivity : AppCompatActivity() { private const val TAG = "RoadObjectsActivity" } - private val replayRouteMapper = ReplayRouteMapper() - private val mapboxReplayer = MapboxReplayer() private val navigationLocationProvider = NavigationLocationProvider() private val binding: LayoutActivityRestStopBinding by lazy { @@ -74,14 +70,17 @@ class RoadObjectsActivity : AppCompatActivity() { } private val mapboxNavigation: MapboxNavigation by lazy { - MapboxNavigationProvider.create( + MapboxNavigation( NavigationOptions.Builder(this) .accessToken(Utils.getMapboxSapaAccessToken(this)) - .locationEngine(ReplayLocationEngine(mapboxReplayer)) .build() ) } + @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) + private val mapboxTripStarter = MapboxTripStarter.create() + .enableReplayRoute() + private val mapCamera: CameraAnimationsPlugin by lazy { binding.mapView.camera } @@ -121,6 +120,7 @@ class RoadObjectsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(binding.root) initNavigation() + initReplayer() initStyle() initListeners() } @@ -129,15 +129,15 @@ class RoadObjectsActivity : AppCompatActivity() { super.onStop() mapboxNavigation.unregisterRoutesObserver(routesObserver) mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver) - mapboxNavigation.unregisterRouteProgressObserver(replayProgressObserver) mapboxNavigation.unregisterLocationObserver(locationObserver) } + @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) override fun onDestroy() { super.onDestroy() + mapboxTripStarter.onDetached(mapboxNavigation) routeLineApi.cancel() routeLineView.cancel() - mapboxReplayer.finish() mapboxNavigation.onDestroy() } @@ -150,10 +150,6 @@ class RoadObjectsActivity : AppCompatActivity() { routeLineApi.setRoutes(listOf(RouteLine(route, null))) { routeLineView.renderRouteDrawData(style, it) } - - val routeOrigin = Utils.getRouteOriginPoint(route) - val cameraOptions = CameraOptions.Builder().center(routeOrigin).zoom(15.0).build() - binding.mapView.getMapboxMap().setCamera(cameraOptions) } } @@ -163,12 +159,16 @@ class RoadObjectsActivity : AppCompatActivity() { enabled = true } mapboxNavigation.registerRoutesObserver(routesObserver) - mapboxNavigation.setRoutes(listOf(getRoute())) mapboxNavigation.registerLocationObserver(locationObserver) - mapboxNavigation.registerRouteProgressObserver(replayProgressObserver) - mapboxReplayer.pushRealLocation(this, 0.0) - mapboxReplayer.playbackSpeed(1.5) - mapboxReplayer.play() + } + + @OptIn(ExperimentalPreviewMapboxNavigationAPI::class) + private fun initReplayer() { + mapboxTripStarter.onAttached(mapboxNavigation) + val routeOrigin = Utils.getRouteOriginPoint(getRoute()) + mapboxNavigation.mapboxReplayer.pushEvents( + listOf(ReplayRouteMapper.mapToUpdateLocation(0.0, routeOrigin)) + ) } private val locationObserver: LocationObserver = object : LocationObserver { @@ -202,21 +202,11 @@ class RoadObjectsActivity : AppCompatActivity() { private fun initListeners() { binding.startNavigation.setOnClickListener { mapboxNavigation.registerRouteProgressObserver(routeProgressObserver) - mapboxNavigation.startTripSession() binding.startNavigation.visibility = View.GONE - startSimulation(mapboxNavigation.getRoutes()[0]) + mapboxNavigation.setRoutes(listOf(getRoute())) } } - private fun startSimulation(route: DirectionsRoute) { - mapboxReplayer.stop() - mapboxReplayer.clearEvents() - val replayData = replayRouteMapper.mapDirectionsRouteGeometry(route) - mapboxReplayer.pushEvents(replayData) - mapboxReplayer.seekTo(replayData[0]) - mapboxReplayer.play() - } - private val routesObserver = RoutesObserver { val builder = SpannableStringBuilder("The route has\n\n") val navigationRoute = it.navigationRoutes.first() @@ -342,8 +332,6 @@ class RoadObjectsActivity : AppCompatActivity() { return getString(stringResource, formatter.format(distance)) } - private val replayProgressObserver = ReplayProgressObserver(mapboxReplayer) - private fun getRoute(): DirectionsRoute { val routeAsString = Utils.readRawFileText(this, R.raw.route_with_sapa) return DirectionsRoute.fromJson(routeAsString)