Cómo compilar apps multimedia para vehículos

Android Auto y el SO Android Automotive te permiten ofrecerles a los usuarios el contenido de tu app de música en sus vehículos. Una app de música para vehículos debe proporcionar un servicio de navegador multimedia para que Android Auto y el SO Android Automotive (u otra aplicación con un navegador multimedia) puedan descubrir y mostrar tu contenido.

En esta guía, se da por sentado que ya tienes una app de música que reproduce audio en un teléfono y que cumple con la arquitectura de apps de música de Android.

Además, se describen los componentes necesarios de los elementos MediaBrowserService y MediaSession que tu app necesita a fin de que funcione en Android Auto o el SO Android Automotive. Después de que completes la infraestructura de medios principal, podrás agregar compatibilidad con Android Auto y con el SO Android Automotive a tu app de música.

Antes de comenzar

  1. Revisa la documentación de la API de medios de Android.
  2. Revisa los lineamientos de diseño para apps del SO Android Automotive y los lineamientos de diseño para apps de Android Auto.
  3. Revisa los términos y conceptos clave que se indican en esta sección.

Términos y conceptos clave

Servicio de navegador multimedia
Es un servicio de Android que implementa tu app de música y que cumple con la API de MediaBrowserServiceCompat. Tu app usa este servicio para exponer su contenido.
Navegador multimedia
Es una API que usan las apps de música para descubrir servicios de navegador multimedia y mostrar su contenido. Android Auto y el SO Android Automotive usan un navegador multimedia a fin de encontrar el servicio de ese tipo de navegador para tu app.
Elemento multimedia

El navegador multimedia organiza su contenido en un árbol de objetos MediaItem. Un elemento multimedia puede tener una de estas marcas o ambas:

  • Reproducible: Esta marca indica que el elemento es una hoja en el árbol de contenido. El elemento representa una sola transmisión de sonido, como una canción de un álbum, un capítulo de un audiolibro o un episodio de un podcast.
  • Explorable: Esta marca indica que el elemento es un nodo en el árbol de contenido y que tiene elementos secundarios. Por ejemplo, el elemento representa un álbum y sus elementos secundarios son las canciones del álbum.

Un elemento multimedia reproducible y explorable funciona como una lista de reproducción. Puedes seleccionar el elemento para que se reproduzcan todos sus elementos secundarios, o bien puedes navegar por ellos.

Actividades optimizadas para vehículos

Se trata de las actividades de una app del SO Android Automotive que cumplen con los lineamientos de diseño para este SO. La interfaz de estas actividades no está desarrollada por el SO Android Automotive, por lo que debes asegurarte de que tu app cumpla con los lineamientos de diseño. Por lo general, se incluyen objetivos táctiles y tamaños de fuente más grandes, compatibilidad con los modos diurno y nocturno, y relaciones de contraste más altas.

Las interfaces de usuario optimizadas para vehículos solo se muestran si las restricciones de la experiencia del usuario de vehículo (CUXR) no están vigentes, ya que estas interfaces requieren atención extensa o interacción por parte del usuario. Las CUXR no se aplican si el vehículo está detenido o estacionado, pero siempre se aplican cuando está en movimiento.

No es necesario diseñar actividades para Android Auto porque este usa su propia interfaz optimizada para vehículos con la información de tu servicio de navegador multimedia.

Cómo configurar los archivos del manifiesto de tu app

Para poder crear tu servicio de navegador multimedia, debes configurar los archivos de manifiesto de tu app.

Declara tu servicio de navegador multimedia

Tanto Android Auto como el SO Android Automotive se conectan a tu app mediante tu servicio de navegador multimedia para explorar elementos de ese tipo. Debes declarar tu servicio de navegador multimedia en el manifiesto a fin de que Android Auto y el SO Android Automotive descubran el servicio y se conecten a tu app.

En el siguiente fragmento de código, se muestra cómo declarar el servicio de navegador multimedia en el manifiesto. Debes incluir este código en el archivo de manifiesto del módulo del SO Android Automotive y en el archivo de manifiesto de la app de tu teléfono.

<application>
    ...
    <service android:name=".MyMediaBrowserService"
             android:exported="true">
        <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService"/>
        </intent-filter>
    </service>
    ...
</application>

Especifica un ícono de app

Debes especificar un ícono de la app que Android Auto y el SO Android Automotive puedan usar para representar tu app en la IU del sistema.

Puedes especificar el ícono que se usa para representar a tu app con la siguiente declaración de manifiesto:

<!--The android:icon attribute is used by Android Automotive OS-->
<application>
    ...
    android:icon="@mipmap/ic_launcher">
    ...
    <!--Used by Android Auto-->
    <meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
               android:resource="@drawable/ic_auto_icon" />
    ...
</application>

Crea tu servicio de navegador multimedia

Puedes extender la clase MediaBrowserServiceCompat para crear un servicio de navegador multimedia. Android Auto y el SO Android Automotive pueden usar tu servicio a los efectos de hacer lo siguiente:

  • Explorar la jerarquía del contenido de tu app con el fin de mostrar un menú al usuario
  • Obtener el token para el objeto MediaSessionCompat de tu app con el fin de controlar la reproducción de audio

Flujo de trabajo del servicio de navegador multimedia

