Usa la biblioteca de apps de Android para vehículos

La Biblioteca de apps de Android para vehículos te permite usar tus apps de navegación, lugares de interés e Internet de las cosas (IoT) en el vehículo. A tal efecto, proporciona un conjunto de plantillas diseñadas para cumplir con los estándares de distracción del conductor y tener en cuenta los detalles, como la variedad de factores de pantalla del vehículo y las modalidades de entrada.

En esta guía, se proporciona una descripción general de las funciones y los conceptos clave de la biblioteca, y se te guiará en el proceso de configuración de una app simple. Para obtener una introducción paso a paso, consulta el codelab Aprende los conceptos básicos de la Biblioteca de apps para vehículos.

Antes de comenzar

  1. Consulta las páginas de Diseño para la conducción que abordan la Biblioteca de apps para vehículos
  2. Revisa los términos y conceptos clave que se indican en la sección siguiente.
  3. Familiarízate con la IU del sistema de Android Auto y el diseño del SO Android Automotive.
  4. Revisa las Notas de versión.
  5. Revisa las Muestras.

Términos y conceptos clave

Modelos y plantillas
La interfaz de usuario se representa con un gráfico de objetos modelo que pueden organizarse de manera diferente según lo que permita la plantilla a la que pertenecen. Las plantillas son un subconjunto de los modelos que pueden actuar como una raíz en esos gráficos. Los modelos incluyen la información que se le mostrará al usuario, en forma de imágenes y texto, así como atributos para configurar aspectos de la apariencia visual de esa información (por ejemplo, colores de texto o tamaños de imagen). El host convierte los modelos en vistas que están diseñadas para cumplir con los estándares de distracción del conductor y se encarga de detalles como la variedad de modalidades de entrada y la de los factores de pantalla de los vehículos.
Host
El host es el componente del backend que implementa la funcionalidad que ofrecen las APIs de la biblioteca para que tu app se ejecute en el vehículo. Las responsabilidades del host van desde descubrir tu app y administrar su ciclo de vida hasta convertir tus modelos en vistas y notificar a tu app de las interacciones del usuario. En dispositivos móviles, Android Auto implementa este host. En el SO Android Automotive, este host se instala como una app del sistema.
Restricciones de plantillas
Varias plantillas aplican restricciones en el contenido de sus modelos. Por ejemplo, las plantillas de listas tienen una cantidad limitada de elementos que se pueden presentar al usuario. Las plantillas también tienen restricciones en relación con la forma en que se pueden conectar para formar el flujo de una tarea. Por ejemplo, la app solo puede enviar hasta cinco plantillas a la pila de pantallas. Obtén más detalles en Restricciones de las plantillas.
Screen
Screen es una clase proporcionada por la biblioteca que las apps implementan a los efectos de administrar la interfaz de usuario que se presenta a este último. Una Screen tiene un ciclo de vida y proporciona el mecanismo necesario de modo que la app envíe la plantilla que se mostrará cuando la pantalla sea visible. Las instancias de Screen también se pueden insertar en una pila de Screen y quitarse de ella, lo que garantiza que cumplan con las restricciones del flujo de plantillas.
CarAppService
CarAppService es una clase abstracta de Service que tu app debe implementar y exportar para que el host la descubra y la administre. El CarAppService de tu app es responsable de validar que se pueda confiar en una conexión de host usando createHostValidator y, luego, proporcionando instancias de Session a cada conexión con onCreateSession.
Session

Session es una clase abstracta que tu app debe implementar y mostrar usando CarAppService.onCreateSession. Funciona como punto de entrada para mostrar información en la pantalla del vehículo. Tiene un ciclo de vida que informa el estado actual de tu app en la pantalla del vehículo, por ejemplo, si la app está visible u oculta.

Cuando se inicia una Session (por ejemplo, cuando se inicia la app por primera vez), el host solicita la Screen inicial que se mostrará usando el método onCreateScreen.

Cómo instalar la Biblioteca de apps para vehículos

Si deseas obtener instrucciones para agregar la biblioteca a tu app, visita la página de versiones de la biblioteca de Jetpack.

Cómo configurar los archivos del manifiesto de tu app

Para poder crear tu app para vehículos, configura sus archivos de manifiesto como se indica a continuación.

