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 para funcionar 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. Consulta la documentación de la API de medios de Android.
  2. Revisa la sección Cómo crear apps de música para obtener orientación sobre el diseño.
  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 tu app de música implementa y que cumple con la API de MediaBrowserServiceCompat. La 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 para encontrar el servicio de ese tipo de navegador de tu app.
Elemento multimedia

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

  • FLAG_PLAYABLE: Indica que el elemento es una hoja en el árbol de contenido, y que 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.
  • FLAG_BROWSABLE: Indica que el elemento es un nodo en el árbol de contenido y que tiene elementos secundarios. Por ejemplo, que representa un álbum y sus elementos secundarios son las canciones del álbum.

Un elemento multimedia reproducible y explorable funciona como una playlist. 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 la app mediante tu servicio de navegador multimedia para explorar elementos de ese tipo. Declara tu servicio de navegador multimedia en el manifiesto para que Android Auto y el SO Android Automotive puedan encontrarlo y conectarse con tu app.

En el siguiente fragmento de código, se muestra cómo declarar el servicio de navegador multimedia en el manifiesto. Incluye este código en el archivo de manifiesto del módulo del SO Android Automotive y en el 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>

Cómo especificar íconos de la app

Debes especificar íconos de apps que Android Auto y el SO Android Automotive puedan usar para representar tu app en la IU del sistema. Se requieren dos tipos de íconos:

  • Ícono de selector
  • Ícono de atribución

Ícono de selector

El ícono de selector representa tu app en la IU del sistema, por ejemplo, en el selector y en la bandeja de íconos. Puedes usar la siguiente declaración de manifiesto para especificar si deseas usar el ícono de tu app para dispositivos móviles y, así, representar la app de música para vehículos:

<application
    ...
    android:icon="@mipmap/ic_launcher"
    ...
/>

Si quieres usar un ícono diferente al de tu app para dispositivos móviles, configura la propiedad android:icon en el elemento <service> del servicio de navegador multimedia, en el manifiesto:

<application>
    ...
    <service
        ...
        android:icon="@mipmap/auto_launcher"
        ...
    />
</application>

Ícono de atribución

Figura 1: Ícono de atribución en la tarjeta multimedia

El ícono de atribución se usa en lugares en los que tiene prioridad el contenido multimedia, como en las tarjetas multimedia. Considera volver a utilizar el ícono pequeño que se usa para las notificaciones. Este ícono debe ser monocromático. Puedes especificar el ícono que se usa para representar a tu app con la siguiente declaración de manifiesto:

<application>
    ...
    <meta-data
        android:name="androidx.car.app.TintableAttributionIcon"
        android:resource="@drawable/ic_status_icon" />
    ...
</application>

Cómo crear 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 para lo siguiente:

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

También puedes usar tu servicio de navegador multimedia para permitir que otros clientes accedan al contenido multimedia desde tu app. Estos clientes multimedia pueden ser tanto otras apps del teléfono del usuario como otros clientes remotos.

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 navegador 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 utilizan el método onCreate() para comunicarse con el servicio de navegador multimedia de tu app. 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 la sección Estructura del menú raíz en esta página 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 más 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 para 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 tengan una compatibilidad sólida con ese sistema operativo.

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 el 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 para 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 con una cadena 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.

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 whether 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 whether 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 ver 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 2: Contenido raíz que 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 con el argumento Bundle que se pasa a onGetRoot(). Si sigues estas sugerencias, el sistema mostrará el contenido raíz como pestañas de navegación de forma óptima. De lo contrario, es posible que se descarte parte del contenido raíz o se lo vuelva menos detectable. Se envían dos sugerencias:

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, tal vez sea preferible que 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 para garantizar que las pestañas se procesen de forma óptima:

  • Proporciona íconos monocromáticos, preferiblemente blancos, para cada elemento de la pestaña.
  • Proporciona etiquetas cortas pero significativas para cada elemento de pestaña. Tener etiquetas breves reduce la posibilidad de que se corten las cadenas.

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 MediaDescriptionCompat que representan elementos de la jerarquía de contenido, pasa el URI a través de setIconUri(). Para los objetos MediaMetadataCompat que representan el elemento que se está reproduciendo, pasa el URI mediante putString() con cualquiera de las siguientes claves:

