Cómo crear apps multimedia de Android para vehículos

El SO Android Automotive y Android Auto te permiten ofrecerles a los usuarios el contenido multimedia de tu app en sus vehículos. Para obtener una descripción general sobre cómo Android habilita las experiencias de tu app en vehículos, consulta la descripción general de Android para autos.

En esta guía, se da por sentado que ya tienes una app para teléfonos multimedia, y se describe cómo compilar una app para el SO Android Automotive y cómo extender tu app para teléfonos con Android Auto.

Antes de comenzar

Antes de comenzar a compilar tu app, asegúrate de seguir los pasos de Cómo comenzar a usar Android para autos y, luego, revisa la información de esta sección.

Términos y conceptos clave

Servicio de exploración multimedia
Es un servicio de Android implementado por tu app multimedia que cumple con la API MediaBrowseServiceCompat. La app usa este servicio para exponer el contenido de exploración multimedia en el SO Android Automotive y en Android Auto.
Exploración multimedia
Se trata de una API que usan las apps multimedia para exponer su contenido en el SO Android Automotive y en Android Auto.
Elemento multimedia
Es un objeto MediaBrowserCompat.MediaItem individual en el árbol de exploración multimedia. Los elementos multimedia pueden ser de uno de los siguientes tipos:
  • Reproducibles: Estos elementos representan transmisiones reales de sonido, como canciones de un álbum, capítulos de un libro o episodios de un podcast.
  • Explorables: Estos elementos organizan en grupos los elementos multimedia reproducibles. Por ejemplo, puedes agrupar capítulos en un libro, canciones en un álbum o episodios en un podcast.

Nota: Un elemento multimedia explorable y reproducible se trata como elemento reproducible.

Actividades optimizadas para vehículos

Se trata de las actividades de una app con SO Android Automotive que cumplen con los lineamientos de diseño del SO Android Automotive. La interfaz de estas actividades no está desarrollada por el SO Android Automotive, de modo 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, porque estas interfaces requieren atención extendida o interacción 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 que diseñes actividades para Android Auto, ya que este diseña su propia interfaz optimizada para vehículos con la información del servicio de exploración multimedia.

Cómo configurar los archivos del manifiesto de tu app

Debes configurar los archivos del manifiesto para indicar que la app está disponible para el SO Android Automotive y que la app del teléfono es compatible con los servicios multimedia de Android Auto.

Declara la compatibilidad con el SO Android Automotive

La app que distribuyes con tu SO Android Automotive debe ser independiente de la app de teléfono. Te recomendamos usar módulos y Android App Bundle para reutilizar el código, y compilar y publicar tu app con facilidad. Agrega la siguiente entrada al archivo de manifiesto en el módulo del SO Android Automotive para indicar que el código del módulo está restringido a este SO:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.media">
       <uses-feature
               android:name="android.hardware.type.automotive"
               android:required="true"/>
    </manifest>
    

Declara la compatibilidad del contenido multimedia con Android Auto

Android Auto usa la app del teléfono con el fin de proporcionar a los usuarios una experiencia optimizada para conductores. Usa la siguiente entrada del manifiesto para declarar que tu app de teléfono es compatible con Android Auto:

<application>
        ...
        <meta-data android:name="com.google.android.gms.car.application"
            android:resource="@xml/automotive_app_desc"/>
        ...
    <application>
    

Esta entrada de manifiesto hace referencia a un archivo XML que declara con qué capacidades de Automotive es compatible tu app. Para indicar que tienes una app multimedia, agrega un archivo XML denominado automotive_app_desc.xml al directorio res/xml/ de tu proyecto. Este archivo debe incluir el siguiente contenido:

<automotiveApp>
        <uses name="media"/>
    </automotiveApp>
    

Declara tu servicio de exploración multimedia

Tanto el SO Android Automotive como Android Auto se conectan a tu app mediante un servicio de exploración multimedia con el fin de buscar elementos multimedia. Debes declarar el servicio del explorador multimedia en tu manifiesto para permitir que el SO Android Automotive y Android Auto detecten el servicio y se conecten a tu app.

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

El SO Android Automotive y Android Auto muestran a los usuarios el ícono de tu app en diferentes lugares mientras usan tu aplicación o interactúan con el vehículo. Por ejemplo, si un usuario está ejecutando una app de navegación, y termina una canción y comienza otra, verá una notificación con el ícono de tu app. Android Auto y el SO Android Automotive también muestran el ícono de tu app en otras ubicaciones mientras el usuario navega por el contenido multimedia.

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>
    

Compila un servicio de exploración multimedia

