diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bce98b0c..3344e7d2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -109,6 +109,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/mapbox/navigation/examples/ExamplesList.kt b/app/src/main/java/com/mapbox/navigation/examples/ExamplesList.kt
index f02671d8..061532e1 100644
--- a/app/src/main/java/com/mapbox/navigation/examples/ExamplesList.kt
+++ b/app/src/main/java/com/mapbox/navigation/examples/ExamplesList.kt
@@ -11,6 +11,7 @@ import com.mapbox.navigation.examples.junctions.ShowJunctionsActivity
import com.mapbox.navigation.examples.location.ShowCurrentLocationActivity
import com.mapbox.navigation.examples.maneuvers.ShowManeuversActivity
import com.mapbox.navigation.examples.multiplewaypoints.MultipleWaypointsActivity
+import com.mapbox.navigation.examples.preview.PreviewActivity
import com.mapbox.navigation.examples.replay.ReplayHistoryActivity
import com.mapbox.navigation.examples.routeline.RenderRouteLineActivity
import com.mapbox.navigation.examples.signboard.MapboxSignboardActivity
@@ -82,6 +83,12 @@ fun Context.examplesList() = listOf(
getString(R.string.description_turn_by_turn),
TurnByTurnExperienceActivity::class.java
),
+ MapboxExample(
+ ContextCompat.getDrawable(this, R.drawable.mapbox_route_preview),
+ getString(R.string.title_preview),
+ getString(R.string.description_preview),
+ PreviewActivity::class.java
+ ),
MapboxExample(
ContextCompat.getDrawable(this, R.drawable.mapbox_screenshot_multiple_waypoints),
getString(R.string.title_multiple_way_points),
diff --git a/app/src/main/java/com/mapbox/navigation/examples/preview/PreviewActivity.kt b/app/src/main/java/com/mapbox/navigation/examples/preview/PreviewActivity.kt
new file mode 100644
index 00000000..b2c07604
--- /dev/null
+++ b/app/src/main/java/com/mapbox/navigation/examples/preview/PreviewActivity.kt
@@ -0,0 +1,390 @@
+package com.mapbox.navigation.examples.preview
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.res.Resources
+import android.location.Location
+import android.os.Bundle
+import android.view.View
+import androidx.core.content.ContextCompat
+import com.mapbox.api.directions.v5.models.Bearing
+import com.mapbox.api.directions.v5.models.RouteOptions
+import com.mapbox.geojson.Point
+import com.mapbox.maps.EdgeInsets
+import com.mapbox.maps.MapView
+import com.mapbox.maps.MapboxMap
+import com.mapbox.maps.Style
+import com.mapbox.maps.plugin.LocationPuck2D
+import com.mapbox.maps.plugin.animation.camera
+import com.mapbox.maps.plugin.gestures.gestures
+import com.mapbox.maps.plugin.locationcomponent.location
+import com.mapbox.navigation.base.extensions.applyDefaultNavigationOptions
+import com.mapbox.navigation.base.extensions.applyLanguageAndVoiceUnitOptions
+import com.mapbox.navigation.base.options.NavigationOptions
+import com.mapbox.navigation.base.route.NavigationRoute
+import com.mapbox.navigation.base.route.NavigationRouterCallback
+import com.mapbox.navigation.base.route.RouterFailure
+import com.mapbox.navigation.base.route.RouterOrigin
+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.trip.session.LocationMatcherResult
+import com.mapbox.navigation.core.trip.session.LocationObserver
+import com.mapbox.navigation.examples.R
+import com.mapbox.navigation.examples.databinding.ActivityPreviewBinding
+import com.mapbox.navigation.ui.maps.camera.NavigationCamera
+import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSource
+import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler
+import com.mapbox.navigation.ui.maps.camera.transition.NavigationCameraTransitionOptions
+import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
+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
+
+/**
+ * This example demonstrates:
+ * - A basic route preview;
+ * - Switching between free drive, preview, and active guidance.
+ *
+ * Before running the example make sure you have put your access_token in the correct place
+ * inside [app/src/main/res/values/mapbox_access_token.xml]. If not present then add this file
+ * at the location mentioned above and add the following content to it
+ *
+ *
+ *
+ *
+ *
+ *
+ * The example assumes that you have granted location permissions and does not enforce it. However,
+ * the permission is essential for proper functioning of this example.
+ *
+ * How to use this example:
+ * - You can long-click the map to select a destination.
+ * - Click "Start active guidance" to start navigation.
+ * - Click "Finish active guidance" to switch back to free drive.
+ */
+class PreviewActivity : Activity() {
+
+ /**
+ * Bindings to the example layout.
+ */
+ private lateinit var binding: ActivityPreviewBinding
+
+ /**
+ * Mapbox Maps entry point obtained from the [MapView].
+ * You need to get a new reference to this object whenever the [MapView] is recreated.
+ */
+ private lateinit var mapboxMap: MapboxMap
+
+ /**
+ * [NavigationLocationProvider] is a utility class that helps to provide location updates generated by the Navigation SDK
+ * to the Maps SDK in order to update the user location indicator on the map.
+ */
+ private val navigationLocationProvider = NavigationLocationProvider()
+
+ /**
+ * Mapbox Navigation entry point. There should only be one instance of this object for the app.
+ * You can use [MapboxNavigationProvider] to help create and obtain that instance.
+ */
+ private lateinit var mapboxNavigation: MapboxNavigation
+
+ /**
+ * Used to execute camera transitions based on the data generated by the [viewportDataSource].
+ * This includes transitions from route overview to route following and continuously updating the camera as the location changes.
+ */
+ private lateinit var navigationCamera: NavigationCamera
+
+ /**
+ * Produces the camera frames based on the location and routing data for the [navigationCamera] to execute.
+ */
+ private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
+
+ /*
+ * Below are generated camera padding values to ensure that the route fits well on screen while
+ * other elements are overlaid on top of the map (including instruction view, buttons, etc.)
+ */
+ private val pixelDensity = Resources.getSystem().displayMetrics.density
+ private val overviewPadding: EdgeInsets by lazy {
+ EdgeInsets(
+ 140.0 * pixelDensity,
+ 40.0 * pixelDensity,
+ 120.0 * pixelDensity,
+ 40.0 * pixelDensity
+ )
+ }
+ private val followingPadding: EdgeInsets by lazy {
+ EdgeInsets(
+ 180.0 * pixelDensity,
+ 40.0 * pixelDensity,
+ 150.0 * pixelDensity,
+ 40.0 * pixelDensity
+ )
+ }
+
+ /**
+ * The observer gets notified with location updates.
+ *
+ * Exposes raw updates coming directly from the location services
+ * and the updates enhanced by the Navigation SDK (cleaned up and matched to the road).
+ */
+ private val locationObserver = object : LocationObserver {
+ var firstLocationUpdateReceived = false
+
+ override fun onNewRawLocation(rawLocation: Location) {
+ // Use raw location only for cycling and walking cases.
+ // For vehicles use map matched location.
+ }
+
+ override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
+ val enhancedLocation = locationMatcherResult.enhancedLocation
+ // update location puck's position on the map
+ navigationLocationProvider.changePosition(
+ location = enhancedLocation,
+ keyPoints = locationMatcherResult.keyPoints,
+ )
+
+ // update camera position to account for new location
+ viewportDataSource.onLocationChanged(enhancedLocation)
+ viewportDataSource.evaluate()
+
+ // if this is the first location update the activity has received,
+ // it's best to immediately move the camera to the current user location
+ if (!firstLocationUpdateReceived) {
+ firstLocationUpdateReceived = true
+ navigationCamera.requestNavigationCameraToOverview(
+ stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
+ .maxDuration(0) // instant transition
+ .build()
+ )
+ }
+ }
+ }
+
+ /**
+ * The observer gets notified whenever the tracked routes change.
+ * Use this observer to draw routes during active guidance or to cleanup when navigation switches to free drive.
+ * The observer isn't triggered in free drive.
+ */
+ private val routesObserver = RoutesObserver { routeUpdateResult ->
+ val navigationRoutes = routeUpdateResult.navigationRoutes
+ if (navigationRoutes.isNotEmpty()) {
+ routeLineApi.setNavigationRoutes(
+ navigationRoutes,
+ // alternative metadata is available only in active guidance.
+ mapboxNavigation.getAlternativeMetadataFor(navigationRoutes)
+ ) { value ->
+ mapboxMap.getStyle()?.apply {
+ routeLineView.renderRouteDrawData(this, value)
+ }
+ }
+
+ // update the camera position to account for the new route
+ viewportDataSource.onRouteChanged(navigationRoutes.first())
+ viewportDataSource.evaluate()
+ } else {
+ // remove route line from the map
+ mapboxMap.getStyle()?.let { style ->
+ routeLineApi.clearRouteLine { value ->
+ routeLineView.renderClearRouteLineValue(
+ style,
+ value
+ )
+ }
+ }
+ // remove the route reference from camera position evaluations
+ viewportDataSource.clearRouteData()
+ viewportDataSource.evaluate()
+ navigationCamera.requestNavigationCameraToOverview()
+ }
+ }
+
+ /**
+ * Generates updates for the [routeLineView] with the geometries and properties of the routes that should be drawn on the map.
+ */
+ private lateinit var routeLineApi: MapboxRouteLineApi
+
+ /**
+ * Draws route lines on the map based on the data from the [routeLineApi]
+ */
+ private lateinit var routeLineView: MapboxRouteLineView
+
+ @SuppressLint("MissingPermission")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityPreviewBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ mapboxMap = binding.mapView.getMapboxMap()
+
+ // initialize the location puck
+ binding.mapView.location.apply {
+ this.locationPuck = LocationPuck2D(
+ bearingImage = ContextCompat.getDrawable(
+ this@PreviewActivity,
+ R.drawable.mapbox_navigation_puck_icon
+ )
+ )
+ setLocationProvider(navigationLocationProvider)
+ enabled = true
+ }
+
+ // initialize Mapbox Navigation
+ mapboxNavigation = if (MapboxNavigationProvider.isCreated()) {
+ MapboxNavigationProvider.retrieve()
+ } else {
+ MapboxNavigationProvider.create(
+ NavigationOptions.Builder(this.applicationContext)
+ .accessToken(getString(R.string.mapbox_access_token))
+ .build()
+ )
+ }
+
+ // initialize Navigation Camera
+ viewportDataSource = MapboxNavigationViewportDataSource(mapboxMap)
+ navigationCamera = NavigationCamera(
+ mapboxMap,
+ binding.mapView.camera,
+ viewportDataSource
+ )
+ // set camera paddings
+ viewportDataSource.overviewPadding = overviewPadding
+ viewportDataSource.followingPadding = followingPadding
+ // set the animations lifecycle listener to ensure the NavigationCamera stops
+ // automatically following the user location when the map is interacted with
+ binding.mapView.camera.addCameraAnimationsLifecycleListener(
+ NavigationBasicGesturesHandler(navigationCamera)
+ )
+
+ // load map style
+ mapboxMap.loadStyleUri(
+ Style.MAPBOX_STREETS
+ ) {
+ // add long click listener that search for a route to the clicked destination
+ binding.mapView.gestures.addOnMapLongClickListener { point ->
+ findRoute(point)
+ true
+ }
+ }
+
+ // initialize route line, the withRouteLineBelowLayerId is specified to place
+ // the route line below road labels layer on the map
+ // the value of this option will depend on the style that you are using
+ // and under which layer the route line should be placed on the map layers stack
+ val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(this)
+ .withRouteLineBelowLayerId("road-label")
+ .build()
+ routeLineApi = MapboxRouteLineApi(mapboxRouteLineOptions)
+ routeLineView = MapboxRouteLineView(mapboxRouteLineOptions)
+
+ // We recommend starting a trip session for routes preview to get, display,
+ // and use for route request a map matched location.
+ // See [PreviewActivity#locationObserver].
+ mapboxNavigation.startTripSession()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ mapboxNavigation.registerLocationObserver(locationObserver)
+ mapboxNavigation.registerRoutesObserver(routesObserver)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ mapboxNavigation.unregisterLocationObserver(locationObserver)
+ mapboxNavigation.unregisterRoutesObserver(routesObserver)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ mapboxNavigation.onDestroy()
+ }
+
+ private fun findRoute(destination: Point) {
+ val originLocation = navigationLocationProvider.lastLocation
+ val originPoint = originLocation?.let {
+ Point.fromLngLat(it.longitude, it.latitude)
+ } ?: return
+
+ // execute a route request
+ // it's recommended to use the
+ // applyDefaultNavigationOptions and applyLanguageAndVoiceUnitOptions
+ // that make sure the route request is optimized
+ // to allow for support of all of the Navigation SDK features
+ mapboxNavigation.requestRoutes(
+ RouteOptions.builder()
+ .applyDefaultNavigationOptions()
+ .applyLanguageAndVoiceUnitOptions(this)
+ .coordinatesList(listOf(originPoint, destination))
+ // provide the bearing for the origin of the request to ensure
+ // that the returned route faces in the direction of the current user movement
+ .bearingsList(
+ listOf(
+ Bearing.builder()
+ .angle(originLocation.bearing.toDouble())
+ .degrees(45.0)
+ .build(),
+ null
+ )
+ )
+ .layersList(listOf(mapboxNavigation.getZLevel(), null))
+ .alternatives(true)
+ .build(),
+ object : NavigationRouterCallback {
+ override fun onRoutesReady(
+ routes: List,
+ routerOrigin: RouterOrigin
+ ) {
+ previewRoutes(routes)
+ }
+
+ override fun onFailure(
+ reasons: List,
+ routeOptions: RouteOptions
+ ) {
+ // no impl
+ }
+
+ override fun onCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) {
+ // no impl
+ }
+ }
+ )
+ }
+
+ private fun previewRoutes(routes: List) {
+ // Mapbox navigation doesn't have a special state for route preview.
+ // Preview state is managed by an application.
+ // Display the routes you received on the map.
+ routeLineApi.setNavigationRoutes(routes) { value ->
+ mapboxMap.getStyle()?.apply {
+ routeLineView.renderRouteDrawData(this, value)
+ // update the camera position to account for the new route
+ viewportDataSource.onRouteChanged(routes.first())
+ viewportDataSource.evaluate()
+ navigationCamera.requestNavigationCameraToOverview()
+ }
+ }
+ binding.buttonStartActiveGuidance.apply {
+ visibility = View.VISIBLE
+ setOnClickListener {
+ startActiveGuidance(routes)
+ }
+ }
+ }
+
+ private fun startActiveGuidance(routes: List) {
+ binding.buttonStartActiveGuidance.visibility = View.GONE
+ // Set routes to switch navigator from free drive to active guidance state.
+ // In active guidance navigator emits your route progress, voice and banner instructions, etc.
+ mapboxNavigation.setNavigationRoutes(routes)
+ navigationCamera.requestNavigationCameraToFollowing()
+ binding.buttonFinishActiveGuidance.apply {
+ visibility = View.VISIBLE
+ setOnClickListener {
+ visibility = View.GONE
+ // Set an empty list to finish active guidance and switch back to free drive state.
+ mapboxNavigation.setNavigationRoutes(emptyList())
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/mapbox/navigation/examples/preview/res/drawable/mapbox_route_preview.jpg b/app/src/main/java/com/mapbox/navigation/examples/preview/res/drawable/mapbox_route_preview.jpg
new file mode 100644
index 00000000..094805cb
Binary files /dev/null and b/app/src/main/java/com/mapbox/navigation/examples/preview/res/drawable/mapbox_route_preview.jpg differ
diff --git a/app/src/main/java/com/mapbox/navigation/examples/preview/res/layout/activity_preview.xml b/app/src/main/java/com/mapbox/navigation/examples/preview/res/layout/activity_preview.xml
new file mode 100644
index 00000000..454bd615
--- /dev/null
+++ b/app/src/main/java/com/mapbox/navigation/examples/preview/res/layout/activity_preview.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/mapbox/navigation/examples/preview/res/values/strings.xml b/app/src/main/java/com/mapbox/navigation/examples/preview/res/values/strings.xml
new file mode 100644
index 00000000..07a80819
--- /dev/null
+++ b/app/src/main/java/com/mapbox/navigation/examples/preview/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+ Route Preview
+ Preview a route before navigation.
+ Start active guidance
+ Finish active guidance
+
+