En los siguientes pasos, se describe cómo descargar arte desde un URI web y exponerlo a través de un URI local. Para obtener un ejemplo más completo, consulta la implementación de openFile() y los métodos circundantes en la app de ejemplo de Universal Android Music Player.

  1. Compila un URI de content:// correspondiente al URI web. La sesión y el servicio del navegador multimedia deben pasar este URI de contenido a Android Auto y el SO Android Automotive.

    Kotlin

    fun Uri.asAlbumArtContentURI(): Uri {
      return Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(this.getPath()) // Make sure you trust the URI
        .build()
    }
    

    Java

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
    
  2. En tu implementación de ContentProvider.openFile(), verifica si existe un archivo para el URI correspondiente. De lo contrario, descarga y almacena en caché el archivo de imagen. En el siguiente fragmento de código, se usa Glide.

    Kotlin

    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
      val context = this.context ?: return null
      val file = File(context.cacheDir, uri.path)
      if (!file.exists()) {
        val remoteUri = Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.path)
            .build()
        val cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
    
        cacheFile.renameTo(file)
        file = cacheFile
      }
      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(context.getCacheDir(), uri.getPath());
      if (!file.exists()) {
        Uri remoteUri = new Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.getPath())
            .build();
        File cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS);
    
        cacheFile.renameTo(file);
        file = cacheFile;
      }
      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 se presentan como elementos de lista.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM: Los elementos correspondientes se presentan como elementos de cuadrícula.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: Los elementos correspondientes se presentan como elementos de una lista de categorías. 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. Los íconos deben ser elementos de diseño vectoriales de tono. Se espera que este ajuste solo se brinde para elementos explorables.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: Los elementos correspondientes se presentan como elementos de una cuadrícula de categorías. 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. Los íconos deben ser elementos de diseño vectoriales de tono. Se espera que este ajuste 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 Content Style te permite anular el estilo de contenido predeterminado de cualquier elemento secundario de un elemento multimedia explorable, así como cualquier elemento multimedia en sí.

Para anular el valor predeterminado de los elementos secundarios de un elemento multimedia explorable, crea un paquete de extras en el MediaDescription del elemento multimedia y agrega las mismas sugerencias de antes. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE se aplica a los elementos secundarios reproducibles, mientras que DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE se aplica a los elementos secundarios explorables.

Para anular el valor predeterminado de un elemento multimedia específico en sí mismo, en lugar de sus elementos secundarios, crea un paquete de extras en la MediaDescription del elemento multimedia y agrega una sugerencia con la clave DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM. Usa los mismos valores descritos anteriormente para especificar la presentación del elemento.

En el siguiente fragmento de código, se muestra la creación de un objeto MediaItem explorable que anula el estilo de contenido predeterminado para él y sus elementos secundarios. Se diseña como un elemento de lista de categoría, sus elementos secundarios explorables como elementos de lista y sus elementos secundarios reproducibles como elementos de cuadrícula:

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_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM)
    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_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM);
    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 una asignación con la clave DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE y un valor de cadena idéntico. Localiza esta cadena, que se usa como título del grupo.

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*/);
}

La 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 debe pasar los 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 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

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 constantes específicas para determinar los indicadores que se mostrarán.

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

Figura 4: Vista de exploración con un punto para contenido no reproducido en el primer elemento y una barra de progreso para el contenido reproducido de forma parcial en el segundo elemento

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

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

Para 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 indica cómo mostrar los indicadores para un elemento multimedia explícito que está completo en un 70%:

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)
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7)
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);
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7);
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 MediaMetadataCompat de tu mediaSession. No puedes mostrar los indicadores DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS o DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE 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(
    MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.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 MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.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());