Para crear un servicio de exploración multimedia, puedes extender la clase MediaBrowserServiceCompat. Tanto el SO Android Automotive como Android Auto pueden usar tu servicio para 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 exploración multimedia

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

  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 exploración multimedia de tu app mediante el método onCreate(). En tu 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; en su lugar, 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 de elemento multimedia raíz. El SO Android Automotive y Android Auto muestran estos elementos multimedia como el nivel superior de los elementos de contenido. Los elementos de contenido de nivel superior deben ser explorables.
  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.

Cómo exploran los usuarios el contenido de las apps multimedia

Para ayudar a los usuarios a explorar con rapidez el contenido de tus apps, Android Auto incluye una capacidad de exploración que les permite seleccionar una letra en un teclado en pantalla. Luego, se muestra al usuario una lista de elementos que comienzan con esa letra en la lista actual del panel lateral. Esta prestación funciona con contenido ordenado y sin ordenar, y actualmente está disponible solo en inglés.

Figura 1: Selector alfanumérico en la pantalla del vehículo

Figura 2: Vista de lista alfabética en la pantalla del vehículo

Compila tu jerarquía de contenido

El SO Android Automotive y Android Auto llaman al servicio de exploración multimedia de tu app con el fin de averiguar qué contenido está disponible. Para ello, debes implementar dos métodos en tu servicio de exploración: onGetRoot() y onLoadChildren().

Implementa GetRoot

El método onGetRoot() del servicio muestra información sobre el nodo raíz de la jerarquía de contenido. El SO Android Automotive y Android Auto usan este nodo raíz para solicitar el resto del contenido usando 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 Universal Android Music Player en GitHub.

Agrega validación de paquetes para onGetRoot()

Cuando se llama al método onGetRoot() del servicio, el paquete de llamada pasa la información de identificación al 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 blanca y verificas el certificado usado para firmar el APK del paquete. Si no se puede verificar el paquete, se muestra null para denegar el acceso al 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 llamen al método onGetRoot(). 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 sobre cómo implementar la validación de paquetes para el método onGetRoot() del servicio.

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 la jerarquía de contenido está representado por un objeto MediaBrowserCompat.MediaItem. Cada uno de estos elementos multimedia se identifica mediante una string de ID única. Las apps cliente tratan estas strings de ID como tokens opacos. Si una app de 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: El SO Android Automotive y Android Auto tienen límites estrictos para la cantidad de elementos multimedia que pueden mostrarse en cada nivel del menú. Estos límites minimizan las distracciones de los conductores y ayudan a operar la app con comandos por voz. Para obtener más información, consulta Cómo examinar los detalles de contenido y Panel de apps de Android Auto.

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

Aplica los estilos de contenido

Después de compilar la jerarquía de contenido con elementos navegables o reproducibles, puedes aplicar los estilos de contenido que determinan cómo se muestran esos elementos en el vehículo.

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.

Elementos de títulos

Este estilo de contenido muestra incluso más metadatos que el estilo de contenido de Elementos de listas. Si quieres usar este estilo de contenido, debes proporcionar metadatos adicionales para cada elemento multimedia.

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() del servicio. El SO Android Automotive y Android Auto leen el paquete de extras asociado con cada elemento en el árbol de exploración y buscan las constantes para determinar el estilo adecuado.

Usa el siguiente código para declarar estas constantes en tu app:

Kotlin

    /** Declares that ContentStyle is supported */
    val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"

    /**
    * Bundle extra indicating the presentation hint for playable media items.
    */
    val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"

    /**
    * Bundle extra indicating the presentation hint for browsable media items.
    */
    val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"

    /**
    * Specifies the corresponding items should be presented as lists.
    */
    val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1

    /**
    * Specifies that the corresponding items should be presented as grids.
    */
    val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
    

Java

    /** Declares that ContentStyle is supported */
    public static final String CONTENT_STYLE_SUPPORTED =
       "android.media.browse.CONTENT_STYLE_SUPPORTED";

    /**
    * Bundle extra indicating the presentation hint for playable media items.
    */
    public static final String CONTENT_STYLE_PLAYABLE_HINT =
       "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT";

    /**
    * Bundle extra indicating the presentation hint for browsable media items.
    */
    public static final String CONTENT_STYLE_BROWSABLE_HINT =
       "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT";

    /**
    * Specifies the corresponding items should be presented as lists.
    */
    public static final int CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1;

    /**
    * Specifies that the corresponding items should be presented as grids.
    */
    public static final int CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2;
    

Una vez que hayas declarado estas constantes, inclúyelas en el paquete de extras del método onGetRoot() del servicio para establecer el estilo predeterminado del contenido. 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

    @Nullable
    override fun onGetRoot(
        @NonNull clientPackageName: String,
        clientUid: Int,
        @Nullable rootHints: Bundle
    ): BrowserRoot {
        val extras = Bundle()
        extras.putBoolean(CONTENT_STYLE_SUPPORTED, true)
        extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE)
        extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE)
        return BrowserRoot(ROOT_ID, extras)
    }
    

