Cómo compilar una app de navegación

En esta página, se detallan las diferentes funciones de la biblioteca de apps para vehículos que puedes usar para implementar la funcionalidad de tu app de navegación paso a paso.

Declara la compatibilidad con la navegación en tu manifiesto

Tu app de navegación debe declarar la categoría de app para vehículos androidx.car.app.category.NAVIGATION en el filtro de intents de su 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>

Admite intents de navegación

Para admitir intents de navegación en tu app, incluidos aquellos que provienen de Asistente de Google cuando se realiza una consulta por voz, tu app debe controlar el intent CarContext.ACTION_NAVIGATE en su Session.onCreateScreen y Session.onNewIntent.

Si deseas obtener detalles sobre el formato del intent, consulta la documentación de CarContext.startCarApp.

Accede a las plantillas de navegación

Las apps de navegación pueden acceder a las siguientes plantillas diseñadas específicamente para ellas. Todas estas plantillas muestran una plataforma en segundo plano con el mapa y, durante la navegación activa, instrucciones paso a paso.

  • NavigationTemplate: También muestra un mensaje informativo opcional y estimaciones de viaje durante la navegación activa.
  • MapTemplate: Presenta una versión compacta de una lista (como en ListTemplate) o de un panel (información detallada con acciones destacadas, como en el objeto PaneTemplate) junto a un mapa.
  • PlaceListNavigationTemplate: También muestra una lista de lugares, que pueden tener los marcadores correspondientes dibujados en el mapa.
  • RoutePreviewNavigationTemplate: Muestra una lista de rutas que se pueden seleccionar y destacar en el mapa.

Si quieres obtener más información para diseñar la interfaz de usuario de tu app de navegación con esas plantillas, consulta el documento sobre apps de navegación.

Para obtener acceso a las plantillas de navegación, la app debe declarar el permiso androidx.car.app.NAVIGATION_TEMPLATES en su archivo AndroidManifest.xml:

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

Dibuja el mapa

Las apps de navegación pueden acceder a una Surface para dibujar el mapa en plantillas relevantes.

Se puede acceder a un objeto SurfaceContainer si configuras una instancia de SurfaceCallback en el servicio para vehículos de AppManager:

Kotlin

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

Java

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

La SurfaceCallback proporciona una devolución de llamada cuando el SurfaceContainer está disponible junto con otras devoluciones de llamada al momento en que se cambian las propiedades de la Surface.

Para obtener acceso a la plataforma, tu app debe declarar el permiso androidx.car.app.ACCESS_SURFACE en su archivo AndroidManifest.xml:

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

El área visible del mapa

El host puede dibujar elementos de la interfaz de usuario para las plantillas en la parte superior del mapa. El host comunica el área que no tiene obstrucciones y es completamente visible para el usuario cuando se llama al método SurfaceCallback.onVisibleAreaChanged. Además, para minimizar la cantidad de cambios, el host llama al método SurfaceCallback.onStableAreaChanged con el rectángulo más pequeño, que siempre está visible en función de la plantilla actual.

Por ejemplo, cuando una app de navegación usa la NavigationTemplate con una barra de acción en la parte superior, esa barra podrá ocultarse cuando el usuario no haya interactuado con la pantalla durante un tiempo para dejar más espacio para el mapa. En este caso, hay una devolución de llamada a onStableAreaChanged y onVisibleAreaChanged con el mismo rectángulo. Cuando la barra de acciones está oculta, solo se llama a onVisibleAreaChanged con el área más grande. En caso de que el usuario interactúe con la pantalla, solo se llamará a onVisibleAreaChanged con el primer rectángulo.

Admite el modo oscuro

Las apps de navegación deben volver a dibujar su mapa en la instancia de Surface con los colores oscuros apropiados cuando el host determine que las condiciones lo requieren, como se describe en Calidad de las apps para Android para vehículos.

Para decidir si quieres dibujar un mapa oscuro, puedes usar el método CarContext.isDarkMode. Cuando cambie el estado del modo oscuro, recibirás una llamada a Session.onCarConfigurationChanged.

Las apps de navegación deben comunicar metadatos de navegación adicionales al host. El host usa esa información con el fin de proporcionarla a la consola central del vehículo y evitar conflictos de las aplicaciones de navegación en relación con los recursos compartidos.