Declara tu CarAppService

El host se conecta a tu app a través de tu implementación de CarAppService. Debes declarar este servicio en el manifiesto para permitir que el host descubra tu app y se conecte a ella.

También debes declarar la categoría de tu app en el elemento <category> del filtro de intents de la app. Si quieres conocer los valores permitidos para este elemento, consulta la lista de categorías de apps compatibles.

En el siguiente fragmento de código, se muestra cómo declarar un servicio de app para vehículos para una app de lugares de interés en tu manifiesto:

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

    ...
<application>

Categorías de apps compatibles

Para declarar la categoría de tu app, agrega uno o más de los siguientes valores de categoría en el filtro de intents cuando declares tu CarAppService como se describe en la sección anterior:

Consulta Calidad de las apps para Android para vehículos si deseas obtener descripciones detalladas de cada categoría y los criterios que cumplen las apps para pertenecer a ellas.

Especifica el nombre y el ícono de la app

Debes especificar un ícono y un nombre de app que pueda usar el host para representar tu app en la IU del sistema.

Puedes especificar el nombre de la app y el ícono que representarán a tu app con los atributos label y icon de tu CarAppService:

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

Si no se declaran la etiqueta ni el ícono en el elemento <service>, el host recurrirá a los valores especificados para el elemento <application>.

Cómo establecer un tema predeterminado

Para establecer un tema predeterminado para tu app para vehículos, agrega un elemento <meta-data> en tu archivo de manifiesto, de la siguiente manera:

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Luego, declara tu recurso de estilo para configurar los siguientes atributos para el tema de tu app para vehículos:

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Nivel de API de la app para vehículos

La Biblioteca de apps para vehículos define sus propios niveles de API, para que puedas saber qué funciones de la biblioteca son compatibles con el host de la plantilla en cada vehículo. Para recuperar el nivel máximo de API de la app para vehículos admitido por un host, usa el método getCarAppApiLevel().

En el archivo AndroidManifest.xml, declara el nivel mínimo de API de la app para vehículos compatible con tu app.

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

Si deseas obtener detalles para mantener la retrocompatibilidad y declarar el nivel mínimo de API necesario para una función, consulta la documentación de la anotación de RequiresCarApi. Si quieres obtener una definición del nivel de API que se requiere para usar una función específica de la Biblioteca de apps para vehículos, consulta la documentación de referencia de CarAppApiLevels.

Cómo crear tu CarAppService y Session

La app necesita extender la clase CarAppService e implementar el método onCreateSession, lo que muestra una instancia de Session correspondiente a la conexión actual al host:

Kotlin

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Java

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

La instancia de Session es responsable de mostrar la instancia de Screen que se usará la primera vez que se inicie la app:

Kotlin

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Java

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

Para manejar situaciones en las que la app para vehículos deba iniciarse desde una pantalla que no sea la de inicio o la de destino de tu app (como el manejo de vínculos directos), puedes completar previamente una pila de actividades de pantallas usando ScreenManager.push antes de mostrarlas desde onCreateScreen. Esta acción previa permite a los usuarios volver a las pantallas anteriores desde la primera pantalla que muestre tu app.

Cómo crear tu pantalla de inicio

Para crear las pantallas que muestra tu app, define las clases que extienden la clase Screen e implementa el método onGetTemplate, que muestra la instancia de la Template que representa el estado de la IU que se mostrará en la pantalla del vehículo.

En el siguiente fragmento, se muestra cómo declarar una Screen que usa una plantilla PaneTemplate para mostrar una cadena simple de "Hello World!":

Kotlin

class HelloWorldScreen(carContext: CarContext) : Screen(carContext) {
    override fun onGetTemplate(): Template {
        val row = Row.Builder().setTitle("Hello world!").build()
        val pane = Pane.Builder().addRow(row).build()
        return PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build()
    }
}

Java

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

La clase CarContext