Java

    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
       @Nullable Bundle rootHints) {
       Bundle extras = new Bundle();
       extras.putBoolean(CONTENT_STYLE_SUPPORTED, true);
       extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE);
       extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE);
       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. Con el fin de anular el valor predeterminado, crea un paquete de extras en el MediaDescription de un elemento multimedia.

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

Kotlin

    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(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE)
        extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE)
        return MediaBrowser.MediaItem(
            mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)
    }
    

Java

    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(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE);
       extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE);
       return new MediaBrowser.MediaItem(
           mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
    }
    

Agrega elementos de título

Para presentar los elementos multimedia como elementos de título, usa un estilo de contenido por elemento que agrupe los elementos. Cada elemento multimedia del grupo debe declarar un paquete de extras en la instancia de MediaDescription que usa una string idéntica. Esta string se usa como título del grupo y puede localizarse.

El SO Android Automotive y Android Auto no clasifican los elementos agrupados de esta manera. Debes pasar los elementos multimedia juntos y en el orden que quieres que se muestren.

Por ejemplo, supongamos que la app pasó tres elementos multimedia en el siguiente orden:

  • Elemento multimedia 1 con extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  • Elemento multimedia 2 con extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
  • Elemento multimedia 3 con extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")

El SO Android Automotive OS y Android Auto no combinan el elemento multimedia 1 y el elemento multimedia 3 en el grupo "Songs", sino que estos se guardan separados.

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

Kotlin

    val EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"

    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(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
        return MediaBrowser.MediaItem(
            mediaDescriptionBuilder.build(), /* playable or browsable flag*/)
    }
    

Java

    public static final String EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT =
      "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT";

    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(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs");
       return new MediaBrowser.MediaItem(
           mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
    }
    

Muestra indicadores de metadatos adicionales

Figura 3: Vista de reproducción con indicadores de metadatos

Puedes incluir indicadores de metadatos adicionales para proporcionar información de un vistazo del contenido del árbol de exploración multimedia y durante la reproducción. En el árbol de exploración, el SO Android Automotive y Android Auto leen los extras asociados con un elemento, y buscan ciertas constantes para determinar qué indicadores mostrar. Durante la reproducción multimedia, el SO Android Automotive y Android Auto leen los metadatos de la sesión multimedia y buscan determinadas constantes para determinar cuáles indicadores mostrar.

Usa el siguiente código para declarar las constantes del indicador de metadatos en tu app:

Kotlin

    // Bundle extra indicating that a song contains explicit content.
    var EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT"

    /**
    * Bundle extra indicating that a media item is available offline.
    * Same as MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS.
    */
    var EXTRA_IS_DOWNLOADED = "android.media.extra.DOWNLOAD_STATUS"

    /**
    * Bundle extra value indicating that an item should show the corresponding
    * metadata.
    */
    var EXTRA_METADATA_ENABLED_VALUE:Long = 1

    /**
    * Bundle extra indicating the played state of long-form content (such as podcast
    * episodes or audiobooks).
    */
    var EXTRA_PLAY_COMPLETION_STATE = "android.media.extra.PLAYBACK_STATUS"

    /**
    * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
    * not been played at all.
    */
    var STATUS_NOT_PLAYED = 0

    /**
    * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
    * been partially played (i.e. the current position is somewhere in the middle).
    */
    var STATUS_PARTIALLY_PLAYED = 1

    /**
    * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
    * been completed.
    */
    var STATUS_FULLY_PLAYED = 2
    

Java

    // Bundle extra indicating that a song contains explicit content.
    String EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT";

    /**
     * Bundle extra indicating that a media item is available offline.
     * Same as MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS.
     */
    String EXTRA_IS_DOWNLOADED = "android.media.extra.DOWNLOAD_STATUS";

    /**
     * Bundle extra value indicating that an item should show the corresponding
     * metadata.
     */
    long EXTRA_METADATA_ENABLED_VALUE = 1;

    /**
     * Bundle extra indicating the played state of long-form content (such as podcast
     * episodes or audiobooks).
     */
    String EXTRA_PLAY_COMPLETION_STATE = "android.media.extra.PLAYBACK_STATUS";

    /**
     * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
     * not been played at all.
     */
    int STATUS_NOT_PLAYED = 0;

    /**
     * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
     * been partially played (i.e. the current position is somewhere in the middle).
     */
    int STATUS_PARTIALLY_PLAYED = 1;

    /**
     * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
     * been completed.
     */
    int STATUS_FULLY_PLAYED = 2;
    

Una vez que hayas declarado estas constantes, puedes usarlas para mostrar los indicadores de metadatos. Para mostrar los indicadores que aparecen mientras el usuario está usando el árbol de exploración 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

    val extras = Bundle()
    extras.putLong(EXTRA_IS_EXPLICIT, 1)
    extras.putInt(EXTRA_PLAY_COMPLETION_STATE, STATUS_PARTIALLY_PLAYED)
    val description = MediaDescriptionCompat.Builder()
    .setMediaId(/*...*/)
    .setTitle(resources.getString(/*...*/))
    .setExtras(extras)
    .build()
    return MediaBrowserCompat.MediaItem(description, /* flags */)
    

Java

    Bundle extras = new Bundle();
    extras.putLong(EXTRA_IS_EXPLICIT, 1);
    extras.putInt(EXTRA_PLAY_COMPLETION_STATE, 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 de Long EXTRA_IS_EXPLICIT o EXTRA_IS_DOWNLOADED en el método MediaMetadata.Builder() de la mediaSession. No puedes mostrar el indicador EXTRA_PLAY_COMPLETION_STATE 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

    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(
        EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE)
      .putLong(
        EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE)
      .build())
    

Java

    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(
                EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE)
            .putLong(
                EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE)
            .build());
    