Los metadatos de navegación se proporcionan en el servicio para vehículos de NavigationManager, al que se puede acceder desde el CarContext:

Kotlin

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

Java

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

Inicia, finaliza y detén la navegación

Para que el host administre varias apps de navegación, notificaciones de rutas y datos del clúster del vehículo, debe conocer el estado actual de la navegación. Cuando un usuario inicia la navegación, la app debe llamar a NavigationManager.navigationStarted. Del mismo modo, cuando finaliza la navegación (por ejemplo, cuando el usuario llega a su destino o cancela la navegación), la app debe llamar a NavigationManager.navigationEnded.

Solo llama a NavigationManager.navigationEnded cuando el usuario termine de usar la navegación. En cambio, si necesitas volver a calcular la ruta en medio de un viaje, usa Trip.Builder.setLoading(true).

En ocasiones, el host necesitará una app para detener la navegación y llamará a onStopNavigation en un objeto NavigationManagerCallback proporcionado por tu app a través de NavigationManager.setNavigationManagerCallback. En este caso, la app deberá dejar de emitir la información de próximo paso en la pantalla del clúster, las notificaciones de navegación y las indicaciones por voz.

Actualiza la información del viaje

Durante la navegación activa, llama a NavigationManager.updateTrip. La información que se brinde en esta llamada se usará en las pantallas del clúster y de aviso del vehículo. Según el vehículo que se conduzca, no se muestra toda la información al usuario. Por ejemplo, la consola central de computadora (DHU) muestra el Step agregado a Trip, pero no muestra la información de Destination.

Dibuja en la pantalla del clúster

Para proporcionar una experiencia del usuario lo más envolvente posible, te recomendamos ir más allá de mostrar los metadatos básicos en la pantalla del clúster del vehículo. A partir del nivel de API 6 de la app para vehículos, las apps de navegación tienen la opción de procesar su propio contenido directamente en la pantalla del clúster (en vehículos compatibles), con las siguientes limitaciones:

  • La API de la pantalla del clúster no es compatible con los controles de entrada.
  • La pantalla del clúster solo debe mostrar mosaicos de mapas. De manera opcional, se puede mostrar una navegación de ruta activa en estos mosaicos.
  • La API de la pantalla del clúster solo admite el uso de NavigationTemplate.
    • A diferencia de las pantallas principales, es posible que las pantallas del clúster no muestren de manera coherente todos los elementos de la IU de NavigationTemplate, como las instrucciones paso a paso, las tarjetas de hora de llegada y las acciones. Las tarjetas de mapas son el único elemento de la IU que se muestra de manera coherente.

Declara la compatibilidad del clúster

Para permitir que la aplicación de host sepa que tu app admite la renderización en pantallas de clústeres, debes agregar un elemento <category> de androidx.car.app.category.FEATURE_CLUSTER a tu <intent-filter> de CarAppService, como se muestra en el siguiente fragmento:

<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"/>
        <category android:name="androidx.car.app.category.FEATURE_CLUSTER"/>
      </intent-filter>
    </service>
    ...
</application>

Administración del ciclo de vida y el estado

A partir del nivel de API 6, el flujo del ciclo de vida de la app para vehículos se mantiene igual, pero ahora CarAppService::onCreateSession toma un parámetro de tipo SessionInfo que brinda información adicional sobre la Session que se crea (como el tipo de pantalla y el conjunto de plantillas compatibles).

Las apps tienen la opción de usar la misma clase de Session para controlar el clúster y la pantalla principal, o crear Sessions específicas de la pantalla para personalizar el comportamiento en cada pantalla (como se muestra en el siguiente fragmento).

Kotlin

override fun onCreateSession(sessionInfo: SessionInfo): Session {
  return if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) {
    ClusterSession()
  } else {
    MainDisplaySession()
  }
}

Java

@Override
@NonNull
public Session onCreateSession(@NonNull SessionInfo sessionInfo) {
  if (sessionInfo.getDisplayType() == SessionInfo.DISPLAY_TYPE_CLUSTER) {
    return new ClusterSession();
  } else {
    return new MainDisplaySession();
  }
}