En esta sección, se describe cómo el SO Android Automotive y Android Auto interactúan con tu servicio de navegadores multimedia durante un flujo de trabajo típico de los usuarios.

  1. Un usuario inicia tu app en el SO Android Automotive o en Android Auto.
  2. El SO Android Automotive o Android Auto se comunican con el servicio de navegador multimedia de tu app mediante el método onCreate(). En la implementación del método onCreate(), debes crear y registrar un objeto MediaSessionCompat y su objeto de devolución de llamada.
  3. El SO Android Automotive o Android Auto llaman al método onGetRoot() del servicio para obtener el elemento multimedia raíz en la jerarquía de contenido. El elemento multimedia raíz no se muestra, sino que se usa para recuperar más contenido de la app.
  4. El SO Android Automotive o Android Auto llaman al método onLoadChildren() del servicio para obtener los elementos secundarios del elemento multimedia raíz. El SO Android Automotive y Android Auto muestran estos elementos multimedia como el nivel superior de los elementos de contenido. Consulta Estructura del menú raíz para obtener más información sobre lo que el sistema espera en este nivel.
  5. Si el usuario selecciona un elemento multimedia explorable, se llama de nuevo al método onLoadChildren() del servicio para recuperar los elementos secundarios del elemento de menú seleccionado.
  6. Si el usuario selecciona un elemento multimedia reproducible, el SO Android Automotive o Android Auto llaman al método de devolución de llamada de la sesión multimedia correspondiente para llevar a cabo esa acción.
  7. Si la app lo permite, el usuario también puede hacer búsquedas en el contenido. En este caso, el SO Android Automotive o Android Auto llaman al método onSearch() del servicio.

Compila tu jerarquía de contenido

El SO Android Automotive o Android Auto llaman al servicio de navegador multimedia de tu app para averiguar qué contenido está disponible. Para ello, debes implementar dos métodos en tu servicio de navegador multimedia: onGetRoot() y onLoadChildren().

Implementa onGetRoot

El método onGetRoot() de tu servicio muestra información sobre el nodo raíz de tu jerarquía de contenido. El SO Android Automotive y Android Auto usan este nodo raíz para solicitar el resto del contenido mediante el método onLoadChildren().

En el siguiente fragmento de código, se muestra una implementación simple del método onGetRoot():

Kotlin

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? =
    // Verify that the specified package is allowed to access your
    // content! You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        null
    } else MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)

Java

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {

    // Verify that the specified package is allowed to access your
    // content! You'll need to write your own logic to do this.
    if (!isValid(clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return null.
        // No further calls will be made to other media browsing methods.

        return null;
    }

    return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null);
}

Para obtener un ejemplo detallado de este método, consulta el método onGetRoot() en la app de ejemplo de Universal Android Music Player en GitHub.

Agrega validación de paquetes para onGetRoot()

Cuando se realiza una llamada al método onGetRoot() de tu servicio, el paquete de llamadas pasa información de identificación a tu servicio. El servicio puede usar esta información para decidir si el paquete podrá acceder al contenido. Por ejemplo, puedes restringir el acceso al contenido de tu app a una lista de paquetes aprobados si comparas clientPackageName con tu lista de entidades permitidas y verificas el certificado usado para firmar el APK del paquete. Si no se puede verificar el paquete, muestra null a los efectos de denegar el acceso a tu contenido.

Con el fin de proporcionarles a las apps del sistema (como el SO Android Automotive y Android Auto) acceso al contenido, el servicio debe mostrar siempre un valor de BrowserRoot que no sea nulo cuando estas apps del sistema llamen al método onGetRoot(). La firma de la app del SO Android Automotive puede variar según la marca y el modelo del vehículo, por lo que debes permitir que las conexiones de todas las apps del sistema sean compatibles con ese sistema operativo de forma sólida.

En el siguiente fragmento de código, se muestra cómo el servicio puede validar que el paquete de llamada es una app del sistema:

fun isKnownCaller(
    callingPackage: String,
    callingUid: Int
): Boolean {
    ...
    val isCallerKnown = when {
       // If the system is making the call, allow it.
       callingUid == Process.SYSTEM_UID -> true
       // If the app was signed by the same certificate as the platform
       // itself, also allow it.
       callerSignature == platformSignature -> true
       // ... more cases
    }
    return isCallerKnown
}

Este fragmento de código es un extracto de la clase PackageValidator en la app de ejemplo de Universal Android Music Player en GitHub. Consulta esa clase para obtener un ejemplo más detallado de cómo implementar la validación de paquetes para el método onGetRoot() de tu servicio.

Además de permitir las apps del sistema, debes permitir que Asistente de Google se conecte a tu MediaBrowserService. Ten en cuenta que Asistente de Google tiene nombres de paquete separados para el teléfono (que incluye Android Auto) y el SO Android Automotive.

Implementa onLoadChildren()

Después de recibir el objeto del nodo raíz, el SO Android Automotive y Android Auto compilan un menú de nivel superior llamando a onLoadChildren() en el objeto de nodo raíz para obtener sus elementos secundarios. Las apps cliente compilan submenús llamando al mismo método con los objetos del nodo secundario.

Cada nodo de tu jerarquía de contenido está representado por un objeto MediaBrowserCompat.MediaItem. Cada uno de estos elementos multimedia se identifica mediante una string de ID único. Las apps cliente tratan estas strings de ID como tokens opacos. Si una app cliente quiere examinar un submenú o reproducir un elemento multimedia, pasa el token. Tu app es responsable de asociar el token con el elemento multimedia correspondiente.

