Cómo compilar una app de navegación

En esta sección, se detallan las diferentes funciones de la biblioteca que puedes usar a fin de implementar la funcionalidad de tu app de navegación paso a paso.

Declara la compatibilidad de 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

A los efectos de admitir intents de navegación en tu app, incluidos aquellos que provienen de Asistente de Google mediante 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 superficie en segundo plano a la que tu app puede acceder con el fin de dibujar el mapa, junto con otra información proporcionada por la app que varía según la plantilla.

  • NavigationTemplate: Muestra el mapa junto con un mensaje informativo opcional o las instrucciones sobre cómo llegar y las estimaciones de viaje durante la navegación activa.
  • PlaceListNavigationTemplate: Muestra una lista de lugares que pueden tener marcadores correspondientes en el mapa.
  • RoutePreviewNavigationTemplate: Muestra una lista de rutas que se pueden seleccionar y destacar en el mapa.

Obtén más información para diseñar la interfaz de usuario de tu app de navegación con esas plantillas. Consulta los lineamientos de diseño de la biblioteca de la app de Android para vehículos.

A los efectos de obtener acceso a las plantillas de navegación, tu app debe declarar el permiso androidx.car.app.NAVIGATION_TEMPLATES en su AndroidManifest.xml:

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

Cómo dibujar el mapa

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

Se puede acceder a un objeto SurfaceContainer si se configura una instancia de SurfaceCallback al 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 cuando cambian las propiedades de la Surface.

Para obtener acceso a la superficie, tu app debe declarar el permiso androidx.car.app.ACCESS_SURFACE en su 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 diferentes plantillas ubicadas en la parte superior del mapa. El host comunicará el área que no está oculta y resulta completamente visible para el usuario mediante una llamada al método SurfaceCallback.onVisibleAreaChanged. Además, a fin de minimizar la cantidad de cambios, el host también llamará al método SurfaceCallback.onStableAreaChanged con el rectángulo más pequeño, que siempre estará 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 a fin de dejar más espacio para el mapa. En este caso, habrá una devolución de llamada a onStableAreaChanged y onVisibleAreaChanged con el mismo rectángulo. Cuando la barra de acciones esté oculta, solo se llamará 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.

Modo oscuro

Las aplicaciones de navegación deben rediseñar 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 los lineamientos de calidad de las apps para Android Auto.

A los efectos de decidir si debes 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 aplicaciones 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 a través del servicio para automóviles 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);

Cómo iniciar, finalizar y detener la navegación

A los efectos de 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 debes llamar 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 stopNavigation en un objeto NavigationManagerListener proporcionado por tu app a través de NavigationManager.setListener. 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.

Información del viaje

Durante la navegación activa, la app deberá llamar 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 en cuestión, es posible que no se muestre toda la información al usuario. Por ejemplo, la consola central de escritorio muestra el Step agregado a Trip, pero no muestra la información de Destination.

A fin de probar que la información llega al clúster, se puede configurar la herramienta de consola central de escritorio (DHU) de modo que muestre una pantalla de clúster simple. Crea un archivo cluster.ini con el siguiente contenido:

[general]
instrumentcluster = true

Luego, puedes invocar la DHU con un parámetro de línea de comandos adicional:

dhu -c cluster.ini

Notificaciones paso a paso

Se pueden proporcionar instrucciones de navegación paso a paso (TBT) por medio 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 mediante 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 emergente (HUN). Si no se establece la importancia mediante 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();

Lineamientos para las notificaciones paso a paso

Las apps de navegación deben actualizar 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. Para controlar el comportamiento de las HUN, las apps establecen la importancia de la notificación mediante el método CarAppExtender.Builder.setImportance. Establecer la importancia en IMPORTANCE_HIGH mostrará una HUN. Si lo estableces en cualquier otro valor, solo se actualizará el widget de riel.

Indicaciones por voz

A fin de reproducir las indicaciones de navegación en las bocinas del vehículo, tu app deberá solicitar foco de audio. Como parte de tu AudioFocusRequest, deberás establecer el uso en AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE. También deberás establecer la ganancia del foco en AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

Cómo simular la navegación

Para verificar la funcionalidad de navegación de tu app cuando la envíes a Google Play Store, esta deberá 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 podrá salir de este modo cuando el ciclo de vida de la Session actual alcance el estado Lifecycle.Event.ON_DESTROY.

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

adb shell dumpsys activity service CAR_APP_SERVICE_NAME AUTO_DRIVE

Por 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. Esta es la app que, por ejemplo, recibirá intents de navegación cuando el usuario invoque comandos de navegación mediante Asistente o cuando otra app envíe un intent para iniciar la navegación.

Cómo permitir que los usuarios interactúen con tu mapa

Con la app para vehículos en el nivel de API 2 y versiones posteriores, puedes agregar zoom o desplazamiento lateral a tu mapa en el NavigationTemplate a fin de que los usuarios puedan ver diferentes partes.

Métodos SurfaceCallback

La SurfaceCallback tiene tres métodos de devolución de llamada que te permiten agregar interactividad con el mapa a tu NavigationTemplate: onScale, onScroll 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
Pellizco (zoom) onScale
Arrastre con un toque onScroll
Deslizamiento con un toque onFling
Presionar dos veces onScale (con un factor de escala determinado por el host de la plantilla)
Movimiento rotativo en modo de desplazamiento lateral onScroll (con un factor de distancia determinado por el host de la plantilla)

Barra de acciones del mapa

El NavigationTemplate puede tener una barra de acciones relacionadas con el mapa, como acercar y alejar la imagen, volver a centrar, mostrar una brújula o cualquier otra acción que la app decida mostrar. La barra de acciones del mapa puede tener hasta cuatro botones de ícono que pueden actualizarse sin afectar la profundidad de la tarea. Al igual que la barra de acción, la barra de acciones del mapa se oculta durante el estado de inactividad y vuelve a aparecer cuando se activa.

Para obtener devoluciones de llamada de interactividad con mapas, debes agregar un botón Action.PAN en la barra de acciones del mapa. Si tu app omite el botón Action.PAN en la barra de acciones del 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. Cuando el usuario presiona el botón de desplazamiento lateral, el host ingresa en el modo de desplazamiento lateral. 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 mediante el método setPanModeListener en el Builder de NavigationTemplate. 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. Tu app debe dibujar información de conducción, como la velocidad, el límite de velocidad o alguna advertencia sobre el tránsito, según el tamaño del área estable, de manera que la barra de acciones del mapa no cubra la información importante.