Build a navigation app

This section details the different features of the library that you can make use of to implement the functionality of your turn-by-turn navigation app.

Declare navigation support in your manifest

Your navigation app needs to declare the androidx.car.app.category.NAVIGATION car app category in the intent filter of its CarAppService:

<application>
    ...
   <service
       ...
        android:name=".MyNavigationCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService" />
        <category android:name="androidx.car.app.category.NAVIGATION"/>
      </intent-filter>
    </service>
    ...
<application>

Support navigation intents

In order to support navigation Intents to your app, including those coming from the Google Assistant using a voice query, your app needs to handle the CarContext.ACTION_NAVIGATE intent in its Session.onCreateScreen and Session.onNewIntent.

See the documentation of CarContext.startCarApp for details on the format of the intent.

Access the navigation templates

Navigation apps can access the following templates specifically designed for navigation apps. All these templates display a surface in the background that your app can access in order to draw your map, alongside other information provided by your app which varies per template.

  • NavigationTemplate: displays the map along with an optional informational message or routing directions and travel estimates during active navigation.
  • PlaceListNavigationTemplate: displays a list of places that can have corresponding markers drawn in the map.
  • RoutePreviewNavigationTemplate: displays a list of routes one of which can be selected and highlighted in the map.

For more details on how to design your navigation app’s user interface using those templates, see the Android for Cars App Library Design Guidelines.

In order to get access to the navigation templates, your app needs to declare the androidx.car.app.NAVIGATION_TEMPLATES permission in its AndroidManifest.xml:

<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES"/>

Drawing the Map

Navigation applications can access a Surface to draw the map on relevant templates.

A SurfaceContainer object can then be accessed by setting a SurfaceCallback instance to the AppManager car service:

Kotlin

carContext.getCarService(AppManager::class.java).setSurfaceCallback(surfaceCallback)

Java

carContext.getCarService(AppManager.class).setSurfaceCallback(surfaceCallback);

The SurfaceCallback provides a callback when the SurfaceContainer is available along with other callbacks when the properties of the Surface change.

In order to get access to the surface, your app needs to declare the androidx.car.app.ACCESS_SURFACE permission in its AndroidManifest.xml:

<uses-permission android:name="androidx.car.app.ACCESS_SURFACE"/>

The map’s visible area

The host may draw user interface elements for the different templates on top of the map. The host will communicate the area that is guaranteed to be unoccluded and fully visible to the user by calling the SurfaceCallback.onVisibleAreaChanged method. Also, in order to minimize the number of changes, the host will also call the SurfaceCallback.onStableAreaChanged method with the smallest rectangle, which will always be visible based on the current template.

For example, when a navigation app is using the NavigationTemplate with an action strip on top, the action strip may hide itself when the user has not interacted with the screen for a while to make more space for the map. In this case, there will be a callback to onStableAreaChanged and onVisibleAreaChanged with the same rectangle. When the action strip is hidden, only onVisibleAreaChangedwill be called with the larger area. If the user interacts with the screen, then again only onVisibleAreaChanged is called with the first rectangle.

Dark mode

Navigation applications must redraw their map onto the Surface instance with the proper dark colors when the host determines that conditions warrant it, as described in the Android Auto app quality guidelines.

In order to decide on whether you should draw a dark map you can use the CarContext.isDarkMode method. Whenever the dark mode status changes, you will receive a call to Session.onCarConfigurationChanged.

Navigation applications must communicate additional navigation metadata with the host. The host makes use of the information to provide information to the vehicle head unit and to prevent navigation applications from clashing over shared resources.

Navigation metadata is provided through the NavigationManager car service accessible from the CarContext:

Kotlin

val navigationManager = carContext.getCarService(NavigationManager::class.java)

Java

NavigationManager navigationManager = carContext.getCarService(NavigationManager.class);

Starting, ending, and stopping navigation