Con el fin de ayudar a un usuario a explorar el contenido, tu app puede permitirle examinar un grupo de resultados relacionados cada vez que realiza una búsqueda por voz. El SO Android Automotive y Android Auto muestran los resultados en la barra "Mostrar más resultados" de la interfaz.

Figura 4: Opción "Mostrar más resultados" en la pantalla del vehículo

Para mostrar los resultados de búsqueda explorables, debes crear una constante y, además, incluirla en el paquete de extras del método onGetRoot() del servicio.

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

Kotlin

    // Bundle extra indicating that onSearch() is supported
    val EXTRA_MEDIA_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED"

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

Java

    public static final String EXTRA_MEDIA_SEARCH_SUPPORTED =
       "android.media.browse.SEARCH_SUPPORTED";

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

Para comenzar a proporcionar resultados de búsqueda, anula el método onSearch() en el servicio de exploración multimedia. El SO Android Automotive y Android Auto redireccionan los términos de búsqueda del usuario a este método cada vez que un usuario invoca la prestación "Mostrar más resultados". Puedes organizar los resultados de búsqueda del método onSearch() con los elementos de título para aumentar la capacidad de exploración. 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<ArrayList<MediaItem>> result) {

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

      new AsyncTask<Void, Void, Void>() {
        ArrayList<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);
          }
          return 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

El SO Android Automotive y Android Auto envían comandos de control de reproducción mediante el 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 exploración multimedia, crea una instancia de MediaSessionCompat y, luego, registra la sesión multimedia llamando a setSessionToken().

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 para manejar las solicitudes de control de reproducción. Con el fin de crear este objeto de devolución de llamada, puedes proporcionar una implementación de la clase MediaSessionCompat.Callback para tu app. En la siguiente sección, se analiza cómo implementar este objeto.

Implementa comandos de reproducción

Cuando un usuario solicita la reproducción de un elemento multimedia desde la app, el SO Android Automotive y Android Auto usan la clase MediaSessionCompat.Callback del objeto MediaSessionCompat de la app que obtuvieron del servicio de exploración multimedia. Cuando un usuario quiere controlar la reproducción de contenido, por ejemplo, detener la reproducción o bien omitir la siguiente pista, el SO Android Automotive y Android Auto invocan 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 que admite.

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 de inmediato después del inicio. Debes implementar este método en tu app multimedia.
onPlay()
Se invoca si el usuario opta por reproducir contenido sin elegir un elemento específico. Tu app debe reproducir este contenido predeterminado. Si se detiene la reproducción con onPause(), tu app deberá reanudar la reproducción.

Nota: La app no debe iniciar automáticamente la reproducción de música cuando el SO Android Automotive o Android Auto se conectan a tu servicio de exploración 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 se pasa al ID que el servicio de exploración multimedia asignó al elemento multimedia en la 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 tu app no lo admite. Por ejemplo, si tu app reproduce una transmisión en vivo (como una emisión deportiva), no tendrá sentido implementar el método onSkipToNext() y, en su lugar, podrás usar la implementación predeterminada de onSkipToNext().

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). El SO Android Automotive y Android Auto envían de manera automática el contenido de audio al sistema del vehículo para que se reproduzca en las bocinas.

Para obtener más información acerca de cómo 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

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

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

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

El SO Android Automotive y Android Auto muestran los botones de cada acción habilitada, además de la cola de reproducción, si eliges crear una.

Reserva un espacio sin usar

El SO Android Automotive y Android Auto reservan espacio en la IU para las acciones ACTION_SKIP_TO_PREVIOUS y ACTION_SKIP_TO_NEXT. Además, Android Auto reserva espacio para la cola de reproducción. Si la app no admite alguna de estas funciones, el SO Android Automotive y Android Auto usan el espacio para mostrar las acciones personalizadas que creas.