Actualiza la barra de progreso en la vista de exploración mientras se reproduce el contenido

Como se explicó anteriormente, puedes usar el extra DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE para mostrar una barra de progreso del contenido que se reprodujo parcialmente en la vista de navegación. Sin embargo, si un usuario continúa reproduciendo el contenido que no se terminó de reproducir en Android Auto o el SO Android Automotive, el indicador en cuestión se volverá inexacto a medida que pase el tiempo.

Para que Android Auto y el SO Android Automotive mantengan la barra de progreso actualizada, puedes proporcionar más información en MediaMetadataCompat y PlaybackStateCompat de modo que el contenido en curso se vincule a los elementos multimedia de la vista de exploración. Debes cumplir los siguientes requisitos para que el elemento multimedia tenga una barra de progreso de actualización automática:

En el siguiente fragmento de código, se muestra cómo indicar que el elemento que se está reproduciendo está vinculado a un elemento en la vista de exploración:

Kotlin

import androidx.media.utils.MediaConstants

// When the MediaItem is constructed to show in the browse view.
// Suppose the item was 25% complete when the user launched the browse view.
val mediaItemExtras = Bundle()
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters.
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

// Elsewhere, when the user has selected MediaItem for playback.
mediaSession.setMetadata(
    MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters.
        .build())

val playbackStateExtras = Bundle()
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id")
mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters.
        .build())

Java

import androidx.media.utils.MediaConstants;

// When the MediaItem is constructed to show in the browse view.
// Suppose the item was 25% complete when the user launched the browse view.
Bundle mediaItemExtras = new Bundle();
mediaItemExtras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId("my-media-id")
        .setExtras(mediaItemExtras)
        // ...and any other setters.
        .build();
return MediaBrowserCompat.MediaItem(description, /* flags */);

// Elsewhere, when the user has selected MediaItem for playback.
mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id")
        // ...and any other setters.
        .build());

Bundle playbackStateExtras = new Bundle();
playbackStateExtras.putString(
    MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id");
mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setExtras(playbackStateExtras)
        // ...and any other setters.
        .build());

Figura 5: 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

La 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 mediante interfaces de búsqueda o condiciones que cambian a partir de búsquedas realizadas con anterioridad en la sesión. Para obtener más información, consulta la sección Cómo admitir acciones de voz en esta guía.

Para mostrar los resultados de la búsqueda explorables, incluye la clave constante BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED en el paquete de extras del método onGetRoot() de tu servicio y mapea 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.
}

Acciones de navegación personalizadas

Una sola acción de navegación personalizada.

Figura 6: Acción de navegación personalizada única

Las acciones de navegación personalizadas te permiten agregar íconos y etiquetas personalizados a los objetos MediaItem de tu app en la app de música del vehículo, y controlar las interacciones del usuario con estas acciones. Esto te permite extender la funcionalidad de la app de música de varias maneras, como agregar acciones "Descargar", "Agregar a la fila", "Reproducir radio", "Favoritos" o "Quitar".

Un menú ampliado de acciones de navegación personalizada.

Figura 7: Menú ampliado de acciones de navegación personalizada

Si hay más acciones personalizadas de las que permite mostrar el OEM, se mostrará un menú ampliado al usuario.

¿Cómo funcionan?

Cada acción de navegación personalizada se define con lo siguiente:

  • Un ID de acción (un identificador de cadena único)
  • Una etiqueta de acción (el texto que se muestra al usuario)
  • Un URI de ícono de acción (un elemento de diseño vectorial que puede ajustarse)

Define una lista de acciones de navegación personalizadas a nivel global como parte de tu BrowseRoot. Luego, puedes adjuntar un subconjunto de estas acciones a MediaItem. individuales.