In order for the host to manage multiple navigation apps, routing notifications, and vehicle cluster data, it needs to be aware of the current state of navigation. When a user starts navigation, the app should call NavigationManager.navigationStarted. Similarly, when navigation ends, for example when the user arrives at their destination or the user cancels navigation, the app should call NavigationManager.navigationEnded.

You should only call NavigationManager.navigationEnded when the user is finished navigating. For example, if you need to recalculate the route in the middle of a trip, use Trip.Builder.setLoading(true) instead.

Occasionally, the host will need an app to stop navigation and will call stopNavigation in a NavigationManagerListener object provided by your app through NavigationManager.setListener. The app must then stop issuing next-turn information in the cluster display, navigation notifications, and voice guidance.

Trip information

During active navigation, the app should call NavigationManager.updateTrip. The information provided in this call will be used in the vehicle’s cluster and heads-up displays. Not all information may be displayed to the user depending on the particular vehicle being driven. For example, the Desktop Head Unit shows the Step added to the Trip, but does not show the Destination information.

In order to test that the information is reaching the cluster the Desktop Head Unit (DHU) tool can be configured to show a simple cluster display. Create an cluster.ini file with the following contents:

[general]
instrumentcluster = true

You can then invoke the DHU with an additional command line parameter:

dhu -c cluster.ini

Customize TravelEstimate with text and/or an icon

To customize the travel estimate with text and/or an icon, use the TravelEstimate.Builder's setTripIcon and/or setTripText methods. The NavigationTemplate uses TravelEstimate to optionally set text and icons alongside, or in place of, the estimated time of arrival, remaining time, and remaining distance.

Figure 1. Travel estimate with custom icon and text

The following snippet uses TravelEstimate.Builder's setTripIcon and setTripText methods to customize the travel estimate:

Kotlin

TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...))
      ...
      .setTripIcon(CarIcon.Builder(...).build())
      .setTripText(CarText.create(...))
      .build()

Java

new TravelEstimate.Builder(Distance.create(...), DateTimeWithZone.create(...))
      ...
      .setTripIcon(CarIcon.Builder(...).build())
      .setTripText(CarText.create(...))
      .build();

Turn-by-turn notifications

The turn-by-turn (TBT) navigation instructions can be given with a frequently updated navigation notification. In order to be treated as a navigation notification in the car screen, your notification's builder must do the following:

  1. Mark the notification as ongoing with the NotificationCompat.Builder.setOngoing method.
  2. Set the notification’s category to Notification.CATEGORY_NAVIGATION.
  3. Extend the notification with a CarAppExtender.

A navigation notification will be displayed in the rail widget at the bottom of the car screen. If the notification's importance level is set to IMPORTANCE_HIGH, it will also be displayed as a heads-up notification (HUN). If the importance is not set with the CarAppExtender.Builder.setImportance method, the notification channel's importance will be used.

The app can set a PendingIntent in the CarAppExtender that will be sent to the app when the user taps on the HUN or the rail widget.

If NotificationCompat.Builder.setOnlyAlertOnce is called with a value of true, a high-importance notification will alert only once in the HUN.

The following snippet shows how to build a navigation notification:

Kotlin

NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    ...
    .setOnlyAlertOnce(true)
    .setOngoing(true)
    .setCategory(NotificationCompat.CATEGORY_NAVIGATION)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(carScreenTitle)
            ...
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_OPEN_APP.hashCode(),
                    Intent(ACTION_OPEN_APP).setComponent(
                        ComponentName(context, MyNotificationReceiver::class.java)),
                        0))
            .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH)
            .build())
    .build()

Java

new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    ...
    .setOnlyAlertOnce(true)
    .setOngoing(true)
    .setCategory(NotificationCompat.CATEGORY_NAVIGATION)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(carScreenTitle)
            ...
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_OPEN_APP.hashCode(),
                    new Intent(ACTION_OPEN_APP).setComponent(
                        new ComponentName(context, MyNotificationReceiver.class)),
                        0))
            .setImportance(NotificationManagerCompat.IMPORTANCE_HIGH)
            .build())
    .build();

Guidelines for turn-by-turn notifications