Si no quieres rellenar esos espacios con acciones personalizadas, puedes reservarlos, y el SO Android Automotive y Android Auto dejarán el espacio en blanco cada vez que la app no admita la función correspondiente. Para ello, llama al método setExtras() con un paquete de extras que contenga las constantes que corresponden a cada una de las funciones reservadas. Establece cada una de las constantes para las que quieres reservar espacio en true.

En el siguiente fragmento de código, se muestran las constantes que puedes usar para reservar el espacio sin usar:

Kotlin

    // Use these extras to show the transport control buttons for the corresponding actions,
    // even when they are not enabled in the PlaybackState.
    private const val SLOT_RESERVATION_SKIP_TO_NEXT =
            "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"
    private const val SLOT_RESERVATION_SKIP_TO_PREV =
            "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"
    private const val SLOT_RESERVATION_QUEUE =
            "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE"
    

Java

    // Use these extras to show the transport control buttons for the corresponding actions,
    // even when they are not enabled in the PlaybackState.
    private static final String SLOT_RESERVATION_SKIP_TO_NEXT =
        "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
    private static final String SLOT_RESERVATION_SKIP_TO_PREV =
        "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
    private static final String SLOT_RESERVATION_QUEUE =
        "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";
    

Establece el PlaybackState inicial

A medida que el SO Android Automotive y Android Auto se comunican con tu servicio de exploración multimedia, la sesión multimedia se comunica con el estado de la reproducción de contenido mediante PlaybackState. La app no debe iniciar automáticamente la reproducción de música cuando el SO Android Automotive o Android Auto se conectan al servicio de exploración multimedia. En su lugar, confía en que el SO Android Automotive y Android Auto reanudarán o iniciarán la reproducción según el estado del vehículo o las acciones del usuario.

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

Agrega acciones de reproducción personalizadas

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

Puedes agregar esas acciones 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 Universal Android Music Player en GitHub.

Después de crear la acción personalizada, la sesión multimedia puede responder a la acción anulando el 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 Universal Android Music Player en GitHub.

Íconos para acciones personalizadas

Cada acción personalizada que creas requiere un recurso de ícono. Las apps de vehículos se pueden ejecutar en diferentes tamaños y densidades de pantallas, de modo que los íconos que proporcionas deben ser elementos de diseño de vectores. Un elemento de diseño de vector te permite escalar 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.

Proporciona un estilo de ícono alternativo para las acciones inhabilitadas

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

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

Cómo admitir acciones de voz

Tu app multimedia 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 tu app ya está ejecutando un elemento multimedia, el usuario podrá decir "Reproducir [elemento]" para indicar a la app que reproduzca un elemento diferente sin necesidad de mirar ni tocar la pantalla del vehículo.

Declara la compatibilidad con las acciones de voz

En el siguiente fragmento de código, se muestra cómo declarar la compatibilidad con las acciones de voz en los archivos de manifiesto de la app. 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.

<activity>
        <intent-filter>
            <action android:name=
                 "android.media.action.MEDIA_PLAY_FROM_SEARCH" />
            <category android:name=
                 "android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
    

Analiza las búsquedas por voz

Cuando un usuario busca un elemento multimedia específico, como "Reproducir jazz en [nombre de la app]" o "Escuchar [título de canción]", el método de devolución de llamada onPlayFromSearch() recibe los resultados de la búsqueda por voz en el parámetro de consulta y un paquete de extras.

Para que tu app analice la búsqueda por voz e inicie la reproducción, sigue estos pasos:

  1. Usa el paquete de extras y la string de búsqueda que muestra la búsqueda para filtrar los resultados.
  2. Crea una cola de reproducción a partir de los resultados.
  3. Reproduce el elemento multimedia más relevante de los resultados.

El método onPlayFromSearch() lleva un parámetro de extras con información más detallada de la búsqueda. Estos extras te ayudan a encontrar el contenido de audio en tu app para su reproducción. Si los resultados de la búsqueda no pueden proporcionar estos datos, puedes implementar la lógica para analizar los datos de búsqueda sin procesar y reproducir las pistas correspondientes.

El SO Android Automotive y Android Auto admiten los siguientes extras:

En el siguiente fragmento de código, se muestra cómo anular el método onPlayFromSearch() en tu implementación de MediaSession.Callback para analizar la búsqueda por voz e iniciar la reproducción:

Kotlin

    override fun onPlayFromSearch(query: String?, extras: Bundle?) {
        if (query.isNullOrEmpty()) {
            // The user provided generic string e.g. 'Play music'
            // Build appropriate playlist queue
        } else {
            // Build a queue based on songs that match "query" or "extras" param
            val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS)
            if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) {
                isArtistFocus = true
                artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
            } else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) {
                isAlbumFocus = true
                album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
            }

            // Implement additional "extras" param filtering
        }

        // Implement your logic to retrieve the queue
        var result: String? = when {
            isArtistFocus -> artist?.also {
                searchMusicByArtist(it)
            }
            isAlbumFocus -> album?.also {
                searchMusicByAlbum(it)
            }
            else -> null
        }
        result = result ?: run {
            // No focus found, search by query for song title
            query?.also {
                searchMusicBySongTitle(it)
            }
        }

        if (result?.isNotEmpty() == true) {
            // Immediately start playing from the beginning of the search results
            // Implement your logic to start playing music
            playMusic(result)
        } else {
            // Handle no queue found. Stop playing if the app
            // is currently playing a song
        }
    }
    

Java

    @Override
    public void onPlayFromSearch(String query, Bundle extras) {
        if (TextUtils.isEmpty(query)) {
            // The user provided generic string e.g. 'Play music'
            // Build appropriate playlist queue
        } else {
            // Build a queue based on songs that match "query" or "extras" param
            String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS);
            if (TextUtils.equals(mediaFocus,
                    MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
                isArtistFocus = true;
                artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST);
            } else if (TextUtils.equals(mediaFocus,
                    MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
                isAlbumFocus = true;
                album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM);
            }

            // Implement additional "extras" param filtering
        }

        // Implement your logic to retrieve the queue
        if (isArtistFocus) {
            result = searchMusicByArtist(artist);
        } else if (isAlbumFocus) {
            result = searchMusicByAlbum(album);
        }

        if (result == null) {
            // No focus found, search by query for song title
            result = searchMusicBySongTitle(query);
        }

        if (result != null && !result.isEmpty()) {
            // Immediately start playing from the beginning of the search results
            // Implement your logic to start playing music
            playMusic(result);
        } else {
            // Handle no queue found. Stop playing if the app
            // is currently playing a song
        }
    }
    

Consulta el ejemplo del Universal Media Player, que incluye más detalles sobre cómo implementar la búsqueda por voz para reproducir contenido de audio en tu app.

Controla las búsquedas vacías

Cuando un usuario dice "Reproducir música en [nombre de tu app]", el SO Android Automotive o Android Auto intentan iniciar la app y reproducir audio llamando al método onPlayFromSearch() de la app. Sin embargo, como el usuario no indicó el nombre de un elemento multimedia, el método onPlayFromSearch() recibe un parámetro de búsqueda vacío. En estos casos, la app debe responder de inmediato reproduciendo audio, por ejemplo, una canción de la lista de reproducción más reciente o de una cola aleatoria.

Implementa acciones de reproducción por voz

Para proporcionar una experiencia manos libres mientras los usuarios conducen y escuchan contenido multimedia, la app debe permitir a los usuarios controlar la reproducción de contenido con las acciones por voz. Cuando los usuarios dicen comandos como "Siguiente canción", "Pausar música" o "Reanudar música", el sistema activa el método de devolución de llamada correspondiente, donde se implementa la acción de control de reproducción.

Para proporcionar acciones de reproducción habilitadas por voz, primero habilita los controles de hardware; para ello, configura estas marcas en el objeto MediaSession de tu app:

Kotlin

    session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
            or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
    )
    

Java

    session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
        MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
    

Después de configurar las marcas, implementa los métodos de devolución de llamada con los controles de reproducción que admites en tu app. El SO Android Automotive y Android Auto admiten las siguientes acciones de reproducción habilitadas por voz:

Frase de ejemplo Método de devolución de llamada
"Siguiente canción" onSkipToNext()
"Canción anterior" onSkipToPrevious()
"Pausar música" onPause()
"Detener música" onStop()
"Reanudar música" onPlay()

Consulta el ejemplo de Universal Media Player, que incluye más detalles sobre cómo implementar acciones de reproducción habilitadas por voz en tu app.

Implementa actividades de configuración y acceso para el SO Android Automotive

Además del servicio de exploración multimedia, también puedes proporcionar actividades de configuración y acceso optimizadas para vehículos en tu app con el SO Android Automotive. Estas actividades te permiten proporcionar una funcionalidad de la app que no está incluida en las API de Android Media.

Agrega una actividad de configuración

Puedes agregar una actividad de configuración optimizada para vehículos, de modo que los usuarios puedan configurar los ajustes de la app en su vehículo. La actividad de configuración también puede proporcionar otros flujos de trabajo, como acceso o salida de la cuenta de un usuario o cambio de una cuenta de usuario a otra.

Flujos de trabajo de la actividad de configuración

Tu actividad de configuración puede proporcionar al usuario diferentes flujos de trabajo. En la siguiente imagen, se muestra cómo un usuario interactúa con la actividad de configuración en el SO Android Automotive:

Flujos de trabajo para una actividad de configuración

Figura 6: Diagrama de flujos de trabajo de una actividad de configuración