No hay garantías sobre cuándo o si se proporciona la pantalla del clúster, y también es posible que la Session del clúster sea la única Session (por ejemplo, si el usuario cambió la pantalla principal por otra app mientras está navegando activamente por tu app). El acuerdo "estándar" es que la app obtiene el control de la visualización del clúster solo después de que se haya llamado a NavigationManager::navigationStarted. Sin embargo, es posible que la app obtenga la pantalla del clúster mientras no haya una navegación activa, o bien que nunca se le proporcione la pantalla del clúster. Depende de tu app manejar estas situaciones renderizando el estado inactivo de los mosaicos de mapas de tu app.

El host crea instancias independientes de Binder y CarContext por Session. Esto significa que, cuando se usan métodos como ScreenManager::push o Screen::invalidate, solo se ve afectada la Session desde la que se los llama. Las apps deben crear sus propios canales de comunicación entre estas instancias si se necesita la comunicación entre Session (por ejemplo, con transmisiones, un singleton compartido, entre otros).

Prueba la compatibilidad con clústeres

Puedes probar tu implementación en Android Auto y el SO Android Automotive. Para Android Auto, esto se hace configurando la consola central de la computadora para emular una pantalla del clúster secundaria. En el SO Android Automotive, las imágenes genéricas del sistema del nivel de API 30 y versiones posteriores emulan la pantalla de un clúster.

Personaliza TravelEstimate con texto o un ícono

Para personalizar la estimación de viaje con texto, un ícono o ambos, usa los métodos setTripIcon o setTripText de la clase TravelEstimate.Builder. NavigationTemplate usa TravelEstimate para establecer de forma opcional el texto y los íconos junto a la hora estimada de llegada, el tiempo restante y distancia restante, o en lugar de ellos.

Figura 1. Duración estimada del viaje con ícono y texto personalizados

En el siguiente fragmento, se usan setTripIcon y setTripText para personalizar la estimación de viaje:

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();

Proporciona notificaciones paso a paso

Brinda instrucciones de navegación paso a paso (TBT) a través de una notificación de navegación actualizada con frecuencia. Para que se la considere una notificación de navegación en la pantalla del vehículo, el compilador de la notificación deberá hacer lo siguiente:

  1. Marcar la notificación como en curso con el método NotificationCompat.Builder.setOngoing
  2. Establecer la categoría de la notificación en Notification.CATEGORY_NAVIGATION
  3. Extender la notificación con un CarAppExtender

Se mostrará una notificación de navegación en el widget de riel, en la parte inferior de la pantalla del vehículo. Si el nivel de importancia de la notificación se establece en IMPORTANCE_HIGH, también se mostrará como una notificación de atención (HUN). Si no se establece la importancia con el método CarAppExtender.Builder.setImportance, se usará la importancia del canal de notificaciones.

La app puede establecer un PendingIntent en el CarAppExtender que se le enviará cuando el usuario presione la HUN o el widget de riel.

Si se llama a NotificationCompat.Builder.setOnlyAlertOnce con un valor true, la HUN tendrá una única alerta de notificación de importancia alta.

En el siguiente fragmento, se muestra cómo compilar una notificación de navegación:

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();

Actualiza la notificación TBT con frecuencia sobre los cambios de distancia, lo que actualizará el widget de riel y solo mostrará la notificación como una HUN. Puedes controlar el comportamiento de la HUN si configuras la importancia de la notificación con CarAppExtender.Builder.setImportance. Cuando se establece la importancia en IMPORTANCE_HIGH, se muestra una HUN. Si se establece en cualquier otro valor, solo se actualiza el widget de riel.

Actualiza el contenido de PlaceListNavigationTemplate

Puedes permitir que los conductores actualicen el contenido con solo presionar un botón mientras exploran listas de lugares compiladas con PlaceListNavigationTemplate. Para habilitar la actualización de la lista, implementa el método onContentRefreshRequested de la interfaz OnContentRefreshListener y usa PlaceListNavigationTemplate.Builder.setOnContentRefreshListener si buscas configurar el objeto de escucha en la plantilla.

En el siguiente fragmento, se muestra cómo configurar el objeto de escucha en la plantilla:

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();

