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, que 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.MapWithContentTemplate
: Una plantilla que permite que una app renderice tarjetas de mapa con algún tipo de contenido (por ejemplo, una lista). Por lo general, el contenido se renderiza como una superposición sobre las tarjetas del mapa, con las áreas visibles y estables del mapa que se ajustan al contenido.
Si quieres obtener más información para diseñar la interfaz de usuario de tu app de navegación con estas 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
:
<manifest ...>
...
<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES"/>
...
</manifest>
Se requiere un permiso adicional para dibujar mapas.
Cómo migrar a MapWithContentTemplate
A partir del nivel de API 7 de la app para vehículos, MapTemplate
, PlaceListNavigationTemplate
y RoutePreviewNavigationTemplate
dejan de estar disponibles. Se seguirá admitiendo el uso de plantillas obsoletas, pero se recomienda migrar a MapWithContentTemplate
.
La funcionalidad que proporcionan estas plantillas se puede implementar con MapWithContentTemplate
. Consulta los siguientes fragmentos para ver ejemplos:
MapTemplate
Kotlin
// MapTemplate (deprecated) val template = MapTemplate.Builder() .setPane(paneBuilder.build()) .setActionStrip(actionStrip) .setHeader(header) .setMapController(mapController) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( PaneTemplate.Builder(paneBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(mapController) .build()
Java
// MapTemplate (deprecated) MapTemplate template = new MapTemplate.Builder() .setPane(paneBuilder.build()) .setActionStrip(actionStrip) .setHeader(header) .setMapController(mapController) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new PaneTemplate.Builder(paneBuilder.build()) .setHeader(header) build()) .setActionStrip(actionStrip) .setMapController(mapController) .build();
PlaceListNavigationTemplate
Kotlin
// PlaceListNavigationTemplate (deprecated) val template = PlaceListNavigationTemplate.Builder() .setItemList(itemListBuilder.build()) .setHeader(header) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( ListTemplate.Builder() .setSingleList(itemListBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController( MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build()
Java
// PlaceListNavigationTemplate (deprecated) PlaceListNavigationTemplate template = new PlaceListNavigationTemplate.Builder() .setItemList(itemListBuilder.build()) .setHeader(header) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new ListTemplate.Builder() .setSingleList(itemListBuilder.build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(new MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build();
RoutePreviewNavigationTemplate
Kotlin
// RoutePreviewNavigationTemplate (deprecated) val template = RoutePreviewNavigationTemplate.Builder() .setItemList( ItemList.Builder() .addItem( Row.Builder() .setTitle(title) .build()) .build()) .setHeader(header) .setNavigateAction( Action.Builder() .setTitle(actionTitle) .setOnClickListener { ... } .build()) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build() // MapWithContentTemplate val template = MapWithContentTemplate.Builder() .setContentTemplate( ListTemplate.Builder() .setSingleList( ItemList.Builder() .addItem( Row.Builder() .setTitle(title) .addAction( Action.Builder() .setTitle(actionTitle) .setOnClickListener { ... } .build()) .build()) .build()) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController( MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build()
Java
// RoutePreviewNavigationTemplate (deprecated) RoutePreviewNavigationTemplate template = new RoutePreviewNavigationTemplate.Builder() .setItemList(new ItemList.Builder() .addItem(new Row.Builder() .setTitle(title)) .build()) .build()) .setHeader(header) .setNavigateAction(new Action.Builder() .setTitle(actionTitle) .setOnClickListener(() -> { ... }) .build()) .setActionStrip(actionStrip) .setMapActionStrip(mapActionStrip) .build(); // MapWithContentTemplate MapWithContentTemplate template = new MapWithContentTemplate.Builder() .setContentTemplate(new ListTemplate.Builder() .setSingleList(new ItemList.Builder() .addItem(new Row.Builder() .setTitle(title)) .addAction(new Action.Builder() .setTitle(actionTitle) .setOnClickListener(() -> { ... }) .build()) .build()) .build())) .setHeader(header) .build()) .setActionStrip(actionStrip) .setMapController(new MapController.Builder() .setMapActionStrip(mapActionStrip) .build()) .build();
Comunica los metadatos de navegación
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.
- Lineamiento de calidad de las apps para vehículos
NF-9
: La pantalla del clúster solo debe mostrar mosaicos de mapas. De manera opcional, se puede mostrar una ruta de navegación 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.
- 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
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.
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:
- Marcar la notificación como en curso con el método
NotificationCompat.Builder.setOngoing
- Establecer la categoría de la notificación en
Notification.CATEGORY_NAVIGATION
- 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.
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
.
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:
Si se agota el tiempo de espera de
Alert
, el host llama al métodoAlertCallback.onCancel
con el valorAlertCallback.REASON_TIMEOUT
. Luego, llama al métodoAlertCallback.onDismiss
.Si el conductor hace clic en uno de los botones de acción, el host llama a
Action.OnClickListener
y, luego, aAlertCallback.onDismiss
.Si no se admite la
Alert
, el host llama aAlertCallback.onCancel
con el valorAlertCallback.REASON_NOT_SUPPORTED
. El host no llama aAlertCallback.onDismiss
porque no se mostró laAlert
.
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 la sección de 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 unAlert
que tiene unalertId
igual al ID delAlert
que se muestra actualmente no realiza ninguna acción. LaAlert
no se actualiza. Para actualizar unaAlert
, debes volver a crearla con unalertId
nuevo. - Si llamas a
showAlert
con unaAlert
que tiene unalertId
diferente a laAlert
que aparece en ese momento, se descarta laAlert
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.