Cuando un usuario interactúa con una acción de navegación personalizada, tu app recibe una devolución de llamada en onCustomAction(). Luego, puedes controlar la acción y actualizar la lista de acciones para el MediaItem, si es necesario. Esto es útil para acciones con estado, como "Favorite" y "Download". En el caso de las acciones que no necesitan actualización, como "Reproducir radio", no necesitas actualizar la lista de acciones.

Acciones de navegación personalizadas en la raíz de un nodo de exploración.

Figura 8: Barra de herramientas de acciones de navegación personalizada

También puedes adjuntar acciones de navegación personalizadas a la raíz de un nodo de navegación. Estas acciones se mostrarán en una barra de herramientas secundaria, debajo de la principal.

Cómo implementar acciones de navegación personalizadas

Sigue estos pasos para agregar acciones de navegación personalizadas a tu proyecto:

  1. Anula dos métodos en tu implementación de MediaBrowserServiceCompat:
  2. Analiza los límites de acciones en el tiempo de ejecución:
  3. Crea la lista global de acciones de navegación personalizadas:
    • Para cada acción, crea un objeto Bundle con las siguientes claves: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID: El ID de acción * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: La etiqueta de acción * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: El URI del ícono de acción * Agrega todos los objetos Bundle de acción a una lista.
  4. Agrega la lista global a tu BrowseRoot:
  5. Agrega acciones a tus objetos MediaItem:
    • Puedes agregar acciones a objetos MediaItem individuales si incluyes la lista de IDs de acción en los servicios adicionales MediaDescriptionCompat con la clave DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST. Esta lista debe ser un subconjunto de la lista global de acciones que definiste en el BrowseRoot.
  6. Controla acciones y muestra el progreso o los resultados:

A continuación, se muestran algunos cambios que puedes realizar en tu BrowserServiceCompat para comenzar a usar las acciones de navegación personalizadas.

Anula BrowserServiceCompat

Debes anular los siguientes métodos en MediaBrowserServiceCompat.

public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)

public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)

Límite de acciones de análisis

Deberías comprobar cuántas acciones de navegación personalizadas se admiten.

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
    rootHints.getInt(
            MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0)
}

Cómo compilar una acción de navegación personalizada

Cada acción debe empaquetarse en un Bundle separado.

  • ID de acción
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
    
  • Etiqueta de acción
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
    
  • URI del ícono de acción
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")
    

Agrega acciones de navegación personalizadas a Parceable ArrayList

Agrega todos los objetos Bundle de acciones de navegación personalizadas a una ArrayList.

private ArrayList<Bundle> createCustomActionsList(
                                        CustomBrowseAction browseActions) {
    ArrayList<Bundle> browseActionsBundle = new ArrayList<>();
    for (CustomBrowseAction browseAction : browseActions) {
        Bundle action = new Bundle();
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                browseAction.mId);
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                getString(browseAction.mLabelResId));
        action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                browseAction.mIcon);
        browseActionsBundle.add(action);
    }
    return browseActionsBundle;
}

Cómo agregar una lista de acciones de navegación personalizadas a la raíz de la navegación

public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                             Bundle rootHints) {
    Bundle browserRootExtras = new Bundle();
    browserRootExtras.putParcelableArrayList(
            BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST,
            createCustomActionsList()));
    mRoot = new BrowserRoot(ROOT_ID, browserRootExtras);
    return mRoot;
}

Agrega acciones a un MediaItem

MediaDescriptionCompat buildDescription (long id, String title, String subtitle,
                String description, Uri iconUri, Uri mediaUri,
                ArrayList<String> browseActionIds) {

    MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
    bob.setMediaId(id);
    bob.setTitle(title);
    bob.setSubtitle(subtitle);
    bob.setDescription(description);
    bob.setIconUri(iconUri);
    bob.setMediaUri(mediaUri);

    Bundle extras = new Bundle();
    extras.putStringArrayList(
          DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST,
          browseActionIds);

    bob.setExtras(extras);
    return bob.build();
}
MediaItem mediaItem = new MediaItem(buildDescription(...), flags);