El botón de actualización solo se muestra en el encabezado de PlaceListNavigationTemplate si el objeto de escucha tiene un valor.

Cuando el conductor hace clic en el botón de actualización, se llama al método onContentRefreshRequested de la implementación de OnContentRefreshListener. En onContentRefreshRequested, llama al método Screen.invalidate. Luego, el host volverá a llamar al método Screen.onGetTemplate de tu app para recuperar la plantilla con el contenido actualizado. Consulta Cómo actualizar el contenido de una plantilla para obtener más información. Siempre que la siguiente plantilla que muestra onGetTemplate sea del mismo tipo, se contará como una actualización y no se sumará a la cuota de la plantilla.

Proporciona indicaciones de audio

Para reproducir las indicaciones de navegación en las bocinas del vehículo, tu app deberá solicitar un foco de audio. Como parte de tu AudioFocusRequest, establece el uso en AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE. Además, establece la ganancia del foco en AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

Simula la navegación

Para verificar la funcionalidad de navegación de tu app cuando la envías a Google Play Store, tu app debe implementar la devolución de llamada NavigationManagerCallback.onAutoDriveEnabled. Cuando se realice esta devolución de llamada, la app deberá simular la navegación al destino seleccionado cuando el usuario inicie la navegación. Tu app puede salir de este modo cada vez que el ciclo de vida de la Session actual alcanza el estado Lifecycle.Event.ON_DESTROY.

Puedes probar si se llama a tu implementación de onAutoDriveEnabled si ejecutas lo siguiente desde una línea de comandos:

adb shell dumpsys activity service CAR_APP_SERVICE_NAME AUTO_DRIVE

Esto se muestra en el siguiente ejemplo:

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

App de vehículo de navegación predeterminada

En Android Auto, la app de vehículo de navegación predeterminada corresponde a la última app de navegación que inició el usuario. La app predeterminada recibe intents de navegación cuando el usuario invoca comandos de navegación con Asistente o cuando otra app envía un intent para iniciar la navegación.

Permite que los usuarios interactúen con tu mapa

Puedes agregar compatibilidad para que los usuarios interactúen con los mapas, por ejemplo, permitiéndoles ver diferentes partes de este con el uso del zoom y el desplazamiento lateral. Cada plantilla tiene un requisito diferente de nivel de API mínimo de la app para vehículos. Consulta la siguiente tabla para ver el nivel mínimo de la plantilla que deseas implementar.

PlantillaCompatible con la interactividad desde el nivel de API de la app para vehículos
NavigationTemplate2
PlaceListNavigationTemplate4
RoutePreviewNavigationTemplate4
MapTemplate5

Métodos SurfaceCallback

La interfaz de SurfaceCallback tiene varios métodos de devolución de llamada que te permiten agregar interactividad a los mapas compilados con las plantillas de NavigationTemplate, PlaceListNavigationTemplate, RoutePreviewNavigationTemplate o MapTemplate: onClick, onScroll, onScale y onFling. Consulta la siguiente tabla para descubrir cómo se relacionan estas devoluciones de llamada con las interacciones del usuario.

Interacción Método SurfaceCallback Compatible desde el nivel de API de la app para vehículos
Presionar onClick 5
Pellizcar para acercar onScale 2
Arrastrar con un toque onScroll 2
Deslizar con un toque onFling 2
Presionar dos veces onScale (con factor de escala determinado por el host de la plantilla) 2
Rotar ligeramente en modo de desplazamiento lateral onScroll (con factor de distancia determinado por el host de la plantilla) 2

Barra de acciones en mapa

Las plantillas de NavigationTemplate, PlaceListNavigationTemplate, RoutePreviewNavigationTemplate y MapTemplate pueden incluir una barra de acciones en mapa, como acercar y alejar la imagen, volver a centrar, mostrar una brújula y otras acciones que decidas mostrar. La barra de acciones en mapa puede tener hasta cuatro botones de ícono que pueden actualizarse sin afectar la profundidad de la tarea. Se oculta durante el estado inactivo y vuelve a aparecer durante el estado activo.

Para obtener devoluciones de llamada de interactividad con mapas, debes agregar un botón Action.PAN en la barra de acciones en mapa. Cuando el usuario presiona el botón de desplazamiento lateral, el host ingresa al modo de desplazamiento, como se describe en la siguiente sección.