Declara una actividad de configuración

Debes declarar la actividad de configuración en el archivo de manifiesto de la app, como se muestra en el siguiente fragmento de código:

<application>
        ...
        <activity android:name=".AppSettingsActivity"
                  android:exported="true"
                  android:theme="@style/SettingsActivity"
                  android:label="@string/app_settings_activity_title">
            <intent-filter>
                <action android:name="android.intent.action.APPLICATION_PREFERENCES"/>
            </intent-filter>
        </activity>
        ...
    <application>
    

Implementa tu actividad de configuración

Cuando un usuario inicia tu app, el SO Android Automotive detecta la actividad de configuración que declaraste y muestra una prestación. El usuario puede presionar o seleccionar esta prestación usando la pantalla de su vehículo para navegar por la actividad. El SO Android Automotive envía el intent ACTION_APPLICATION_PREFERENCES, que le indica a la app que inicie la actividad de configuración.

Agrega una actividad de acceso

Si tu app requiere que los usuarios accedan antes de usarla, puedes agregar una actividad de acceso optimizada para vehículos que controle el acceso y la salida de tu app. También puedes agregar flujos de trabajo de acceso y salida a una actividad de configuración, pero debes usar una actividad de acceso dedicada si la app no se puede usar hasta que el usuario accede.

Flujo de trabajo de la actividad de acceso

En la siguiente imagen, se muestra cómo un usuario interactúa con la actividad de acceso usando el SO Android Automotive:

Flujos de trabajo para una actividad de acceso

Figura 7: Diagrama de flujos de trabajo para una actividad de acceso

Solicita el acceso cuando se inicie la app

Para requerir que un usuario acceda mediante la actividad correspondiente antes de usar la app, debes hacer lo siguiente en tu servicio de exploración multimedia:

  1. Establece el PlaybackState de la sesión multimedia en STATE_ERROR usando el método setState(). De este modo, se indica al SO Android Automotive que no se podrán llevar a cabo otras operaciones hasta que se resuelva el error.
  2. Establece el código de error PlaybackState de la sesión multimedia en ERROR_CODE_AUTHENTICATION_EXPIRED. De este modo, se indica al SO Android Automotive que el usuario debe autenticarse.
  3. Establece el mensaje de error PlaybackState de la sesión multimedia usando el método setErrorMessage(). Como este mensaje de error se muestra al usuario, el mensaje se debe localizar según la configuración regional actual del usuario.
  4. Establece los extras de PlaybackState de la sesión multimedia con el método setExtras(). Incluye las siguientes dos claves:

    • android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: Se trata de una string que se muestra en el botón que inicia el flujo de trabajo de acceso. Como esta string se muestra al usuario, se debe localizar según la configuración regional actual del usuario.
    • android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: Se trata de una instancia de PendingIntent que dirige al usuario a la actividad de acceso cuando presiona el botón al que hace referencia android.media.extras.ERROR_RESOLUTION_ACTION_LABEL.

En el siguiente fragmento de código, se muestra cómo la app puede requerir el acceso del usuario antes de que pueda usarla:

Kotlin

    val signInIntent = Intent(this, SignInActivity::class.java)
    val signInActivityPendingIntent = PendingIntent.getActivity(this, 0,
        signInIntent, 0)
    val extras = Bundle().apply {
        putString(
            "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL",
            "Sign in"
        )
        putParcelable(
            "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT",
            signInActivityPendingIntent
        )
    }

    val playbackState = PlaybackStateCompat.Builder()
            .setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
            .setErrorMessage(
                PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
                "Authentication required"
            )
            .setExtras(extras)
            .build()
    mediaSession.setPlaybackState(playbackState)
    

Java

    Intent signInIntent = new Intent(this, SignInActivity.class);
    PendingIntent signInActivityPendingIntent = PendingIntent.getActivity(this, 0,
        signInIntent, 0);
    Bundle extras = new Bundle();
    extras.putString(
        "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL",
        "Sign in");
    extras.putParcelable(
        "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT",
        signInActivityPendingIntent);

    PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
        .setErrorMessage(
                PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
                "Authentication required"
        )
        .setExtras(extras)
        .build();
    mediaSession.setPlaybackState(playbackState);
    

Una vez que el usuario se haya autenticado correctamente, la app deberá establecer PlaybackState de nuevo en un estado diferente de STATE_ERROR y, luego, llevar al usuario de nuevo al SO Android Automotive llamando al método finish() de la actividad.

Implementa tu actividad de acceso

Google ofrece una variedad de herramientas de identidad que puedes usar para ayudar a los usuarios a acceder a tu app en sus vehículos. Algunas herramientas, como Firebase Authentication, proporcionan herramientas de pila completa que pueden ayudar a crear experiencias de autenticación personalizadas. Otras herramientas aprovechan las credenciales existentes de los usuarios o de otras tecnologías para ayudarlo a crear experiencias de acceso sin problemas para los usuarios.