Resultado de la compilación onCustomAction

  • Analiza mediaId de Bundle extras:
    @Override
    public void onCustomAction(
              @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){
      String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID);
    }
    
  • Para los resultados asíncronos, desconecta el resultado. result.detach()
  • Cómo crear el paquete de resultados
    • Mensaje para el usuario
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
      
    • Actualizar elemento(usar para actualizar acciones en un elemento)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
      
    • Abre la Vista de reproducción.
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
      
    • Actualizar el nodo de exploración
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
      
  • Si se produce un error, llama a result.sendError(resultBundle)..
  • Si se actualiza el progreso, llama a result.sendProgressUpdate(resultBundle).
  • Para finalizar, llama a result.sendResult(resultBundle).

Actualizar estado de acción

Si usas el método result.sendProgressUpdate(resultBundle) con la clave EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, puedes actualizar el MediaItem para que refleje el nuevo estado de la acción. Esto te permite proporcionar comentarios en tiempo real al usuario sobre el progreso y el resultado de su acción.

Ejemplo: Acción de descarga

A continuación, se muestra un ejemplo de cómo puedes usar esta función para implementar una acción de descarga con tres estados:

  1. Descargar: Es el estado inicial de la acción. Cuando el usuario selecciona esta acción, puedes cambiarla por "Downloading" y llamar a sendProgressUpdate para actualizar la IU.
  2. Descargando: Este estado indica que la descarga está en curso. Puedes usar este estado para mostrarle al usuario una barra de progreso o algún otro indicador.
  3. Descargado: Este estado indica que se completó la descarga. Cuando finaliza la descarga, puedes cambiar "Downloading" por "Installed" y llamar a sendResult con la clave EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM para indicar que se debe actualizar el elemento. Además, puedes usar la clave EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE para mostrar un mensaje de éxito al usuario.

Este enfoque te permite proporcionar comentarios claros al usuario sobre el proceso de descarga y su estado actual. Puedes agregar aún más detalles con íconos para mostrar los estados de descarga del 25%, 50% y 75%.

Ejemplo: Acción favorita

Otro ejemplo es una acción favorita con dos estados:

  1. Favoritos: Esta acción se muestra para los elementos que no están en la lista de favoritos del usuario. Cuando el usuario selecciona esta acción, puedes cambiarla por "Favorited" y llamar a sendResult con la tecla EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM para actualizar la IU.
  2. Favoritos: Esta acción se muestra para los elementos que están en la lista de favoritos del usuario. Cuando el usuario selecciona esta acción, puedes cambiarla por "Favorite" y llamar a sendResult con la tecla EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM para actualizar la IU.

Este enfoque proporciona una forma clara y coherente para que los usuarios administren sus elementos favoritos.

Estos ejemplos muestran la flexibilidad de las acciones de navegación personalizadas y cómo puedes usarlas para implementar una variedad de funcionalidades con comentarios en tiempo real para mejorar la experiencia del usuario en la app de música del vehículo.

Para ver un ejemplo completo de la implementación de esta función, puedes consultar el proyecto TestMediaApp.

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 that implements MediaSession.Callback
        // to handle play control requests.
        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 that implements MediaSession.Callback
    // to handle play control requests.
    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.

Implementa todos los métodos de devolución de llamada presentados a continuación 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 o, si se pausó la reproducción con onPause(), esta se reanuda.

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 la sección sobre la configuración del 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 una elección apropiada según la cadena 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.

Anula estos métodos en tu app para brindar la funcionalidades que desees. No es necesario que implementes un método si su funcionalidad no es compatible con tu app. Por ejemplo, si tu app reproduce una transmisión en vivo, como una transmisión deportiva, no necesitas implementar el método onSkipToNext(). En cambio, puedes usar la implementación predeterminada de onSkipToNext().

La app no necesita una lógica especial para reproducir contenido en las bocinas del vehículo. Cuando recibe una solicitud para reproducir contenido, puede reproducir audio del mismo modo que el resto del contenido a través de las bocinas 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 para su reproducción en las bocinas del vehículo.