La clase CarContext es una subclase ContextWrapper accesible para tus instancias Session y Screen. Proporciona acceso a servicios para vehículos, como ScreenManager para administrar la pila de pantallas; AppManager para funcionalidades generales relacionadas con la app, como acceder al objeto Surface para dibujar el mapa de tu app de navegación; y el NavigationManager que usan las apps de navegación paso a paso para comunicar los metadatos del host de navegación{/15.

Si quieres obtener una lista completa de las funciones de la biblioteca disponibles para las apps de navegación, consulta Accede a las plantillas de navegación.

CarContext también ofrece otras funciones, como permitir que cargues recursos de elementos de diseño usando la configuración de la pantalla del vehículo, iniciar una app en el vehículo por medio de intents e indicar si tu app de navegación debe mostrar su mapa en modo oscuro.

Cómo implementar la navegación en pantalla

A menudo, las apps presentan diferentes pantallas, cada cual con plantillas posiblemente distintas, que el usuario puede navegar, ya que interactúan con la interfaz que se muestra en la pantalla.

La clase ScreenManager proporciona una pila de pantallas que puedes usar para enviar pantallas que se puedan mostrar automáticamente cuando el usuario selecciona un botón Atrás en la pantalla del vehículo o usa el botón Atrás de hardware disponible en algunos automóviles.

En el siguiente fragmento, se muestra cómo agregar una acción de regreso a la plantilla de mensajes y una que envíe una pantalla nueva cuando el usuario la seleccione:

Kotlin

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Java

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

El objeto Action.BACK es una Action estándar que invoca automáticamente ScreenManager.pop. Este comportamiento se puede anular con la instancia OnBackPressedDispatcher disponible en CarContext.

Para garantizar que la app sea segura de usar durante la conducción, la pila de pantallas puede tener un máximo de cinco pantallas. Consulta la sección de Restricciones de plantillas para obtener más detalles.

Cómo actualizar el contenido de una plantilla

Tu app puede solicitar que se invalide el contenido de una Screen a través de una llamada 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 nuevo.

Cuando se actualiza una Screen, es importante comprender el contenido específico de la plantilla que se puede actualizar para que el host no descuente la plantilla nueva de la cuota de plantillas. Consulta la sección de Restricciones de plantillas para obtener más detalles.

Te recomendamos que estructures tus pantallas de modo que haya una asignación uno a uno entre una Screen y el tipo de plantilla que muestra a través de su implementación de onGetTemplate.

Cómo interactuar con el usuario

Tu app puede interactuar con el usuario a través de patrones similares a los de tu app para dispositivos móviles.

Cómo controlar las entradas del usuario

Tu app puede responder a las entradas del usuario pasando los objetos de escucha adecuados a los modelos compatibles. En el siguiente fragmento, se muestra cómo crear un modelo de Action que establece un OnClickListener que realiza una devolución de llamada a un método definido por el código de tu app:

Kotlin

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Java

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

Luego, el método onClickNavigate podrá iniciar la app predeterminada de navegación para vehículos con el método CarContext.startCarApp:

Kotlin

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Java

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

Si deseas obtener más información para iniciar apps, incluido el formato del intent ACTION_NAVIGATE, consulta la sección Cómo iniciar una app para vehículos con un intent.

Algunas acciones, como las que requieren que se dirija al usuario para continuar la interacción en sus dispositivos móviles, solo se permiten cuando el automóvil está estacionado. Puedes usar el elemento ParkedOnlyOnClickListener para implementar esas acciones. Si el vehículo no está estacionado, el host muestra una indicación al usuario de que no se permite la acción en ese caso. Si el vehículo está estacionado, el código se ejecuta con normalidad. En el siguiente fragmento, se muestra cómo usar el ParkedOnlyOnClickListener para abrir una pantalla de configuración en el dispositivo móvil:

Kotlin

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Java

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

Visualización de notificaciones

Las notificaciones enviadas al dispositivo móvil solo se muestran en la pantalla del vehículo si se extienden con un CarAppExtender. Algunos atributos de notificación, como el título, el texto, el ícono y las acciones, se pueden configurar en el CarAppExtender, el cual anulará esos atributos cuando aparezcan en la pantalla del vehículo.

En el siguiente fragmento, se muestra cómo enviar una notificación a la pantalla del vehículo que muestra un título diferente al que se muestra en el dispositivo móvil:

Kotlin

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Java

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

Las notificaciones pueden afectar las siguientes partes de la interfaz de usuario:

  • Es posible que se muestre una notificación emergente (HUN, por su sigla en inglés) al usuario.
  • Se puede agregar una entrada en el centro de notificaciones, opcionalmente con un distintivo visible en el riel.
  • En el caso de las apps de navegación, la notificación puede mostrarse en el widget de riel como se describe en Notificaciones paso a paso.

Puedes elegir la configuración de las notificaciones de tu app de modo que afecten cada uno de los elementos de la interfaz usando la prioridad de la notificación, como se describe en la documentación de CarAppExtender.

Si se llama a NotificationCompat.Builder.setOnlyAlertOnce con un valor true, se muestra una única notificación de prioridad alta como una notificación de atención.

Si deseas obtener más información sobre el diseño de las notificaciones de tu app para vehículos, consulta la guía de Google Design para conducción, en la sección sobre las notificaciones.

Cómo mostrar avisos

Tu app puede mostrar un aviso a través de CarToast, como se ve en este fragmento:

Kotlin

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Java

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

Solicita permisos

Si la app debe acceder a datos o acciones restringidos (por ejemplo, a la ubicación), se aplican las reglas estándar de permisos de Android. A fin de solicitar un permiso, puedes usar el método CarContext.requestPermissions().

El beneficio de usar CarContext.requestPermissions(), en lugar de usar las APIs estándar de Android, es que no necesitas iniciar tu propia Activity para crear el diálogo de permisos. Además, podrás usar el mismo código en Android Auto y en el SO Android Automotive, en lugar de tener que crear flujos que dependen de la plataforma.

Cómo cambiar el estilo del diálogo de permisos en Android Auto

En Android Auto, se mostrará el diálogo de permisos al usuario en el teléfono. De forma predeterminada, no habrá fondo detrás del diálogo. Para establecer un fondo personalizado, declara un tema para apps para vehículos en tu archivo AndroidManifest.xml y configura el atributo carPermissionActivityLayout para el tema de tu app.

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Luego, configura el atributo carPermissionActivityLayout para el tema de tu app para vehículos:

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Cómo iniciar una app para vehículos con un intent

Puedes llamar al método CarContext.startCarApp para realizar una de las siguientes acciones:

  • Abrir el marcador a los efectos de realizar una llamada
  • Iniciar la navegación paso a paso a una ubicación con la app de navegación predeterminada
  • Iniciar tu propia app con un intent

En el siguiente ejemplo, se muestra cómo crear una notificación por medio de una acción que abre tu app en una pantalla que muestra los detalles de una reserva de estacionamiento. Extiende la instancia de notificación con un intent de contenido que tenga un PendingIntent uniendo un intent explícito a la acción de tu app:

Kotlin

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Java

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

La app también debe declarar un BroadcastReceiver que se invoque para procesar el intent cuando el usuario seleccione la acción en la interfaz de notificación y, luego, invoque CarContext.startCarApp con un intent que incluya el URI de datos:

Kotlin

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Java

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

Por último, el método Session.onNewIntent de tu app controlará este intent mostrando la pantalla de reserva de estacionamiento de la pila, si es que no está en la parte superior:

Kotlin

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Java

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

Consulta la sección Visualización de notificaciones para obtener más información sobre el control de notificaciones de la app para vehículos.

Restricciones de plantillas

El host limita la cantidad de plantillas que se muestran para una tarea determinada a un máximo de cinco, de las cuales la última debe ser de uno de los siguientes tipos:

Ten en cuenta que este límite se aplica a la cantidad de plantillas y no a la de instancias de Screen de la pila. Por ejemplo, si una app envía dos plantillas mientras muestra la pantalla A y, luego, envía la pantalla B, podrá enviar tres plantillas más. De manera alternativa, si se estructura cada pantalla para enviar una sola plantilla, la app podrá enviar cinco instancias de pantalla a la pila ScreenManager.

Existen casos especiales en función de estas restricciones: actualizaciones de plantillas y operaciones de retroceso y restablecimiento.

Actualizaciones de plantillas

Algunas actualizaciones de contenido no se toman en cuenta para el límite de plantillas. En general, si una app envía una plantilla nueva que sea del mismo tipo y contenga el mismo contenido principal que la plantilla anterior, la nueva no se descontará de la cuota. Por ejemplo, actualizar el estado de activación de una fila en una ListTemplate no descuenta de la cuota. Si quieres obtener más información sobre los tipos de modificaciones de contenido que pueden considerarse una actualización, consulta la documentación de las plantillas individuales.

Operaciones de retroceso

Para habilitar subflujos dentro de una tarea, el host detecta el envío por parte de la app de una Screen de la pila de ScreenManager y actualiza la cuota restante según la cantidad de plantillas que la app está retrocediendo.

Por ejemplo, si la app envía dos plantillas mientras muestra la pantalla A y, luego, envía la pantalla B y dos plantillas adicionales, tendrá una cuota remanente. Si luego la app regresa a la pantalla A, el host restablecerá la cuota a tres, porque la app retrocedió dos plantillas.

Ten en cuenta que, cuando vuelvas a una pantalla, la app deberá enviar una plantilla del mismo tipo que la que la pantalla envió por última vez. Si envía cualquier otro tipo de plantilla, se mostrará un error. Sin embargo, siempre que el tipo se mantenga igual durante una operación de retroceso, la app podrá modificar libremente el contenido de la plantilla sin afectar la cuota.

Operaciones de restablecimiento

Algunas plantillas tienen una semántica especial que indica el final de una tarea. Por ejemplo, la NavigationTemplate es una vista que se espera que permanezca en pantalla y se actualice con instrucciones nuevas paso a paso para el consumo del usuario. Cuando alcance una de estas plantillas, el host restablecerá la cuota de plantillas y tratará esa plantilla como si fuera el primer paso de una tarea nueva. Esto permitirá que la app comience una tarea nueva. Consulta la documentación de las plantillas individuales para ver cuáles activan un restablecimiento en el host.

Si el host recibe un intent para iniciar la app desde una acción de notificación o desde el selector, la cuota también se restablecerá. Este mecanismo permite que una app inicie un nuevo flujo de tareas a partir de notificaciones, y se aplica incluso si una app ya está vinculada y en primer plano.

Consulta la sección Visualización de notificaciones para obtener más detalles para mostrar las notificaciones de tu app en la pantalla del vehículo. Consulta la sección Cómo iniciar una app para vehículos con un intent si deseas obtener información para iniciar tu app desde una acción de notificación.

API de Connection

Puedes determinar si tu app está ejecutándose en Android Auto o en el SO Android Automotive usando la API de CarConnection para recuperar la información de conexión durante el tiempo de ejecución.

Por ejemplo, en la Session de tu app, inicializa una CarConnection y suscríbete a las actualizaciones de LiveData:

Kotlin

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Java

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

En el observador, puedes reaccionar a los cambios en el estado de conexión:

Kotlin

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Java

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

API de Constraints

Los vehículos pueden admitir distintas cantidades de instancias de Item que se mostrarán al usuario en un determinado momento. Usa el ConstraintManager para verificar el límite de contenido en el tiempo de ejecución y establece la cantidad apropiada de elementos en tus plantillas.

Comienza por obtener un ConstraintManager de CarContext:

Kotlin

val manager = carContext.getCarService(ConstraintManager::class.java)

Java

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

Luego, puedes consultar el objeto recuperado ConstraintManager para el límite de contenido relevante. Por ejemplo, para obtener la cantidad de elementos que pueden mostrarse en una cuadrícula, llama a getContentLimit con CONTENT_LIMIT_TYPE_GRID:

Kotlin

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Java

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

Cómo agregar un flujo de acceso

Si tu app ofrece una experiencia de acceso a los usuarios, puedes usar plantillas como SignInTemplate y LongMessageTemplate con el nivel de API 2 y posteriores de la app para vehículos para administrar el acceso a tu app en la consola central del vehículo.

Para crear una SignInTemplate, define un SignInMethod. Por el momento, la Biblioteca de apps para vehículos admite los siguientes métodos de acceso:

  • InputSignInMethod para el acceso con nombre de usuario y contraseña
  • PinSignInMethod para el acceso con código PIN, en el que el usuario vincula su cuenta desde el teléfono con un PIN que aparece en la consola central
  • ProviderSignInMethod para el acceso de proveedor, como el Acceso con Google y One Tap.
  • QRCodeSignInMethod para el acceso con código QR, en el que el usuario escanea un código QR para completar el acceso en su teléfono (esto está disponible con el nivel 4 de la API de Car y posteriores)

Por ejemplo, para implementar una plantilla que recopile la contraseña del usuario, primero debes crear un InputCallback que procese y valide la entrada del usuario:

Kotlin

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Java

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

Se requiere un InputCallback para el Builder de InputSignInMethod.

Kotlin

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Java

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

Por último, usa tu InputSignInMethod nuevo para crear un SignInTemplate.

Kotlin

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Java

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

Usa AccountManager

Las apps del SO Android Automotive que tienen autenticación deberán usar AccountManager por los siguientes motivos:

  • Mejor UX y administración sencilla de la cuenta: Los usuarios podrán administrar fácilmente todas sus cuentas desde el menú de cuentas en la configuración del sistema, lo que incluye el acceso y el cierre de sesión.
  • Experiencias de "invitado": Como los automóviles son dispositivos compartidos, los OEM pueden habilitar las experiencias de "invitado" en el vehículo, cuando no se puedan agregar cuentas.

Cómo agregar variantes de cadenas de texto

Es posible que se muestre una cantidad de texto diferente en distintos tamaños de pantallas de vehículos. Con la app para vehículos en el nivel de API 2 y versiones posteriores, puedes especificar múltiples variantes de una string de texto que se ajusten mejor a la pantalla. Para ver dónde se admiten variantes de texto, busca plantillas y componentes que acepten un CarText.

Puedes agregar variantes de cadena de texto a un CarText con el método CarText.Builder.addVariant() de la siguiente manera:

Kotlin

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Java

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

De esta manera, puedes usar este CarText, por ejemplo, como texto principal de un GridItem.

Kotlin

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Java

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

Agrega las cadenas en orden de preferencia, por ejemplo, de la más larga a la más corta. El host elegirá la cadena de longitud que corresponda en función de la cantidad de espacio disponible en la pantalla del vehículo.

Cómo agregar CarIcons intercalados para filas

Puedes agregar íconos intercalados con texto para enriquecer el atractivo visual de tu app con CarIconSpan. Consulta la documentación de CarIconSpan.create si deseas obtener más información para crear estos intervalos. Consulta Cómo usar intervalos para dar un estilo increíble al texto para ver una descripción general del funcionamiento de los estilos de texto con intervalos.

Kotlin

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Java

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

APIs de Car Hardware

A partir del nivel de API 3 de la app para vehículos, la Biblioteca de apps para vehículos tiene APIs que puedes usar para acceder a propiedades y sensores del vehículo.

Requisitos

Para usar las APIs con Android Auto, comienza agregando una dependencia en androidx.car.app:app-projected en el archivo build.gradle para tu módulo de Android Auto. Para el SO Android Automotive, agrega una dependencia en androidx.car.app:app-automotive en el archivo build.gradle para tu módulo del SO Android Automotive.

Además, en tu archivo AndroidManifest.xml, debes declarar los permisos relevantes y necesarios para requerir los datos del vehículo que quieres usar. Ten en cuenta que el usuario debe otorgarte estos permisos también. Podrás usar el mismo código en Android Auto y en el SO Android Automotive, en lugar de tener que crear flujos que dependen de la plataforma. Sin embargo, los permisos necesarios son diferentes.

CarInfo

En esta tabla, se describen las propiedades que mostrarán las APIs de CarInfo APIs y los permisos que debes solicitar para usarlas:

Métodos Propiedades Permisos de Android Auto Permisos del SO Android Automotive
fetchModel Marca, modelo, año android.car.permission.CAR_INFO
fetchEnergyProfile Tipos de conectores de VE, tipos de combustible com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO
addTollListener
removeTollListener
Estado de tarjeta de peaje, tipo de tarjeta de peaje
addEnergyLevelListener
removeEnergyLevelListener
Nivel de batería, nivel de combustible, nivel de combustible bajo, rango remanente com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY,
android.car.permission.CAR_ENERGY_PORTS,
android.car.permission.READ_CAR_DISPLAY_UNITS
addSpeedListener
removeSpeedListener
Velocidad sin procesar, velocidad en pantalla (se muestra en la pantalla de clúster del vehículo) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED,
android.car.permission.READ_CAR_DISPLAY_UNITS
addMileageListener
removeMileageListener
Distancia del odómetro com.google.android.gms.permission.CAR_MILEAGE Estos datos no están disponibles en el SO Android Automotive OS para apps instaladas desde Play Store.

Por ejemplo, para obtener el rango remanente, crea una instancia del objeto CarInfo y, luego, crea y registra un OnCarDataAvailableListener:

Kotlin

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)
…
// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Java

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);
…
// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