Nota: Android Auto y el SO Android Automotive tienen límites estrictos sobre la cantidad de elementos multimedia que pueden mostrar en cada nivel del menú. Esos límites minimizan las distracciones de los conductores y ayudan a operar la app mediante comandos por voz. Para obtener más información, consulta Explora detalles de contenido y Explora vistas.

En el siguiente fragmento de código, se muestra una implementación simple del método onLoadChildren():

Kotlin

override fun onLoadChildren(
    parentMediaId: String,
    result: Result<List<MediaBrowserCompat.MediaItem>>
) {
    // Assume for example that the music catalog is already loaded/cached.

    val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()

    // Check if this is the root menu:
    if (MY_MEDIA_ROOT_ID == parentMediaId) {

        // build the MediaItem objects for the top level,
        // and put them in the mediaItems list
    } else {

        // examine the passed parentMediaId to see which submenu we're at,
        // and put the children of that menu in the mediaItems list
    }
    result.sendResult(mediaItems)
}

Java

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaBrowserCompat.MediaItem>> result) {

    // Assume for example that the music catalog is already loaded/cached.

    List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();

    // Check if this is the root menu:
    if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {

        // build the MediaItem objects for the top level,
        // and put them in the mediaItems list
    } else {

        // examine the passed parentMediaId to see which submenu we're at,
        // and put the children of that menu in the mediaItems list
    }
    result.sendResult(mediaItems);
}

Para obtener un ejemplo completo de este método, consulta el método onLoadChildren() en la app de ejemplo de Universal Android Music Player en GitHub.

Estructura el menú raíz

Figura 1: El contenido raíz se muestra como pestañas de navegación

Android Auto y el SO Android Automotive tienen restricciones específicas sobre la estructura del menú raíz. Se comunican con MediaBrowserService a través de sugerencias de raíz, que se pueden leer por medio del argumento Bundle que se pasa a onGetRoot(). Si sigues estas sugerencias, el sistema mostrará el contenido raíz de manera óptima como pestañas de navegación. Si no las sigues, es posible que el sistema descarte parte del contenido raíz o le quite visibilidad. Se envían dos sugerencias:

  1. Un límite de la cantidad de elementos secundarios raíz: En la mayoría de los casos, es posible que este número sea cuatro. Esto significa que no se pueden mostrar más de cuatro pestañas.
  2. Marcas compatibles en los elementos secundarios raíz: Puedes esperar que este valor sea MediaItem#FLAG_BROWSABLE. Esto significa que solo los elementos explorables se pueden mostrar como pestañas, y los elementos reproducibles no pueden mostrarse.

Usa el siguiente código para leer las sugerencias de raíz relevantes:

Kotlin

import androidx.media.utils.MediaConstants

// Later, in your MediaBrowserServiceCompat
override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle
): BrowserRoot {

  val maximumRootChildLimit = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
      /* defaultValue= */ 4)
  val supportedRootChildFlags = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
      /* defaultValue= */ MediaItem.FLAG_BROWSABLE)

  // rest of method..
}

Java

import androidx.media.utils.MediaConstants;

// Later, in your MediaBrowserServiceCompat
@Override
public BrowserRoot onGetRoot(
    String clientPackageName, int clientUid, Bundle rootHints) {

    int maximumRootChildLimit = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
        /* defaultValue= */ 4);
    int supportedRootChildFlags = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
        /* defaultValue= */ MediaItem.FLAG_BROWSABLE);

    // rest of method...
}

Puedes ramificar la lógica de la estructura de tu jerarquía de contenido en función de los valores de estas sugerencias, especialmente si tu jerarquía varía entre las integraciones de MediaBrowser fuera de Android Auto y el SO Android Automotive. Por ejemplo, si sueles mostrar un elemento raíz reproducible, te recomendamos que, en su lugar, lo anides en un elemento raíz explorable, debido al valor de la sugerencia de marcas compatible.

Además de las sugerencias de raíz, debes seguir algunos lineamientos adicionales a fin de garantizar que las pestañas se procesen de forma óptima:

  1. Proporciona íconos monocromáticos (preferentemente uno blanco) para cada elemento de la pestaña.
  2. Proporciona etiquetas cortas pero significativas para cada elemento de la pestaña. Tener etiquetas breves reduce la posibilidad de que se corten las strings.

Muestra material gráfico multimedia

El material gráfico de los elementos multimedia se debe pasar como un URI local mediante ContentResolver.SCHEME_CONTENT o ContentResolver.SCHEME_ANDROID_RESOURCE. Este URI local debe resolverse como un mapa de bits o una interfaz dibujable en vector en los recursos de la aplicación. Para los objetos MediaDescription que representan elementos de la jerarquía de contenido, pasa el URI a través de setIconUri(). Para los objetos MediaMetadata que representan el elemento que se está reproduciendo, pasa el URI a través de putString() mediante cualquiera de las siguientes claves:

A continuación, se muestra un ejemplo de cómo descargar arte desde un URI web y exponerlo a través de un URI local:

  1. Descarga tu archivo de imagen (el siguiente fragmento de código usa Glide).

    Kotlin

    val artFile = Glide.with(context)
      .downloadOnly()
      .load(imageUri)
      .submit()
      .get()
    

    Java

    File artFile = Glide.with(context)
      .downloadOnly()
      .load(imageUri)
      .submit()
      .get();
    
  2. Compila un URI de content:// para el archivo. La sesión y el servicio del navegador multimedia deben pasar este URI a Android Auto y el SO Android Automotive.

    Kotlin

    fun File.asAlbumArtContentURI(): Uri {
      return Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(AUTHORITY)
        .appendPath(this.path)
        .build()
    }
    

    Java

    public static Uri asAlbumArtContentURI(File file) {
      return Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(AUTHORITY)
        .appendPath(file.getPath())
        .build();
    }
    
  3. Permite el acceso al archivo en el método ContentProvider.openFile().

    Kotlin

    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
      val context = this.context ?: return null
      val file = File(uri.path)
      if (!file.exists()) {
        throw FileNotFoundException(uri.path)
      }
      // Only allow access to files under cache path
      val cachePath = context.cacheDir.path
      if (!file.path.startsWith(cachePath)) {
        throw FileNotFoundException()
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
    }
    

    Java

    @Nullable
    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
      Context context = this.getContext();
      File file = new File(uri.getPath());
      if (!file.exists()) {
        throw new FileNotFoundException(uri.getPath());
      }
      // Only allow access to files under cache path
      String cachePath = context.getCacheDir().getPath();
      if (!file.getPath().startsWith(cachePath)) {
        throw new FileNotFoundException();
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }
    

Para obtener más información sobre los proveedores de contenido, consulta Cómo crear un proveedor de contenido.

Aplica los estilos de contenido

Después de crear la jerarquía de contenido con elementos que se pueden reproducir o en los que se puede navegar, puedes aplicar estilos de contenido que determinen cómo se muestran esos elementos en el automóvil.

Puedes usar los siguientes estilos de contenido:

Elementos de listas

Este estilo de contenido prioriza los títulos y los metadatos por sobre las imágenes.

Elementos de cuadrícula

Este estilo de contenido prioriza las imágenes por sobre los títulos y los metadatos.

Establece estilos de contenido predeterminados

Puedes establecer valores globales predeterminados para la manera en la que se muestran los elementos multimedia si incluyes ciertas constantes en el paquete de extras BrowserRoot del método onGetRoot() de tu servicio. Android Auto y el SO Android Automotive leen este paquete y buscan esas constantes para determinar el estilo apropiado.

Los siguientes extras pueden usarse como claves en el paquete:

Las claves se pueden mapear a los siguientes valores constantes de números enteros para influir en la presentación de esos elementos:

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM: los elementos correspondientes deben presentarse como elementos de lista.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM: los elementos correspondientes deben presentarse como elementos de cuadrícula.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: los elementos correspondientes deben presentarse como elementos de la lista "categoría". Estos son los mismos que los elementos comunes de lista, con la excepción de que los márgenes se deben aplicar alrededor de los íconos de los elementos, ya que los íconos lucen mejor cuando son pequeños. Se espera que esta sugerencia solo se brinde para elementos explorables.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: los elementos correspondientes deben presentarse como elementos de la cuadrícula "categoría". Estos son los mismos que los elementos comunes de cuadrícula, con la excepción de que los márgenes se deben aplicar alrededor de los íconos de los elementos, ya que los íconos lucen mejor cuando son pequeños. Se espera que esta sugerencia solo se brinde para elementos explorables.

En el siguiente fragmento de código, se muestra cómo establecer el estilo de contenido predeterminado de los elementos explorables en cuadrículas y el correspondiente a los elementos reproducibles en listas:

Kotlin

import androidx.media.utils.MediaConstants

@Nullable
override fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    return BrowserRoot(ROOT_ID, extras)
}

Java

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    return new BrowserRoot(ROOT_ID, extras);
}

Define estilos de contenido por elemento

La API de estilo de contenido te permite anular el estilo de contenido predeterminado de cualquier elemento secundario de un elemento multimedia explorable. Para anular el valor predeterminado, crea un paquete de extras en el objeto MediaDescription de un elemento multimedia y agrega las mismas sugerencias que se describieron anteriormente.

En el siguiente fragmento de código, se muestra cómo crear un objeto MediaItem explorable que anule el estilo de contenido predeterminado:

Kotlin

import androidx.media.utils.MediaConstants

private fun createBrowsableMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)
}

Java

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createBrowsableMediaItem(
    String mediaId,
    String folderName,
    Uri iconUri) {
    MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
    mediaDescriptionBuilder.setMediaId(mediaId);
    mediaDescriptionBuilder.setTitle(folderName);
    mediaDescriptionBuilder.setIconUri(iconUri);
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    mediaDescriptionBuilder.setExtras(extras);
    return new MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}

Agrupa elementos con sugerencias de títulos

Para agrupar elementos multimedia relacionados, usa una sugerencia que se relacione con ese elemento. Cada elemento multimedia de un grupo debe declarar un paquete de extras en su objeto MediaDescription que incluya un mapeo con la clave DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE y un valor de string idéntico. Esa string se usa como título del grupo y se puede localizar.

En el siguiente fragmento de código, se muestra cómo crear un MediaItem con un encabezado de subgrupo de "Songs":

Kotlin

import androidx.media.utils.MediaConstants

private fun createMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putString(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
        "Songs")
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), /* playable or browsable flag*/)
}

