Copilot

Note

This example is a part of the Navigation SDK Examples. You can find the values for all referenced resources in the res directory. For example, see res/values/strings.xml for R.string.* references used in this example.

CopilotActivity.kt
package com.mapbox.navigation.examples.preview.copilot
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.annotation.RawRes
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.mapbox.api.directions.v5.models.DirectionsResponse
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.options.CopilotOptions
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.base.route.RouterOrigin
import com.mapbox.navigation.base.route.toNavigationRoutes
import com.mapbox.navigation.copilot.HistoryPoint
import com.mapbox.navigation.copilot.MapboxCopilot
import com.mapbox.navigation.copilot.SearchResultUsed
import com.mapbox.navigation.copilot.SearchResultUsedEvent
import com.mapbox.navigation.copilot.SearchResults
import com.mapbox.navigation.copilot.SearchResultsEvent
import com.mapbox.navigation.core.DeveloperMetadataObserver
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.lifecycle.requireMapboxNavigation
import com.mapbox.navigation.core.telemetry.events.FeedbackEvent
import com.mapbox.navigation.core.trip.session.NavigationSessionState
import com.mapbox.navigation.core.trip.session.NavigationSessionStateObserver
import com.mapbox.navigation.examples.preview.R
import com.mapbox.navigation.examples.preview.databinding.MapboxActivityCopilotBinding
/**
* The example demonstrates how to integrate and work with [MapboxCopilot].
* See [CopilotViewModel] to learn about [MapboxCopilot]'s lifecycle and
* when to [MapboxCopilot.start] / [MapboxCopilot.stop].
*
* Copilot is a [MapboxNavigationObserver], so it's tied to the [MapboxNavigation] lifecycle automatically.
* We recommended tracking the [DeveloperMetadata.copilotSessionId] (see [DeveloperMetadataObserver]) so that
* Mapbox teams can better act on specific end-user feedback.
* This ID helps Mapbox teams find the respective traces and troubleshoot issues faster.
*
* **As the application developer, you are responsible for communicating to drivers about the data that is being
* collected from their drives, including what kind of data is being collected and when it is collected.**
*
* Nav SDK exposes configuration settings (see [NavigationOptions.copilotOptions]) to use Copilot in two ways:
* 1) Automatic data collection:
* - Enable Copilot for all trips performed by a specific driver (default option).
* 2) Manual data collection:
* - Copilot data is only sent when an end user submits negative feedback about a specific route to help
* take action on the issue.
* Data collection for Copilot is tightly coupled to the Navigation SDK Feedback, which means this is only effective
* if the feedback events are pushed through [MapboxNavigation] Feedback APIs
* (see [Feedback documentation](https://docs.mapbox.com/android/navigation/guides/feedback/) and
* [MapboxNavigation.postUserFeedback] / [MapboxNavigation.provideFeedbackMetadataWrapper])
*
* If you would like to provide Search analytics into Copilot, you can send the Search events over to
* Copilot (see [MapboxCopilot.push]).
* This information would include whether a routable point for navigation was available.
* Copilot helps understand the impact of search results to a navigation session (arrival experience, routable points).
*
* WARNING: Mapbox Copilot is currently in public-preview. Copilot-related entities and APIs are currently marked
* as [ExperimentalPreviewMapboxNavigationAPI] and subject to change.
* These markings will be removed when the feature is generally available.
*
* Copilot is a library included in the Navigation SDK that Processes full-trip-trace longitude and
* latitude data ("**Copilot**"). Copilot is turned off by default and optionally enabled by Customer at the
* application developer level to improve feedback resolution. If Customer enables Copilot, Customer shall obtain
* and maintain all necessary consents and permissions, including providing notice to and obtaining End
* Users' affirmative express consent before any access or use of Copilot.
*
* Before running the example make sure you have put your access_token in the correct place
* inside [app-preview/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
*
* <?xml version="1.0" encoding="utf-8"?>
* <resources xmlns:tools="http://schemas.android.com/tools">
* <string name="mapbox_access_token"><PUT_YOUR_ACCESS_TOKEN_HERE></string>
* </resources>
*
* How to use the example:
* - Start the example
* - Look at how Navigation session state is Idle
* - Click on Play button - starts Free Drive trip session
* - Look at how Navigation session state changes to Free Drive and Copilot session ID is generated
* - Click on Push Feedback button - pushes FeedbackEvent
* - Click on Push Search button - pushes SearchResultsEvent and SearchResultUsedEvent
* - Click on Set route button - session transitions to Active Guidance
* - Look at how Navigation session state changes to Active Guidance and Copilot session ID is re-generated
* - Click on Push Feedback button - pushes FeedbackEvent
* - Click on Push Search button - pushes SearchResultsEvent and SearchResultUsedEvent
* - Click on Stop button - session transitions to Free Drive
* - Look at how Navigation session state changes to Active Guidance and Copilot session ID is re-generated
* - Click on Stop button - session transitions to Idle
* - Look at how Navigation session state changes to Idle and Copilot session ID empty
*/
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class CopilotActivity : AppCompatActivity() {
private lateinit var binding: MapboxActivityCopilotBinding
private lateinit var copilotViewModel: CopilotViewModel
private var navigationSessionState: NavigationSessionState = NavigationSessionState.Idle
private val navigationSessionStateObserver = NavigationSessionStateObserver {
navigationSessionState = it
val sessionStateText = "Navigation session state:"
when (it) {
is NavigationSessionState.Idle -> {
binding.navigationSessionState.text =
"$sessionStateText Idle"
binding.startStopNavigation.setImageResource(R.drawable.ic_start)
binding.setRoutes.visibility = View.GONE
binding.feedbackPush.visibility = View.GONE
binding.searchPush.visibility = View.GONE
}
is NavigationSessionState.FreeDrive -> {
binding.navigationSessionState.text =
"$sessionStateText Free Drive"
binding.startStopNavigation.setImageResource(R.drawable.ic_stop)
binding.setRoutes.visibility = View.VISIBLE
binding.feedbackPush.visibility = View.VISIBLE
binding.searchPush.visibility = View.VISIBLE
}
is NavigationSessionState.ActiveGuidance -> {
binding.navigationSessionState.text =
"$sessionStateText Active Guidance"
binding.startStopNavigation.setImageResource(R.drawable.ic_stop)
binding.setRoutes.visibility = View.GONE
binding.feedbackPush.visibility = View.VISIBLE
binding.searchPush.visibility = View.VISIBLE
}
}
}
private val developerMetadataObserver = DeveloperMetadataObserver {
val copilotSessionIdText = "Copilot session ID:"
binding.copilotSessionId.text = "$copilotSessionIdText ${it.copilotSessionId}"
}
private val mapboxNavigation by requireMapboxNavigation(
onCreatedObserver = object : MapboxNavigationObserver {
override fun onAttached(mapboxNavigation: MapboxNavigation) {
mapboxNavigation.registerNavigationSessionStateObserver(
navigationSessionStateObserver
)
mapboxNavigation.registerDeveloperMetadataObserver(developerMetadataObserver)
}
override fun onDetached(mapboxNavigation: MapboxNavigation) {
mapboxNavigation.unregisterNavigationSessionStateObserver(
navigationSessionStateObserver
)
mapboxNavigation.unregisterDeveloperMetadataObserver(developerMetadataObserver)
}
}
) {
MapboxNavigationApp.setup(
NavigationOptions.Builder(this)
.accessToken(getString(R.string.mapbox_access_token))
.copilotOptions(
// Set shouldSendHistoryOnlyWithFeedback to true if you want to sent Copilot traces
// only when an end user submits negative feedback
CopilotOptions.Builder().shouldSendHistoryOnlyWithFeedback(false).build()
)
.build()
)
}
private lateinit var routeResponse: DirectionsResponse
@SuppressLint("MissingPermission")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = MapboxActivityCopilotBinding.inflate(layoutInflater)
setContentView(binding.root)
copilotViewModel = ViewModelProvider(this)[CopilotViewModel::class.java]
routeResponse =
DirectionsResponse.fromJson(readRawFileText(this, R.raw.multiple_routes))
binding.startStopNavigation.setOnClickListener {
when (navigationSessionState) {
is NavigationSessionState.Idle -> {
mapboxNavigation.setNavigationRoutes(emptyList())
mapboxNavigation.startTripSession()
}
is NavigationSessionState.FreeDrive -> {
mapboxNavigation.stopTripSession()
}
is NavigationSessionState.ActiveGuidance -> {
mapboxNavigation.setNavigationRoutes(emptyList())
}
}
}
binding.setRoutes.setOnClickListener {
mapboxNavigation.setNavigationRoutes(
routeResponse.routes().toNavigationRoutes(RouterOrigin.Onboard),
)
}
binding.feedbackPush.setOnClickListener {
// Call postUserFeedback every time user submits feedback
mapboxNavigation.postUserFeedback(
FeedbackEvent.POSITIONING_ISSUE,
"Test feedback",
FeedbackEvent.UI,
"encoded_screenshot",
)
Toast.makeText(this, "Feedback event pushed!", Toast.LENGTH_SHORT).show()
}
binding.searchPush.setOnClickListener {
// Push a SearchResultsEvent every time Search results response is retrieved
MapboxCopilot.push(
SearchResultsEvent(
SearchResults("mapbox", "https://mapbox.com", null, null, "?query=test1", null)
)
)
// Push a SearchResultUsedEvent every time a Search result is selected
MapboxCopilot.push(
SearchResultUsedEvent(
SearchResultUsed(
"mapbox",
"test_id",
"mapbox_poi",
"mapbox_address",
HistoryPoint(-77.03396910343713, 38.89992797324407),
null,
)
)
)
Toast.makeText(this, "Search events pushed!", Toast.LENGTH_SHORT).show()
}
}
private fun readRawFileText(context: Context, @RawRes res: Int): String =
context.resources.openRawResource(res).bufferedReader().use { it.readText() }
}
CopilotViewModel.kt
package com.mapbox.navigation.examples.preview.copilot
import androidx.lifecycle.ViewModel
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.copilot.MapboxCopilot
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
/**
* [ViewModel] used in [CopilotActivity]. It handles [MapboxCopilot]'s lifecycle by
* managing when to call [MapboxCopilot.start] / [MapboxCopilot.stop] APIs.
*
* Copilot is a [MapboxNavigationObserver], so it's tied to the [MapboxNavigation] lifecycle automatically.
*
* Copilot is an opt-in feature (see [MapboxCopilot.start] and [MapboxCopilot.stop]), which means you
* have the choice to enable it for your users (drivers). Depending on the use case, you can enable Copilot
* for either all drivers (for example, during a pilot) or a subset of them.
*
* We recommend to tie [MapboxCopilot.start] and [MapboxCopilot.stop] APIs to app's lifecycle.
*
* WARNING: Mapbox Copilot is currently in public-preview. Copilot-related entities and APIs are currently marked
* as [ExperimentalPreviewMapboxNavigationAPI] and subject to change.
* These markings will be removed when the feature is generally available.
*
* Copilot is a library included in the Navigation SDK that Processes full-trip-trace longitude and
* latitude data ("**Copilot**"). Copilot is turned off by default and optionally enabled by Customer at the
* application developer level to improve feedback resolution. If Customer enables Copilot, Customer shall obtain
* and maintain all necessary consents and permissions, including providing notice to and obtaining End
* Users' affirmative express consent before any access or use of Copilot.
*/
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class CopilotViewModel : ViewModel() {
init {
MapboxCopilot.start()
}
override fun onCleared() {
MapboxCopilot.stop()
}
}
mapbox_activity_copilot
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent">
<TextView
android:id="@+id/navigationSessionState"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
tools:text="Navigation Session State: Idle"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/copilotSessionId"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
tools:text="Copilot session ID: e0b2dea9-7246-48e4-ac2e-bb6407535cc9"
android:gravity="center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigationSessionState" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/startStopNavigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:srcCompat="@drawable/ic_start" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/setRoutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_route"
android:visibility="visible"
android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/startStopNavigation"
app:layout_constraintBottom_toBottomOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/feedbackPush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_feedback"
android:visibility="visible"
android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/setRoutes"
app:layout_constraintBottom_toBottomOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/searchPush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_search"
android:visibility="visible"
android:layout_marginBottom="8dp"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/feedbackPush"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>