Si tu app omite el botón Action.PAN en la barra de acciones en mapa, no recibirás entradas del usuario de los métodos SurfaceCallback, por lo que el host saldrá del modo de desplazamiento lateral que se haya activado antes.

En una pantalla táctil, no se mostrará el botón de desplazamiento lateral.

Modo de desplazamiento lateral

En el modo de desplazamiento lateral, el host de la plantilla convierte la entrada del usuario proveniente de dispositivos de entrada no táctiles, como controladores rotativos y paneles táctiles, en los métodos SurfaceCallback correspondientes. Puedes responder a la acción del usuario de ingresar o salir del modo de desplazamiento lateral con el método setPanModeListener en el NavigationTemplate.Builder. El host puede ocultar otros componentes de la IU en la plantilla mientras el usuario se encuentra en el modo de desplazamiento lateral.

Área estable

El área estable se actualiza entre los estados inactivo y activo. Determina si quieres dibujar información relacionada con la conducción, como la velocidad, el límite de velocidad o las advertencias sobre la ruta, según el tamaño del área estable, de modo que la barra de acciones del mapa no obstruya la información importante en el mapa.

Muestra alertas de navegación en contexto

Alert muestra información importante al conductor, con acciones opcionales, sin salir del contexto de la pantalla de navegación. Para brindar la mejor experiencia al conductor, Alert funciona dentro de NavigationTemplate lo que evita que se bloquee la ruta de navegación y minimiza la distracción del conductor.

Alert solo está disponible en NavigationTemplate. Para notificar a un usuario fuera de NavigationTemplate, considera usar una notificación de atención (HUN), como se explica en Visualización de notificaciones.

Por ejemplo, usa Alert para lo siguiente:

  • Informar al conductor sobre una actualización relevante para la navegación actual, como un cambio en las condiciones de tráfico
  • Pedirle al conductor una actualización relacionada con la navegación actual, como la existencia de un radar de velocidad
  • Proponer una próxima tarea y preguntar al conductor si la aceptará; por ejemplo, si está dispuesto a recoger a alguien en el camino

En su forma básica, una Alert consta de un título y el tiempo de duración de Alert. La duración se representa con una barra de progreso. De manera opcional, puedes agregar un subtítulo, un ícono y hasta dos elementos Action.

Figura 2. Alerta de navegación en contexto

Una vez que se muestra una Alert, esta no transfiere a otra plantilla si, como resultado de la interacción con un conductor, se abandona la NavigationTemplate. Permanece en la NavigationTemplate original hasta que se agote el tiempo de espera de Alert, el usuario realice una acción o la app la descarte la Alert.

Crea una alerta

Usa Alert.Builder para crear una instancia Alert:

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();

Si deseas escuchar la cancelación o el descarte de Alert, crea una implementación de la interfaz de AlertCallback. Las rutas de llamadas de AlertCallback son las siguientes:

Configura la duración de las alertas

Elige una duración de Alert que coincida con las necesidades de tu app. La duración recomendada para una Alert de navegación es de 10 segundos. Consulta Alertas de navegación para obtener más información.

Muestra una alerta

Para mostrar una Alert, llama al método AppManager.showAlert disponible desde el elemento CarContext de tu app.

// Show an alert
carContext.getCarService(AppManager.class).showAlert(alert)
  • Llamar a showAlert con un Alert que tiene un alertId igual al ID del Alert que se muestra actualmente no realiza ninguna acción. La Alert no se actualiza. Para actualizar una Alert, debes volver a crearla con un alertId nuevo.
  • Si llamas a showAlert con una Alert que tiene un alertId diferente a la Alert que aparece en ese momento, se descarta la Alert que se muestra.

Descarta una alerta

Si bien una Alert se descarta automáticamente debido al tiempo de espera o a la interacción del conductor, también puedes descartar una Alert de forma manual, por ejemplo, si su información se vuelve obsoleta. Para descartar una Alert, llama al método dismissAlert con el alertId de la Alert.

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

Llamar a dismissAlert con un alertId que no coincide con la Alert que se muestra actualmente no tiene ningún efecto. No arroja una excepción.