Java

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) {
   MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
   mediaDescriptionBuilder.setMediaId(mediaId);
   mediaDescriptionBuilder.setTitle(folderName);
   mediaDescriptionBuilder.setIconUri(iconUri);
   Bundle extras = new Bundle();
   extras.putString(
       MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
       "Songs");
   mediaDescriptionBuilder.setExtras(extras);
   return new MediaBrowser.MediaItem(
       mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
}

Tu app debe pasar todos los elementos multimedia que deseas agrupar como un bloque contiguo. Por ejemplo, supongamos que quieres mostrar dos grupos de elementos multimedia, "Canciones" y "Álbumes" (en ese orden), y que tu app pasa cinco elementos multimedia en el siguiente orden:

  1. Elemento multimedia A con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Elemento multimedia B con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  3. Elemento multimedia C con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Elemento multimedia D con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  5. Elemento multimedia E con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

Debido a que los elementos multimedia de los grupos "Canciones" y "Álbumes" no se mantienen juntos en bloques contiguos, Android Auto y el SO Android Automotive interpretarían esto como los siguientes cuatro grupos:

  • Grupo 1 llamado "Canciones" que contiene el elemento multimedia A
  • Grupo 2 llamado "Álbumes" que contiene el elemento multimedia B
  • Grupo 3 llamado "Canciones" que contiene elementos multimedia C y D
  • Grupo 4 llamado "Álbumes" que contiene el elemento multimedia E

Para mostrar estos elementos en dos grupos, la app pasaría las apps en el siguiente orden:

  1. Elemento multimedia A con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Elemento multimedia C con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  3. Elemento multimedia D con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Elemento multimedia B con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  5. Elemento multimedia E con extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")

Muestra indicadores de metadatos adicionales

Figura 2: Vista de reproducción con metadatos que identifican la canción y el artista, así como un ícono que indica contenido explícito

Puedes incluir indicadores de metadatos adicionales a fin de proporcionar información de un vistazo sobre el contenido en el árbol del navegador multimedia y durante la reproducción. Dentro del árbol de navegación, Android Auto y el SO Android Automotive leen los extras asociados con un elemento y buscan ciertas constantes para determinar qué indicadores mostrar. Durante la reproducción de contenido multimedia, Android Auto y el SO Android Automotive leen los metadatos de la sesión multimedia y buscan determinadas constantes para determinar los indicadores que se mostrarán.

Las siguientes constantes se pueden usar en ambos extras de descripción MediaItem y MediaMetadata:

Las siguientes constantes solo se pueden usar en los extras de descripción MediaItem:

A fin de mostrar los indicadores que aparecen mientras el usuario explora el árbol del navegador multimedia, crea un paquete de extras que incluya una o más de estas constantes y pasa ese paquete al método MediaDescription.Builder.setExtras().

En el siguiente fragmento de código, se describe cómo mostrar los indicadores para un elemento multimedia explícito con reproducción parcial:

Kotlin

import androidx.media.utils.MediaConstants

val extras = Bundle()
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

Java

import androidx.media.utils.MediaConstants;

Bundle extras = new Bundle();
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build();
return new MediaBrowserCompat.MediaItem(description, /* flags */);

Si quieres mostrar los indicadores de un elemento multimedia que se está reproduciendo, puedes declarar los valores Long de METADATA_KEY_IS_EXPLICIT o EXTRA_DOWNLOAD_STATUS en el método MediaMetadata.Builder() de tu elemento mediaSession. No puedes mostrar el indicador DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS en la vista de reproducción.

En el siguiente fragmento de código, se muestra cómo indicar que la canción actual de la vista de reproducción es explícita y que se descargó:

Kotlin

import androidx.media.utils.MediaConstants

mediaSession.setMetadata(
    MediaMetadata.Builder()
        .putString(
            MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadata.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build())

Java

import androidx.media.utils.MediaConstants;

mediaSession.setMetadata(
    new MediaMetadata.Builder()
        .putString(
            MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadata.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build());

Figura 3: Vista de reproducción con la opción "Resultados de la búsqueda" para ver elementos multimedia relacionados con la búsqueda por voz del usuario

Tu app puede proporcionar resultados de la búsqueda contextuales que se muestren a los usuarios cuando inicien una búsqueda. Android Auto y el SO Android Automotive mostrarán estos resultados a través de interfaces de búsqueda o condiciones que cambian a partir de búsquedas realizadas con anterioridad en la sesión.

Para mostrar los resultados de la búsqueda explorables, debes incluir la clave constante BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED en el paquete de extras del método onGetRoot() de tu servicio y mapear al valor booleano true.

En el siguiente fragmento de código, se muestra cómo habilitar la compatibilidad en el método onGetRoot():

Kotlin

import androidx.media.utils.MediaConstants

@Nullable
fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putBoolean(
        MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
    return BrowserRoot(ROOT_ID, extras)
}

Java

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putBoolean(
        MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true);
    return new BrowserRoot(ROOT_ID, extras);
}

Para comenzar a proporcionar resultados de la búsqueda, anula el método onSearch() en el servicio de navegador multimedia. Android Auto y el SO Android Automotive reenvían los términos de búsqueda del usuario a este método cada vez que un usuario invoca una interfaz de búsqueda o una condición de "resultados de la búsqueda". Puedes organizar los resultados de la búsqueda del método onSearch() de tu servicio mediante elementos de título para que sean más explorables. Por ejemplo, si la app reproduce música, podrás organizar los resultados por "Álbum", "Artista" y "Canciones".

En el siguiente fragmento de código, se muestra una implementación simple del método onSearch():

Kotlin

fun onSearch(query: String, extras: Bundle) {
  // Detach from results to unblock the caller (if a search is expensive)
  result.detach()
  object:AsyncTask() {
    internal var searchResponse:ArrayList
    internal var succeeded = false
    protected fun doInBackground(vararg params:Void):Void {
      searchResponse = ArrayList()
      if (doSearch(query, extras, searchResponse))
      {
        succeeded = true
      }
      return null
    }
    protected fun onPostExecute(param:Void) {
      if (succeeded)
      {
        // Sending an empty List informs the caller that there were no results.
        result.sendResult(searchResponse)
      }
      else
      {
        // This invokes onError() on the search callback
        result.sendResult(null)
      }
      return null
    }
  }.execute()
}
// Populates resultsToFill with search results. Returns true on success or false on error
private fun doSearch(
    query: String,
    extras: Bundle,
    resultsToFill: ArrayList
): Boolean {
  // Implement this method
}

Java

@Override
public void onSearch(final String query, final Bundle extras,
                        Result<List<MediaItem>> result) {

  // Detach from results to unblock the caller (if a search is expensive)
  result.detach();

  new AsyncTask<Void, Void, Void>() {
    List<MediaItem> searchResponse;
    boolean succeeded = false;
    @Override
    protected Void doInBackground(Void... params) {
      searchResponse = new ArrayList<MediaItem>();
      if (doSearch(query, extras, searchResponse)) {
        succeeded = true;
      }
      return null;
    }

    @Override
    protected void onPostExecute(Void param) {
      if (succeeded) {
       // Sending an empty List informs the caller that there were no results.
       result.sendResult(searchResponse);
      } else {
        // This invokes onError() on the search callback
        result.sendResult(null);
      }
    }
  }.execute()
}

/** Populates resultsToFill with search results. Returns true on success or false on error */
private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) {
    // Implement this method
}

Habilita el control de reproducción

Android Auto y el SO Android Automotive envían comandos de control de reproducción mediante el elemento MediaSessionCompat del servicio. Debes registrar una sesión y, luego, implementar los métodos de devolución de llamada asociados.

Registra una sesión multimedia

En el método onCreate() del servicio de tu navegador multimedia, crea un objeto MediaSessionCompat y, luego, llama a setSessionToken() a fin de registrar la sesión multimedia.

En el siguiente fragmento de código, se muestra cómo crear y registrar una sesión multimedia:

Kotlin

override fun onCreate() {
    super.onCreate()

    ...
    // Start a new MediaSession
    val session = MediaSessionCompat(this, "session tag").apply {
        // Set a callback object to handle play control requests, which
        // implements MediaSession.Callback
        setCallback(MyMediaSessionCallback())
    }
    sessionToken = session.sessionToken

    ...
}

Java

public void onCreate() {
    super.onCreate();

    ...
    // Start a new MediaSession
    MediaSessionCompat session = new MediaSessionCompat(this, "session tag");
    setSessionToken(session.getSessionToken());

    // Set a callback object to handle play control requests, which
    // implements MediaSession.Callback
    session.setCallback(new MyMediaSessionCallback());

    ...
}

Cuando creas el objeto de la sesión multimedia, estableces un objeto de devolución de llamada que se usa a fin de manejar las solicitudes de control de reproducción. A los efectos de crear este objeto de devolución de llamada, debes proporcionar una implementación de la clase MediaSessionCompat.Callback para tu app. En la siguiente sección, se explica cómo implementar este objeto.

Implementa comandos de reproducción

Cuando un usuario solicita la reproducción de un elemento multimedia de tu app, el SO Android Automotive y Android Auto usan la clase MediaSessionCompat.Callback del objeto MediaSessionCompat de tu app que se obtuvo del servicio de navegador multimedia de la app. Cuando un usuario desea controlar la reproducción de contenido, como pausar la reproducción u omitir la pista, Android Auto y el SO Android Automotive invocan a uno de los métodos del objeto de devolución de llamada.

Para controlar la reproducción de contenido, tu app debe extender la clase abstracta MediaSessionCompat.Callback e implementar los métodos compatibles con tu app.

Debes implementar todos los siguientes métodos de devolución de llamada que correspondan al tipo de contenido que ofrece tu app:

onPrepare()
Se invoca cuando cambia la fuente multimedia. El SO Android Automotive también invoca este método inmediatamente después del inicio. Debes implementar este método en tu app de música.
onPlay()
Se invoca si el usuario opta por reproducir contenido sin elegir un elemento específico. Tu app debe reproducir su contenido predeterminado. Si la reproducción se detuvo con onPause(), tu app debería reanudar la reproducción.

Nota: La app no debería comenzar a reproducir música automáticamente cuando el SO Android Automotive o Android Auto se conecten a tu servicio de navegador multimedia. Para obtener más información, consulta Configura el estado de reproducción inicial.

onPlayFromMediaId()
Se invoca cuando el usuario elige reproducir un elemento específico. El método recibe el ID que tu servicio de navegador multimedia asignó al elemento multimedia en tu jerarquía de contenido.
onPlayFromSearch()
Se invoca cuando el usuario elige reproducir un elemento desde una búsqueda. La app debe hacer la elección apropiada según la string de búsqueda que se pasa.
onPause()
Se invoca cuando el usuario elige pausar la reproducción.
onSkipToNext()
Se invoca cuando el usuario elige omitir el elemento siguiente.
onSkipToPrevious()
Se invoca cuando el usuario elige omitir el elemento anterior.
onStop()
Se invoca cuando el usuario elige detener la reproducción.

La app debe anular estos métodos para proporcionar la funcionalidad deseada. No es necesario que implementes un método si la app no lo admite. Por ejemplo, si tu app reprodujera una transmisión en vivo (como una transmisión deportiva), no tendría sentido implementar el método onSkipToNext(); podrías usar la implementación predeterminada de onSkipToNext() en su lugar.

Tu app no necesita una lógica especial para reproducir contenido en las bocinas del vehículo. Si la app recibe una solicitud para reproducir contenido, deberá reproducir el audio de la misma manera que lo hace normalmente (por ejemplo, por el altavoz o los auriculares del teléfono del usuario). Android Auto y el SO Android Automotive envían automáticamente el contenido de audio al sistema del automóvil a fin de reproducirlo en las bocinas del vehículo.

Si deseas obtener más información para reproducir contenido de audio, consulta Cómo reproducir contenido multimedia, Cómo administrar la reproducción de audio y ExoPlayer.

Establece acciones de reproducción estándar

Android Auto y el SO Android Automotive muestran los controles de reproducción según las acciones habilitadas en el objeto PlaybackStateCompat.

De forma predeterminada, la app debe admitir las siguientes acciones:

Adicionalmente, es posible que tu app admita las siguientes acciones si son relevantes para su contenido:

Además, te recomendamos que crees una cola de reproducción que se pueda mostrar al usuario. Para ello, debes llamar a los métodos setQueue() y setQueueTitle(), habilitar la acción ACTION_SKIP_TO_QUEUE_ITEM y definir la devolución de llamada onSkipToQueueItem().

Android Auto y el SO Android Automotive muestran los botones para cada acción habilitada, así como la cola de reproducción si eliges crear una.

Reserva un espacio sin usar

Android Auto y el SO Android Automotive reservan espacio en la IU para las acciones ACTION_SKIP_TO_PREVIOUS y ACTION_SKIP_TO_NEXT. Si tu app no admite una de estas funciones, Android Auto y el SO Android Automotive usarán el espacio para mostrar cualquier acción personalizada que crees.

Si no quieres llenar esos espacios con acciones personalizadas, puedes reservarlos para que Android Auto y el SO Android Automotive dejen el espacio en blanco cuando tu app no admita la función correspondiente. Para ello, llama al método setExtras() con un paquete de extras que contenga constantes que correspondan a las funciones reservadas. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT corresponde a ACTION_SKIP_TO_NEXT, y SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV corresponde a ACTION_SKIP_TO_PREVIOUS. Usa estas constantes como claves en el paquete y usa el valor booleano true para sus valores.

Establece el PlaybackState inicial

A medida que Android Auto y el SO Android Automotive se comunican con tu servicio de navegador multimedia, tu sesión multimedia comunica el estado de la reproducción de contenido mediante PlaybackState. Tu app no debería iniciar automáticamente la reproducción de música cuando el SO Android Automotive o Android Auto se conecten a tu servicio de navegador multimedia. En su lugar, utiliza Android Auto y el SO Android Automotive a fin de reanudar o iniciar la reproducción según el estado del automóvil o las acciones del usuario.

Para ello, configura el elemento PlaybackState inicial de tu sesión multimedia en STATE_STOPPED, STATE_PAUSED, STATE_NONE o STATE_ERROR.

Las sesiones multimedia dentro de Android Auto y el SO Android Automotive duran solo lo que dure el viaje, por lo que los usuarios inician y detienen estas sesiones con frecuencia. A fin de promover una experiencia fluida entre viajes, lleva un registro del estado de sesión anterior del usuario (por ejemplo, el último elemento multimedia que se reprodujo, el PlaybackState y la fila), para que cuando la app de música reciba una solicitud de reanudación, el usuario pueda retomar automáticamente desde donde dejó.

Agrega acciones de reproducción personalizadas

Puedes agregar acciones de reproducción personalizadas para mostrar las acciones adicionales que admite tu app de música. Si el espacio lo permite (y no está reservado), Android agregará las acciones personalizadas a los controles de transporte. De lo contrario, las acciones personalizadas se mostrarán en el menú ampliado. Las acciones personalizadas se muestran en el orden en que se agreguen a PlaybackState.

Las acciones personalizadas deben proporcionar un comportamiento distinto de las acciones estándar y no deben usarse para reemplazar o duplicar estas últimas.

Puedes agregar acciones personalizadas con el método addCustomAction() en la clase PlaybackStateCompat.Builder.

En el siguiente fragmento de código, se muestra cómo agregar una acción "Iniciar un canal de radio":

Kotlin

stateBuilder.addCustomAction(
    PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon
    ).run {
        setExtras(customActionExtras)
        build()
    }
)

