Compila tu jerarquía de contenido

Android Auto y el SO Android Automotive (AAOS) llaman al servicio de navegador multimedia de tu app para descubrir qué contenido está disponible. Para admitir esta función, implementa estos dos métodos en tu servicio de navegador multimedia.

Implementa onGetRoot

El método onGetRoot de tu servicio devuelve información sobre el nodo raíz de tu jerarquía de contenido. Android Auto y AAOS usan este nodo raíz para solicitar el resto del contenido con el método onLoadChildren. En este fragmento de código, se muestra una implementación 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 onGetRoot en la app de ejemplo de Universal Android Music Player en GitHub.

Agrega validación de paquetes

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:

  • Compara el clientPackageName con tu lista de entidades permitidas.
  • Verifica el certificado que se usó 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 Android Auto y AAOS, acceso al contenido, el servicio debe mostrar un valor de BrowserRoot que no sea nulo cuando estas apps del sistema llamen al método onGetRoot.

La firma de la app del sistema de AAOS varía según la marca y el modelo del automóvil. Asegúrate de permitir las conexiones de todas las apps del sistema para admitir AAOS.

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. Asistente de Google usa nombres de paquete separados para el teléfono, que incluye Android Auto y Android Automotive OS.

Implementa onLoadChildren

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

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 cadenas 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 este fragmento de código, se muestra una implementación de 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&lt;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 descendants of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems)
}

Java

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

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

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

    // 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 descendants of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems);
}

Para ver un ejemplo de este método, consulta onLoadChildren en la app de ejemplo de Universal Android Music Player en GitHub.

Estructura el menú raíz

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 se siguen, estas sugerencias permiten que el sistema muestre el contenido raíz como pestañas de navegación. Si no sigues estas sugerencias, es posible que el sistema descarte parte del contenido raíz o le quite visibilidad.

El contenido raíz se muestra como pestañas de navegación.

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

Si aplicas estas sugerencias, el sistema mostrará el contenido raíz como pestañas de navegación. Si no aplicas estas sugerencias, es posible que se descarte parte del contenido raíz o se lo vuelva menos detectable. Se transmiten las siguientes sugerencias:

Kotlin

import androidx.media.utils.MediaConstants

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

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, usa estos lineamientos para renderizar pestañas de forma óptima:

  • Íconos monocromáticos (preferentemente blancos) para cada elemento de la pestaña

  • Etiquetas cortas y significativas para cada elemento de pestaña (las etiquetas cortas reducen las probabilidades de que se trunquen)