Navigation apps should update the TBT notification regularly on distance changes, which updates the rail widget, and only show the notification as a HUN. Apps can control the HUN behavior by setting the notification's importance with the CarAppExtender.Builder.setImportance method. Setting the importance to IMPORTANCE_HIGH will show a HUN, and setting it to any other value will only update the rail widget.

Refresh PlaceListNavigationTemplate content

You can allow drivers to refresh content with the tap of a button while browsing lists of places built with PlaceListNavigationTemplate. Implement the OnContentRefreshListener interface's onContentRefreshRequested method and use PlaceListNavigationTemplate.Builder.setOnContentRefreshListener to set the listener on the template to enable list refresh.

The following snippet shows setting the listener on the template:

Kotlin

PlaceListNavigationTemplate.Builder()
    ...
    .setOnContentRefreshListener {
        // Execute any desired logic
        ...
        // Then call invalidate() so onGetTemplate() is called again
        invalidate()
    }
    .build()

Java

new PlaceListNavigationTemplate.Builder()
        ...
        .setOnContentRefreshListener(() -> {
            // Execute any desired logic
            ...
            // Then call invalidate() so onGetTemplate() is called again
            invalidate();
        })
        .build();

The refresh button is only shown in the header of the PlaceListNavigationTemplate if the listener has a value.

When the driver clicks the refresh button, the onContentRefreshRequested method of your OnContentRefreshListener implementation is called. Within onContentRefreshRequested, call the Screen.invalidate method. The host subsequently calls back into your app’s Screen.onGetTemplate method to retrieve the template with the refreshed content. See Refresh the contents of a template for more information about refreshing templates. As long as the next template returned by onGetTemplate is of the same type, it is counted as a refresh and is not counted towards the template quota.

Voice guidance

To play navigation guidance over the car speakers your app must request audio focus. As a part of your AudioFocusRequest you should set the usage as AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE. You should also set the focus gain as AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

Simulating navigation

In order to verify your app's navigation functionality when submitting it to the Google Play Store, your app must implement the NavigationManagerCallback.onAutoDriveEnabled callback. When this callback is called, your app should simulate navigation to the chosen destination when the user begins navigation. Your app can exit this mode whenever the current Session's lifecycle reaches the Lifecycle.Event.ON_DESTROY state.

You can test that your implementation of onAutoDriveEnabled is called by executing the following from a command line:

adb shell dumpsys activity service CAR_APP_SERVICE_NAME AUTO_DRIVE

For example:

adb shell dumpsys activity service androidx.car.app.samples.navigation.car.NavigationCarAppService AUTO_DRIVE

Default navigation car app

In Android Auto, the default navigation car app corresponds to the last navigation app that the user launched. This is the app that, for example, will receive navigation intents when the user invokes navigation commands through the assistant or when another app sends an intent to start navigation.

Allow users to interact with your map

You can add support for users to interact with maps, for example, zooming and panning of maps, allowing users to see different parts of a map. Each template has a different minimum Car App API level requirement. See the table below for the minimum level for the template that you want to implement.

TemplateInteractivity supported since Car App API Level
NavigationTemplate2
PlaceListNavigationTemplate4
RoutePreviewNavigationTemplate4
MapTemplate5

SurfaceCallback methods

The SurfaceCallback has several callback methods that enable you to add map interactivity to your maps built with the NavigationTemplate, PlaceListNavigationTemplate, RoutePreviewNavigationTemplate, or MapTemplate templates: onClick, onScroll, onScale, and onFling. See the table below for how these callbacks relate to user interactions.

Interaction SurfaceCallback method Supported since Car App API level
Tap onClick 5
Pinch (zoom) onScale 2
Single-touch drag onScroll 2
Single-touch fling onFling 2
Double-tap onScale (with a scale factor determined by the template host) 2
Rotary nudge in Pan mode onScroll (with a distance factor determined by the template host) 2

Map action strip