Java

stateBuilder.addCustomAction(
    new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media),
        startRadioFromMediaIcon)
    .setExtras(customActionExtras)
    .build());

Para obtener un ejemplo detallado de este método, consulta el método setCustomAction() en la app de ejemplo de Universal Android Music Player en GitHub.

Después de crear tu acción personalizada, la sesión multimedia puede responder a ella mediante la anulación del método onCustomAction().

En el siguiente fragmento de código, se muestra cómo tu app puede responder a una acción "Iniciar un canal de radio":

Kotlin

override fun onCustomAction(action: String, extras: Bundle?) {
    when(action) {
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
            ...
        }
    }
}

Java

@Override
public void onCustomAction(@NonNull String action, Bundle extras) {
    if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
        ...
    }
}

Para obtener un ejemplo detallado de este método, consulta el método onCustomAction en la app de ejemplo de Universal Android Music Player en GitHub.

Íconos para acciones personalizadas

Cada acción personalizada que creas requiere un recurso de ícono. Las apps para vehículos se pueden ejecutar en diferentes tamaños y densidades de pantallas, de modo que los íconos que proporcionas deben ser interfaces dibujables en vector. Una interfaz dibujable en vector te permite ajustar la escala de elementos sin perder los detalles y también facilita la alineación de los bordes y las esquinas según los límites de píxeles en resoluciones más pequeñas.