Para obtener más información sobre la reproducción de contenido de audio, consulta la descripción general de MediaPlayer, la de la app de audio y la de 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:

También puede admitir las siguientes acciones si son relevantes para su contenido:

Además, tienes la opción de crear una cola de reproducción que se pueda mostrar al usuario, pero esto no es obligatorio. Para ello, llama a los métodos setQueue() y setQueueTitle(), habilita ACTION_SKIP_TO_QUEUE_ITEM. y define la devolución de llamada onSkipToQueueItem().

Además, agrega compatibilidad con el ícono Está sonando, que indica lo que se está reproduciendo en el momento. Para ello, llama al método setActiveQueueItemId() y pasa el ID del elemento que se está reproduciendo en la cola. Debe actualizarse setActiveQueueItemId() cada vez que haya un cambio en la cola de reproducción.

Android Auto y el SO Android Automotive muestran los botones para cada acción habilitada, así como la cola de reproducción. Cuando se hace clic en los botones, el sistema invoca su devolución de llamada correspondiente desde MediaSessionCompat.Callback.

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 PlaybackStateCompat. 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 cambio, utiliza Android Auto y el SO Android Automotive para reanudar o iniciar la reproducción según el estado del vehículo o las acciones del usuario.

Para ello, configura el elemento inicial PlaybackStateCompat 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. Para promover una experiencia fluida entre viajes, lleva un registro del estado de sesión anterior del usuario, de modo que cuando la app de música reciba una solicitud de reanudación, el usuario pueda retomar automáticamente desde donde dejó; por ejemplo, el último elemento multimedia que se reprodujo, el PlaybackStateCompat y la cola.

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 agregan a PlaybackStateCompat.

Usa acciones personalizadas para proporcionar un comportamiento distinto de las acciones estándar, no para reemplazarlas o duplicarlas.

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 más 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 más 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 ver los cambios cuando seleccionen la acción.

Proporciona un estilo de ícono alternativo para las acciones inhabilitadas

Si 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 6: Ejemplos de íconos de acción personalizada que no están relacionados con el estilo.

Indica el formato de audio

Para indicar que el contenido multimedia que se está reproduciendo usa un formato de audio especial, puedes especificar los íconos que se renderizan en vehículos compatibles con esta función. Puedes configurar KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI y KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI en el paquete de extras del elemento multimedia en reproducción (pasado a MediaSession.setMetadata()) Asegúrate de establecer ambos elementos adicionales para que se adapten a diferentes diseños.

Además, puedes configurar el elemento adicional KEY_IMMERSIVE_AUDIO para indicarles a los OEMs de automóviles que se trata de audio envolvente, y ellos deben tener mucho cuidado a la hora de decidir si aplican efectos de audio que podrían interferir con el contenido envolvente.

Puedes configurar el elemento multimedia que se está reproduciendo actualmente para que el subtítulo, la descripción o ambos sean vínculos a otros elementos multimedia. Eso permite al usuario saltar rápidamente a elementos relacionados, por ejemplo, a otras canciones del mismo artista, a otros episodios de ese podcast, etc. Si el vehículo admite esta función, los usuarios pueden presionar el vínculo para explorar ese contenido.

Para agregar vínculos, configura los metadatos KEY_SUBTITLE_LINK_MEDIA_ID (para vincular desde el subtítulo) o KEY_DESCRIPTION_LINK_MEDIA_ID (para vincular desde la descripción). Para obtener más detalles, consulta la documentación de referencia de esos campos de metadatos.

Cómo admitir acciones de voz

Tu app de música debe admitir acciones de voz para ayudar a proporcionar a los conductores una experiencia más segura y conveniente que minimice las distracciones. Por ejemplo, si ya está reproduciendo un elemento multimedia, el usuario puede decir "Reproducir [nombre de la canción]" para indicarle que reproduzca una canción diferente sin mirar ni tocar la pantalla del vehículo. Para iniciar las consultas, los usuarios pueden hacer clic en los botones correspondientes del volante o decir en voz alta las palabras clave "Hey Google".