The NavigationTemplate, PlaceListNavigationTemplate, RoutePreviewNavigationTemplate, and MapTemplate templates can have a map action strip for map-related actions such as zoom in and out, recenter, display a compass, or any other actions your app may choose to display. The map action strip can have up to four icon-only buttons that can be refreshed without impacting task depth. Similar to the Action Strip, the map action strip will hide during idle state and reappear on active state.

To receive map interactivity callbacks, you must add an Action.PAN button in the map action strip. If your app omits the Action.PAN button in the map action strip, you will not receive user input from the SurfaceCallback methods and the host will exit any previously activated pan mode. When the user presses the pan button, the host enters pan mode. On a touchscreen, the pan button will not be displayed.

Pan mode

In pan mode, the template host translates user input from non-touch input devices, such as rotary controllers and touchpads, into the appropriate SurfaceCallback methods. Respond to the user action to enter or exit pan mode with the setPanModeListener method in the NavigationTemplate Builder. The host may hide other UI components in the template while the user is in pan mode.

Stable area

The stable area is updated between idle and active state. Your app should draw driving-related information such as speed, speed limit, or road warnings depending on the size of the stable area so that important information on the map doesn’t get occluded by the map action strip.

In-context navigation alerts

Alert displays important information to the driver, with optional actions, without leaving the context of the navigation screen. To provide the best experience to the driver, Alert works within the NavigationTemplate to avoid blocking the navigation route and to minimize driver distraction.

Alert is only available within the NavigationTemplate. To notify a user outside of the NavigationTemplate, consider using a heads-up notification (HUN) as explained in Display notifications.

For example, use Alert to:

  • Inform the driver of an update relevant to the current navigation, such as a change in traffic conditions.
  • Ask the driver for an update related to the current navigation, such as the existence of a speed trap.
  • Propose an upcoming task and ask if the driver is going to accept or not, such as if the driver is willing to pick up someone on their way.

In its basic form, an Alert consists of a title and the Alert duration time. The duration time is represented by a progress bar. Optionally, you can add a subtitle, an icon, and up to two Actions.

Figure 1. In-context navigation alert

Once an Alert is shown, it does not carry over to another template if a driver interaction results in leaving the NavigationTemplate. It stays in the original NavigationTemplate until Alert times out, the user takes an action, or the app dismisses the Alert.

Configure alert duration

Choose an Alert duration that matches your app’s needs. The recommended duration for a navigation Alert is 10 seconds. Refer to the Android for Cars Design Guidelines for guidelines.

Create an alert

Use the Alert.Builder to create an Alert instance.

Kotlin

Alert.Builder(
        /*alertId*/ 1,
        /*title*/ CarText.create("Hello"),
        /*durationMillis*/ 5000
    )
    // The fields below are optional
    .addAction(firstAction)
    .addAction(secondAction)
    .setSubtitle(CarText.create(...))
    .setIcon(CarIcon.APP_ICON)
    .setCallback(...)
    .build()

Java

new Alert.Builder(
        /*alertId*/ 1,
        /*title*/ CarText.create("Hello"),
        /*durationMillis*/ 5000
    )
    // The fields below are optional
    .addAction(firstAction)
    .addAction(secondAction)
    .setSubtitle(CarText.create(...))
    .setIcon(CarIcon.APP_ICON)
    .setCallback(...)
    .build();

If you want to listen for Alert cancellation or dismissal, create an implementation of the AlertCallback interface. The AlertCallback call paths are:

Show an alert

To show an Alert, call the AppManager.showAlert method available through your app’s CarContext.

// Show an alert
carContext.getCarService(AppManager.class).showAlert(alert)

Dismiss an alert

While Alerts automatically dismiss due to timeout or driver interaction, you can also manually dismiss an Alert. For example, if you want to dismiss an Alert because its information is now stale. To dismiss an Alert call the dismissAlert method with the alertId of the Alert.

// Dismiss the same alert
carContext.getCarService(AppManager.class).dismissAlert(alert.getId())

Calling dismissAlert with an alertId that doesn't match the currently displayed Alert (if there is one) does nothing. (It doesn't throw an exception).