Te recomendamos las siguientes herramientas como ayuda con el fin de crear experiencia de acceso más simple para los usuarios que hayan accedido anteriormente desde otro dispositivo:

  • Acceso con Google: Si ya implementaste el acceso con Google para otros dispositivos (como la app de tu teléfono), también debes implementar el acceso con Google para tu app con SO Android Automotive con el fin de admitir los usuarios existentes de acceso con Google.
  • Autocompletar con Google: Si los usuarios optaron por Autocompletar con Google en sus otros dispositivos Android, sus credenciales se guardan en el administrador de contraseñas de Google. Entonces, cuando acceden a tu app con SO Android Automotive, la función Autocompletar con Google sugiere las credenciales guardadas relevantes. El uso de Autocompletar con Google no requiere ningún esfuerzo de desarrollo de aplicación; sin embargo, los desarrolladores de aplicaciones deben optimizar sus apps para obtener resultados de mejor calidad. La función Autocompletar con Google es compatible con todos los dispositivos que ejecutan Android Oreo 8.0 (API nivel 26) o posterior (incluido el SO Android Automotive).

Controla las acciones con acceso protegido

Algunas apps permiten que los usuarios realicen determinadas acciones de forma anónima, pero requieren que accedan para hacer otras. Por ejemplo, un usuario puede reproducir música en una app sin necesidad de acceder, pero debe hacerlo para poder omitir una canción.

En este caso, cuando el usuario intenta realizar la acción restringida (omitir una canción), la app puede sugerir que el usuario se autentique mediante la ejecución de un error no grave. Cuando se usa un error no grave, el sistema muestra el mensaje al usuario sin interrumpir la reproducción del elemento multimedia actual. Para implementar el control de errores no graves, completa los siguientes pasos:

  1. Establece el errorCode para el PlaybackState de la sesión multimedia en ERROR_CODE_AUTHENTICATION_EXPIRED. De este modo, se indica al SO Android Automotive que el usuario debe autenticarse.
  2. Mantén el state del PlaybackState de la sesión multimedia tal como está, no lo establezcas en STATE_ERROR. De este modo, se indica al sistema que el error no es grave.
  3. Establece los extras de PlaybackState de la sesión multimedia con el método setExtras(). Incluye las siguientes dos claves:

    • android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: Se trata de una string que se muestra en el botón que inicia el flujo de trabajo de acceso. Como esta string se muestra al usuario, se debe localizar según la configuración regional actual del usuario.
    • android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: Se trata de una instancia de PendingIntent que dirige al usuario a la actividad de acceso cuando presiona el botón al que hace referencia android.media.extras.ERROR_RESOLUTION_ACTION_LABEL.
  4. Conserva el resto del estado PlaybackState de la sesión multimedia tal como está. De este modo, se permite que la reproducción del elemento multimedia actual continúe mientras el usuario decide si accederá.

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 multimedia 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). Incluso una alarma programada por el usuario desde la app multimedia no debería iniciar la reproducción de música en las bocinas del vehículo. Para 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 modo de vehículo llamando al método getCurrentModeType().

Si el teléfono del usuario está en modo de vehículo, las apps multimedia 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

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 manera 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 multimedia cambia de reproducir música a ejecutar un anuncio, mostrar una notificación al usuario es una distracción (y es innecesario). Para impedir que Android Auto muestre una notificación en este caso, debes establecer la clave de metadatos multimedia android.media.metadata.ADVERTISEMENT en 1, como se muestra en el siguiente fragmento de código:

Kotlin

    const val EXTRA_METADATA_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT"
    ...
    override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) {
        MediaMetadataCompat.Builder().apply {
            // ...
            if (isAd(mediaId)) {
                putLong(EXTRA_METADATA_ADVERTISEMENT, 1)
            }
            // ...
            mediaSession.setMetadata(build())
        }
    }
    

Java

    public static final String EXTRA_METADATA_ADVERTISEMENT =
        "android.media.metadata.ADVERTISEMENT";

    @Override
    public void onPlayFromMediaId(String mediaId, Bundle extras) {
        MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
        // ...
        if (isAd(mediaId)) {
            builder.putLong(EXTRA_METADATA_ADVERTISEMENT, 1);
        }
        // ...
        mediaSession.setMetadata(builder.build());
    }
    

Cómo controlar los errores generales

Cuando la app experimenta un error, debe establecer el estado de reproducción en STATE_ERROR y proporcionar un mensaje de error utilizando 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. El SO Android Automotive y Android Auto luego 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á proporcionar esa información al usuario. Por ejemplo, el mensaje de error deberá decir "Acceder a [nombre de tu app]" en lugar de "Acceder".

Otros recursos