Cuando Android Auto o el SO Android Automotive detectan e interpretan una acción de voz, esa acción de voz se entrega a la app a través de onPlayFromSearch(). La app recibe esta devolución de llamada y encuentra contenido que coincide con la cadena query para comenzar a reproducirlo.

Los usuarios pueden especificar diferentes categorías de términos en su consulta: género, artista, álbum, nombre de la canción, estación de radio o playlist, entre otros. Cuando crees compatibilidad con la búsqueda, ten en cuenta todas las categorías que sean adecuadas para tu app. Si Android Auto o el SO Android Automotive detectan que una búsqueda determinada se ajusta a ciertas categorías, agregará extras en el parámetro extras. Pueden enviarse los siguientes extras:

Ten en cuenta que Android Auto o el SO Android Automotive pueden enviar una cadena query vacía si el usuario no especifica términos de búsqueda. Por ejemplo, si dice "Reproducir música". En ese caso, tu app podría elegir comenzar una pista que se reprodujo recientemente o una sugerida.

Si una búsqueda no se puede procesar rápidamente, no bloquees en onPlayFromSearch(). En cambio, establece el estado de reproducción en STATE_CONNECTING y realiza la búsqueda en un subproceso asíncrono.

Una vez que comience la reproducción, considera propagar la cola de la sesión multimedia con contenido relacionado. Por ejemplo, si el usuario solicita que se reproduzca un álbum, tu app podría llenar la cola con la lista de pistas del álbum. Considera también implementar compatibilidad con los resultados de la búsqueda explorables, de modo que un usuario pueda elegir un segmento diferente que coincida con su consulta.

Además de las consultas de "reproducir", Android Auto y el SO Android Automotive reconocen las consultas por voz para controlar la reproducción como "pausar música y "siguiente canción", y hacen coincidir estos comandos con las devoluciones de llamadas de sesión multimedia adecuadas, como onPause() y onSkipToNext().

Si deseas obtener un ejemplo detallado sobre la implementación de acciones de reproducción habilitadas para voz en tu app, consulta Asistente de Google y las apps de música.

Implementa 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.

Elimina las alarmas en el vehículo

Las apps multimedia de Android Auto no deben iniciar la reproducción de audio en las bocinas del vehículo, a menos que la inicie el usuario (por ejemplo, presionando un botón de reproducción). 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.

Para cumplir con este requisito, tu app puede usar CarConnection como indicador antes de reproducir audio. La app puede verificar si el teléfono proyecta a una pantalla del vehículo. Para ello, consulta LiveData para ver el tipo de conexión del vehículo y verifica si es igual a CONNECTION_TYPE_PROJECTION.

Si el teléfono del usuario está proyectando, las apps de música que admiten alarmas deben 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

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. Para evitar que Android Auto muestre una notificación en este caso, debes establecer la clave de metadatos multimedia 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 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 you normally would.
    mediaSession.setMetadata(builder.build());
}

Cómo controlar los errores generales

Si la app experimenta un error, establece el estado de reproducción en STATE_ERROR y proporciona un mensaje de error con el método setErrorMessage(). Consulta PlaybackStateCompat para obtener una lista de códigos de error que puedes usar cuando configuras el mensaje de error. Los mensajes de error se deben mostrar al usuario y se deben localizar según su configuración regional actual. Android Auto y el SO Android Automotive pueden mostrar el mensaje de error al usuario.

Por ejemplo, si el contenido no está disponible en la región actual del usuario, puedes usar el código de error ERROR_CODE_NOT_AVAILABLE_IN_REGION cuando configures el mensaje de error.

Kotlin

mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR)
        .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region))
        // ...and any other setters.
        .build())

Java

mediaSession.setPlaybackState(
    new PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR)
        .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region))
        // ...and any other setters.
        .build());

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

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

Otros recursos