Si tienes una acción personalizada con estado (por ejemplo, si activa o desactiva una configuración de reproducción), proporciona diferentes íconos para los distintos estados de manera que los usuarios puedan percibir los cambios visualmente cuando seleccionen la acción.

Proporciona un estilo de ícono alternativo para las acciones inhabilitadas

Para los casos en los que una acción personalizada no está disponible en el contexto actual, reemplaza el ícono de acción personalizada por uno alternativo que muestre que la acción está inhabilitada.

Figura 4: Ejemplo de íconos de acción personalizada inhabilitada

Cómo admitir acciones de voz

Tu app de música debe admitir acciones de voz a fin de ayudar a proporcionar a los conductores una experiencia más segura y conveniente que minimice las distracciones. Por ejemplo, si tu app ya está reproduciendo un elemento multimedia, el usuario puede decir "Reproducir Rapsodia bohemia" para indicarle a tu app que reproduzca una canción diferente sin mirar ni tocar la pantalla del vehículo.

A fin de obtener un ejemplo detallado para implementar acciones de reproducción habilitadas para voz en tu app, consulta Asistente de Google y las apps de música.

Cómo implementar protecciones contra distracción

Debido a que el teléfono del usuario está conectado a las bocinas del vehículo mientras usa Android Auto, debes tomar precauciones adicionales para ayudar a evitar la distracción del conductor.