No asumas que los datos del vehículo estarán disponibles en todo momento. Si recibes un error, verifica el estado del valor que solicitaste para entender mejor por qué no se pudieron recuperar los datos requeridos. Consulta la documentación de referencia para obtener la definición completa de la clase CarInfo.

CarSensors

La clase CarSensors te brinda acceso al acelerómetro, al giroscopio, a la brújula y a los datos de ubicación del vehículo. La disponibilidad de estos valores podría depender del OEM. El formato de los datos del acelerómetro, del giroscopio y de la brújula es igual al que obtendrías de la API de SensorManager. Por ejemplo, para verificar la orientación del vehículo, haz lo siguiente:

Kotlin

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)
…
// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Java

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);
…
// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

Para acceder a los datos de ubicación del vehículo, también debes declarar y requerir el permiso android.permission.ACCESS_FINE_LOCATION.

Prueba

Para simular los datos de sensores cuando realizas pruebas en Android Auto, consulta las secciones Sensores y Configuración de sensores de la guía de la consola central de escritorio. Para simular los datos de sensores cuando realizas pruebas en el SO Android Automotive, consulta la sección Cómo emular el estado del hardware de la guía del emulador del SO Android Automotive.

Los ciclos de vida de CarAppService, Session y Screen

Las clases Session y Screen implementan la interfaz LifecycleOwner. A medida que el usuario interactúe con la app, se invocarán las devoluciones de llamada de ciclo de vida de tus objetos Session y Screen, como se describe en los siguientes diagramas.

