Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Mapbox welcomes participation and contributions from everyone.
- `ViewOptionsCustomization.showEndNavigationButton` can be used to show/hide info panel's end navigation button. The default is `true`. [#6506](https://github.com/mapbox/mapbox-navigation-android/pull/6506)
- Introduced `ViewOptionsCustomization.showPoiName` and `ViewOptionsCustomization.showArrivalText` that allows showing/hiding of the POI and arrival text view. [#6515](https://github.com/mapbox/mapbox-navigation-android/pull/6515)
- Introduced `ViewBinderCustomization.infoPanelPoiNameBinder` and `ViewBinderCustomization.infoPanelArrivalTextBinder` that allows injection of custom Info Panel POI and arrival text view into `NavigationView`. [#6515](https://github.com/mapbox/mapbox-navigation-android/pull/6515)
- Added experimental routes preview state, see `MapboxNavigaton#setRoutesPreview`, `MapboxNavigaton#changeRoutesPreviewPrimaryRoute`, `MapboxNavigaton#registerRoutesPreviewObserver`, `MapboxNavigaton#getRoutesPreview`. [#6495](https://github.com/mapbox/mapbox-navigation-android/pull/6495)
#### Bug fixes and improvements
- :warning: Removed `MapboxMapScalebarParams` and replaced `ViewStyleCustomization.mapScalebarParams` by `ViewOptionsCustomization.showMapScalebar` [#6523](https://github.com/mapbox/mapbox-navigation-android/pull/6523)
- Refactored `ScalebarComponent` to use `ViewOptionsCustomization.distanceFormatterOptions` to change between imperial and metric based unit. [#6523](https://github.com/mapbox/mapbox-navigation-android/pull/6523)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.res.Configuration
import android.content.res.Resources
import android.location.Location
import android.os.Bundle
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.Toast
Expand All @@ -19,8 +20,10 @@ import com.mapbox.maps.MapboxMap
import com.mapbox.maps.Style.Companion.MAPBOX_STREETS
import com.mapbox.maps.plugin.LocationPuck2D
import com.mapbox.maps.plugin.animation.camera
import com.mapbox.maps.plugin.gestures.OnMapClickListener
import com.mapbox.maps.plugin.gestures.gestures
import com.mapbox.maps.plugin.locationcomponent.location
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.TimeFormat
import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
Expand All @@ -35,6 +38,7 @@ 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.formatter.MapboxDistanceFormatter
import com.mapbox.navigation.core.preview.RoutesPreviewObserver
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
import com.mapbox.navigation.core.trip.session.LocationObserver
import com.mapbox.navigation.core.trip.session.NavigationSessionStateObserver
Expand All @@ -52,11 +56,10 @@ import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowApi
import com.mapbox.navigation.ui.maps.route.arrow.api.MapboxRouteArrowView
import com.mapbox.navigation.ui.maps.route.arrow.model.RouteArrowOptions
import com.mapbox.navigation.ui.maps.route.line.MapboxRouteLineApiExtensions.setRoutes
import com.mapbox.navigation.ui.maps.route.line.MapboxRouteLineApiExtensions.findClosestRoute
import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineApi
import com.mapbox.navigation.ui.maps.route.line.api.MapboxRouteLineView
import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineOptions
import com.mapbox.navigation.ui.maps.route.line.model.RouteLine
import com.mapbox.navigation.ui.tripprogress.api.MapboxTripProgressApi
import com.mapbox.navigation.ui.tripprogress.model.DistanceRemainingFormatter
import com.mapbox.navigation.ui.tripprogress.model.EstimatedTimeToArrivalFormatter
Expand All @@ -75,6 +78,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Locale

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class MapboxNavigationActivity : AppCompatActivity() {

/* ----- Layout binding reference ----- */
Expand All @@ -89,6 +93,8 @@ class MapboxNavigationActivity : AppCompatActivity() {
// location puck integration
private val navigationLocationProvider = NavigationLocationProvider()

private val waypoints = mutableListOf<Point>()

// camera
private lateinit var navigationCamera: NavigationCamera
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
Expand Down Expand Up @@ -229,18 +235,17 @@ class MapboxNavigationActivity : AppCompatActivity() {
}

private val routesObserver = RoutesObserver { result ->
if (result.routes.isNotEmpty()) {
if (result.navigationRoutes.isNotEmpty()) {
// generate route geometries asynchronously and render them
CoroutineScope(Dispatchers.Main).launch {
val result = routeLineAPI.setRoutes(
listOf(RouteLine(result.routes.first(), null))
)
routeLineAPI.setNavigationRoutes(
result.navigationRoutes,
mapboxNavigation.getAlternativeMetadataFor(result.navigationRoutes)
) {
val style = mapboxMap.getStyle()
if (style != null) {
routeLineView.renderRouteDrawData(style, result)
routeLineView.renderRouteDrawData(style, it)
}
}

// update the camera position to account for the new route
viewportDataSource.onRouteChanged(result.routes.first())
viewportDataSource.evaluate()
Expand All @@ -263,11 +268,52 @@ class MapboxNavigationActivity : AppCompatActivity() {
}
}

private val routesPreviewObserver = RoutesPreviewObserver { update ->
val routePreview = update.routesPreview
if (routePreview != null) {
routeLineAPI.setNavigationRoutes(
routePreview.routesList,
routePreview.alternativesMetadata
) {
val style = mapboxMap.getStyle()
if (style != null) {
routeLineView.renderRouteDrawData(style, it)
}
}
// update the camera position to account for the new route
viewportDataSource.onRouteChanged(routePreview.primaryRoute)
viewportDataSource.evaluate()

binding.mapView.gestures.removeOnMapClickListener(previewMapClickListener)
binding.mapView.gestures.addOnMapClickListener(previewMapClickListener)
} else {
binding.mapView.gestures.removeOnMapClickListener(previewMapClickListener)
}
}

private val navigationSessionStateObserver = NavigationSessionStateObserver {
logD("NavigationSessionState=$it", LOG_CATEGORY)
logD("sessionId=${mapboxNavigation.getNavigationSessionState().sessionId}", LOG_CATEGORY)
}

private val routeClickPadding = com.mapbox.android.gestures.Utils.dpToPx(30f)

private val previewMapClickListener = OnMapClickListener {
CoroutineScope(Dispatchers.Main).launch {
val result = routeLineAPI.findClosestRoute(
it,
binding.mapView.getMapboxMap(),
routeClickPadding
)

val routeFound = result.value?.navigationRoute
if (routeFound != null && routeFound != routeLineAPI.getPrimaryNavigationRoute()) {
mapboxNavigation.changeRoutesPreviewPrimaryRoute(routeFound)
}
}
false
}

@SuppressLint("MissingPermission")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -438,6 +484,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
mapboxNavigation.registerLocationObserver(locationObserver)
mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
mapboxNavigation.registerRoutesPreviewObserver(routesPreviewObserver)
}

override fun onStop() {
Expand All @@ -447,6 +494,7 @@ class MapboxNavigationActivity : AppCompatActivity() {
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
mapboxNavigation.unregisterLocationObserver(locationObserver)
mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
mapboxNavigation.unregisterRoutesPreviewObserver(routesPreviewObserver)
}

override fun onDestroy() {
Expand All @@ -464,19 +512,23 @@ class MapboxNavigationActivity : AppCompatActivity() {
Point.fromLngLat(it.longitude, it.latitude)
} ?: return

waypoints.add(destination)
val coordinates = listOf(origin) + waypoints
val layersList = listOf(mapboxNavigation.getZLevel()) + waypoints.map { null }
mapboxNavigation.requestRoutes(
RouteOptions.builder()
.applyDefaultNavigationOptions()
.applyLanguageAndVoiceUnitOptions(this)
.coordinatesList(listOf(origin, destination))
.layersList(listOf(mapboxNavigation.getZLevel(), null))
.alternatives(true)
.coordinatesList(coordinates)
.layersList(layersList)
.build(),
object : NavigationRouterCallback {
override fun onRoutesReady(
routes: List<NavigationRoute>,
routerOrigin: RouterOrigin
) {
setRouteAndStartNavigation(routes)
setRoutesPreview(routes)
}

override fun onFailure(
Expand All @@ -493,6 +545,19 @@ class MapboxNavigationActivity : AppCompatActivity() {
)
}

private fun setRoutesPreview(routes: List<NavigationRoute>) {
binding.navigateButton.apply {
visibility = View.VISIBLE
setOnClickListener {
visibility = View.GONE
setRouteAndStartNavigation(mapboxNavigation.getRoutesPreview()!!.routesList)
mapboxNavigation.setRoutesPreview(emptyList())
waypoints.clear()
}
}
mapboxNavigation.setRoutesPreview(routes)
}

private fun setRouteAndStartNavigation(route: List<NavigationRoute>) {
// set route
mapboxNavigation.setNavigationRoutes(route)
Expand Down
15 changes: 14 additions & 1 deletion examples/src/main/res/layout/layout_activity_navigation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
>

<com.mapbox.maps.MapView
android:id="@+id/mapView"
Expand Down Expand Up @@ -77,4 +79,15 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/routeOverview" />

<Button
android:id="@+id/navigateButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"
tools:visibility="visible"
android:text="Navigate"/>

</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
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.NavigationRoute
import com.mapbox.navigation.base.trip.model.RouteProgressState
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.directions.session.RoutesUpdatedResult
import com.mapbox.navigation.core.preview.RoutesPreviewUpdate
import com.mapbox.navigation.core.preview.RoutesPreviewUpdateReasons
import com.mapbox.navigation.instrumentation_tests.activity.EmptyTestActivity
import com.mapbox.navigation.instrumentation_tests.utils.MapboxNavigationRule
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routeProgressUpdates
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routesPreviewUpdates
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.routesUpdates
import com.mapbox.navigation.instrumentation_tests.utils.coroutines.sdkTest
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.BaseTest
import com.mapbox.navigation.testing.ui.utils.getMapboxAccessTokenFromResources
import com.mapbox.navigation.testing.ui.utils.runOnMainSync
import kotlinx.coroutines.flow.first
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class RoutesPreviewTest : BaseTest<EmptyTestActivity>(EmptyTestActivity::class.java) {

override fun setupMockLocation(): Location = mockLocationUpdatesRule.generateLocationUpdate {
latitude = 38.894721
longitude = -77.031991
}

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

@Before
fun setUp() {
runOnMainSync {
mapboxNavigation = MapboxNavigationProvider.create(
NavigationOptions.Builder(activity)
.accessToken(getMapboxAccessTokenFromResources(activity))
.build()
)
}
}

@Test
fun transitions_free_drive_to_preview_to_active_guidance_to_free_drive() = sdkTest {
var currentRoutesPreview: RoutesPreviewUpdate? = null
mapboxNavigation.registerRoutesPreviewObserver { update ->
currentRoutesPreview = update
}
var currentRoutes: RoutesUpdatedResult? = null
mapboxNavigation.registerRoutesObserver { update ->
currentRoutes = update
}
// initial free drive
mapboxNavigation.startTripSession()
assertNull(currentRoutesPreview)
assertNull(currentRoutes)
// set routes preview
val routes = RoutesProvider.dc_very_short(activity).toNavigationRoutes()
mapboxNavigation.setRoutesPreview(routes)
mapboxNavigation.routesPreviewUpdates()
.first { it.reason == RoutesPreviewUpdateReasons.PREVIEW_NEW }
assertEquals(RoutesPreviewUpdateReasons.PREVIEW_NEW, currentRoutesPreview?.reason)
assertEquals(routes, currentRoutesPreview!!.routesPreview!!.routesList)
assertNull(currentRoutes)
// start active guidance
mapboxNavigation.setNavigationRoutes(currentRoutesPreview!!.routesPreview!!.routesList)
mapboxNavigation.setRoutesPreview(emptyList())
mapboxNavigation.routesUpdates()
.first { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_NEW }
mapboxNavigation.routeProgressUpdates()
.first { it.currentState == RouteProgressState.TRACKING }
assertEquals(RoutesPreviewUpdateReasons.PREVIEW_CLEAN_UP, currentRoutesPreview?.reason)
assertNull(currentRoutesPreview!!.routesPreview)
assertEquals(RoutesExtra.ROUTES_UPDATE_REASON_NEW, currentRoutes!!.reason)
assertEquals(routes, currentRoutes!!.navigationRoutes)
// back to free drive
mapboxNavigation.setNavigationRoutes(emptyList())
mapboxNavigation.routesUpdates()
.first { it.reason == RoutesExtra.ROUTES_UPDATE_REASON_CLEAN_UP }
assertEquals(RoutesPreviewUpdateReasons.PREVIEW_CLEAN_UP, currentRoutesPreview?.reason)
assertNull(currentRoutesPreview!!.routesPreview)
assertEquals(RoutesExtra.ROUTES_UPDATE_REASON_CLEAN_UP, currentRoutes!!.reason)
assertEquals(emptyList<NavigationRoute>(), currentRoutes!!.navigationRoutes)
}

@Test
fun route_preview_in_parallel_to_active_guidance() = sdkTest {
var currentRoutesPreview: RoutesPreviewUpdate? = null
mapboxNavigation.registerRoutesPreviewObserver { update ->
currentRoutesPreview = update
}
var currentRoutes: RoutesUpdatedResult? = null
mapboxNavigation.registerRoutesObserver { update ->
currentRoutes = update
}
// initial free drive
mapboxNavigation.startTripSession()
assertNull(currentRoutesPreview)
assertNull(currentRoutes)
// set routes preview
val initialRoutes = RoutesProvider.dc_very_short(activity).toNavigationRoutes()
mapboxNavigation.setRoutesPreview(initialRoutes)
mapboxNavigation.routesPreviewUpdates()
.first { it.reason == RoutesPreviewUpdateReasons.PREVIEW_NEW }
// start active guidance
mapboxNavigation.setNavigationRoutes(currentRoutesPreview!!.routesPreview!!.routesList)
mapboxNavigation.setRoutesPreview(emptyList())
mapboxNavigation.routeProgressUpdates()
.first { it.currentState == RouteProgressState.TRACKING }
// preview a different route not leaving action guidance
val updatedRoutes = RoutesProvider.dc_very_short_two_legs(activity).toNavigationRoutes()
mapboxNavigation.setRoutesPreview(updatedRoutes)
mapboxNavigation.routesPreviewUpdates()
.first { it.routesPreview?.routesList == updatedRoutes }
assertEquals(
"active guidance should track initial routes",
initialRoutes,
currentRoutes?.navigationRoutes
)
// user decided to switch to previewed routes
mapboxNavigation.setNavigationRoutes(currentRoutesPreview!!.routesPreview!!.routesList)
mapboxNavigation.setRoutesPreview(emptyList())
mapboxNavigation.routeProgressUpdates().first {
it.navigationRoute == updatedRoutes[0]
}
}
}
Loading