Detecta el modo de vehículo

Las apps de música de Android Auto no deben iniciar la reproducción de audio en las bocinas del vehículo a menos que el usuario inicie la reproducción de forma consciente (por ejemplo, presionando el botón de reproducción en tu app). Ni siquiera una alarma programada por el usuario desde la app de música debería iniciar la reproducción de música en las bocinas del vehículo. A los efectos de cumplir con este requisito, la app deberá determinar si el teléfono está en modo de vehículo antes de reproducir audio. Tu app puede comprobar si el teléfono está en este modo. Para ello, debe llamar al método getCurrentModeType().

Si el teléfono del usuario está en modo de vehículo, las apps de música que admiten alarmas deberán hacer una de las siguientes acciones:

  • Desactivar la alarma
  • Reproducir la alarma mediante STREAM_ALARM y proporcionar una IU en la pantalla del teléfono para desactivarla

En el siguiente fragmento de código, se muestra cómo comprobar si una app se está ejecutando en modo de vehículo:

Kotlin

fun isCarUiMode(c: Context): Boolean {
    val uiModeManager = c.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
    return if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) {
        LogHelper.d(TAG, "Running in Car mode")
        true
    } else {
        LogHelper.d(TAG, "Running in a non-Car mode")
        false
    }
}

Java

 public static boolean isCarUiMode(Context c) {
      UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE);
      if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
            LogHelper.d(TAG, "Running in Car mode");
            return true;
      } else {
          LogHelper.d(TAG, "Running in a non-Car mode");
          return false;
        }
  }

Controla los anuncios de medios

De forma predeterminada, Android Auto muestra una notificación cuando los metadatos de los elementos multimedia cambian durante una sesión de reproducción de audio. Cuando una app de música cambia de reproducir música a ejecutar un anuncio, mostrar una notificación al usuario resulta una distracción (y es innecesario). Para evitar que Android Auto muestre una notificación en este caso, debes establecer la clave de metadatos de medios METADATA_KEY_IS_ADVERTISEMENT en METADATA_VALUE_ATTRIBUTE_PRESENT, como se muestra en el siguiente fragmento de código:

Kotlin

import androidx.media.utils.MediaConstants

override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) {
    MediaMetadataCompat.Builder().apply {
        if (isAd(mediaId)) {
            putLong(
                MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        }
        // ...add any other properties as you normally would
        mediaSession.setMetadata(build())
    }
}

Java

import androidx.media.utils.MediaConstants;

@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
    MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
    if (isAd(mediaId)) {
        builder.putLong(
            MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
    }
    // ...add any other properties as you normally would
    mediaSession.setMetadata(builder.build());
}

Cómo controlar los errores generales

Cuando la app experimenta un error, debes establecer el estado de reproducción en STATE_ERROR y proporcionar un mensaje de error con el método setErrorMessage(). Los mensajes de error se deben mostrar al usuario y se deben localizar según la configuración regional actual del usuario. Android Auto y el SO Android Automotive pueden mostrar el mensaje de error al usuario.

Para obtener más información sobre los estados de error, consulta Cómo trabajar con una sesión multimedia: estados y errores.

Si un usuario de Android Auto necesita abrir la app del teléfono para resolver un error, el mensaje deberá proporcionarle esa información. Por ejemplo, el mensaje de error deberá decir "Acceder a [nombre de tu app]" en lugar de "Acceder".

Otros recursos