Ciclos de vida de un CarAppService y una Session

Figura 1: El ciclo de vida de Session.

Si deseas obtener información detallada, consulta la documentación del método Session.getLifecycle.

Ciclo de vida de una Screen

Figura 2: El ciclo de vida de Screen.

Si deseas obtener información detallada, consulta la documentación del método Screen.getLifecycle.

Cómo grabar con el micrófono del vehículo

Usando el elemento CarAppService de tu app y la API de CarAudioRecord, puedes otorgarle a la app el acceso al micrófono del vehículo del usuario. Los usuarios deben otorgar a la app el permiso para acceder al micrófono del vehículo. Tu app puede grabar y procesar la entrada del usuario dentro de tu app.

Permiso de grabación

Antes de grabar audio, debes declarar el permiso de grabación en tu archivo AndroidManifest.xml y requerir al usuario que lo otorgue.

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

Debes solicitar el permiso de grabación en el tiempo de ejecución. Consulta la sección Solicita permisos si deseas obtener información detallada para solicitar un permiso en tu app para vehículos.

Grabación de audio

Luego de que el usuario otorgue el permiso para grabar, podrás grabar el audio y procesar la grabación.

Kotlin

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

Foco de audio

Para grabar con el micrófono del vehículo, primero debes obtener el foco de audio para asegurarte de que se detenga todo contenido multimedia en reproducción. Si pierdes el foco de audio, deja de grabar.

Este es un ejemplo de la forma de obtener el foco de audio:

Kotlin

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

Biblioteca de pruebas

La biblioteca de pruebas de Android para vehículos brinda clases auxiliares que puedes usar para validar el comportamiento de tus apps en un entorno de pruebas. Por ejemplo, el elemento SessionController te permite simular una conexión al host, y verificar que se crean y se muestran los objetos Screen y Template correctos.

Consulta las Muestras para ver ejemplos de uso.

Cómo informar un problema en la Biblioteca de apps de Android para vehículos

Si encuentras un problema en la biblioteca, infórmalo a través de la herramienta de seguimiento de errores de Google. Asegúrate de completar toda la información solicitada en la plantilla de problemas.

Crear un error nuevo

Antes de informar un error nuevo, revisa si aparece en las notas de la versión de la biblioteca o en la lista de errores. Para suscribirte a un problema o votarlo, haz clic en el ícono de estrella que aparece en la herramienta de seguimiento. Si deseas obtener más información, consulta Cómo suscribirte a un problema.