O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Criar apps de música para carros

O Android Auto e o Android Automotive OS ajudam a levar o conteúdo do seu app de música para os usuários no carro. Um app de música para carros precisa fornecer um serviço de navegador de mídia para que o Android Auto e o Android Automotive OS ou outro app com um navegador de mídia possam descobrir e exibir o conteúdo.

Este guia presume que você já tenha um app de música que reproduz áudio em um smartphone e que ele seja compatível com a arquitetura de app de música do Android.

Este guia descreve os componentes necessários de uma MediaBrowserService e MediaSession que seu app precisa para funcionar no Android Auto ou no Android Automotive OS. Depois de concluir a infraestrutura de mídia principal, você pode adicionar compatibilidade com o Android Auto e adicionar compatibilidade com o Android Automotive OS ao seu app de música.

Antes de começar

  1. Revise a documentação da API Android Media.
  2. Consulte as diretrizes de design de app para o Android Automotive OS e as diretrizes de design de app para o Android Auto (links em inglês).
  3. Revise os principais termos e conceitos listados nesta seção.

Principais termos e conceitos

Serviço de navegador de mídia
Um serviço do Android implementado pelo seu app de música de acordo com a API MediaBrowserServiceCompat. O app usa esse serviço para expor esse conteúdo.
Navegador de mídia
Uma API usada por apps de música para descobrir serviços de navegador de mídia e exibir o conteúdo deles. O Android Auto e o Android Automotive OS usam um navegador de mídia para encontrar o serviço de navegação de mídia do seu app.
Item de mídia

O navegador de mídia organiza o conteúdo em uma árvore de objetos MediaItem. Um item de mídia pode ter apenas uma ou as duas sinalizações a seguir:

  • Reproduzível: esta sinalização indica que o item é uma folha na árvore de conteúdo. O item representa um único fluxo de som, como uma música em um álbum, um capítulo em um audiolivro ou um episódio de um podcast.
  • Visualizável: esta sinalização indica que o item é um nó na árvore de conteúdo e tem filhos. Por exemplo, o item representa um álbum e seus filhos são as músicas desse álbum.

Um item de mídia que seja tanto navegável quanto reproduzível funciona como uma playlist. Você pode selecionar o item para reproduzir todos os filhos ou navegar por eles.

Otimizado para veículos

Uma atividade de um app para Android Automotive OS que segue as diretrizes de design do Android Automotive OS (link em inglês). A interface para essas atividades não é desenhada pelo Android Automotive OS. Portanto, verifique se seu app cumpre as diretrizes de design. Normalmente, isso inclui áreas de toque e tamanhos de fonte maiores, compatibilidade com os modos diurno e noturno e taxas de contraste mais altas.

As interfaces do usuário otimizadas para veículos só podem ser exibidas quando as Restrições de experiência do usuário veicular (CUXRs, na sigla em inglês) não estão em vigor, porque essas interfaces podem exigir atenção ou interação prolongada do usuário. As CUXRs não estão em vigor quando o carro está parado ou estacionado, mas sempre estão em vigor quando o carro está em movimento.

Não é necessário criar atividades para o Android Auto, porque ele projeta a própria interface otimizada para veículos usando as informações do serviço de navegação de mídia.

Configurar os arquivos de manifesto do seu app

Antes de criar o serviço de navegador de mídia, é necessário configurar os arquivos de manifesto do seu app.

Declarar o serviço de navegação de mídia

Tanto o Android Auto quanto o Android Automotive OS se conectam ao seu app por meio do serviço de navegador de mídia para procurar itens de mídia. Declare o serviço de navegador de mídia no manifesto para permitir que o Android Automotive OS e o Android Auto encontrem o serviço e se conectem ao seu app.

O snippet de código a seguir mostra como declarar o serviço de navegação de mídia no seu manifesto. É preciso incluir esse código no arquivo de manifesto do seu módulo Android Automotive OS e no arquivo de manifesto do seu app para smartphones.

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

Especificar um ícone de app

Você precisa especificar um ícone de app que o Android Auto e o Android Automotive OS possam usar para representar seu app na IU do sistema.

Especifique um ícone usado para representar seu app usando a seguinte declaração de manifesto:

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

Criar o serviço de navegador de mídia

Crie um serviço de navegador de mídia estendendo a classe MediaBrowserServiceCompat. O Android Auto e o Android Automotive OS podem usar o serviço para as seguintes ações:

  • Navegar na hierarquia de conteúdo do seu app para apresentar um menu ao usuário.
  • Receber o token para o objeto MediaSessionCompat do seu app para controlar a reprodução de áudio.

Fluxo de trabalho do serviço de navegador de mídia

Esta seção descreve como o Android Automotive OS e o Android Auto interagem com o serviço de navegação de mídia durante um fluxo de trabalho comum do usuário.

  1. Um usuário inicia seu app no Android Automotive OS ou no Android Auto.
  2. O Android Automotive OS ou o Android Auto entra em contato com o serviço de navegação de mídia do seu app usando o método onCreate(). Na implementação desse método onCreate(), você precisa criar e registrar um objeto MediaSessionCompat e o respectivo objeto de callback.
  3. O Android Automotive OS ou o Android Auto chama o método onGetRoot() do serviço para receber o item de mídia raiz na hierarquia de conteúdo. O item de mídia raiz não é exibido, mas é usado para recuperar mais conteúdo do seu app.
  4. O Android Automotive OS ou Android Auto chama o método onLoadChildren() do seu serviço para receber os filhos do item de mídia raiz. O Android Automotive OS e o Android Auto exibem esses itens de mídia como o nível superior dos itens de conteúdo. Os itens de conteúdo de nível superior precisam ser navegáveis.
  5. Se o usuário selecionar um item de mídia navegável, o método onLoadChildren() será chamado novamente para recuperar os filhos do item de menu selecionado.
  6. Se o usuário selecionar um item de mídia reproduzível, o Android Automotive OS ou o Android Auto chamará o método de callback da sessão de mídia adequada para executar essa ação.
  7. Se for compatível com seu app, o usuário também poderá pesquisar seu conteúdo. Nesse caso, o Android Automotive OS ou o Android Auto chama o método onSearch() do serviço.

Criar sua hierarquia de conteúdo

O Android Auto e o Android Automotive OS chamam o serviço de navegação de mídia do seu app para descobrir qual conteúdo está disponível. É necessário implementar dois métodos no serviço do navegador de mídia para oferecer compatibilidade com esse recurso: onGetRoot() e onLoadChildren().

Implementar onGetRoot

O método onGetRoot() do serviço retorna informações sobre o nó raiz da hierarquia de conteúdo. O Android Auto e o Android Automotive OS usam esse nó raiz para solicitar o restante do seu conteúdo usando o método onLoadChildren().

O snippet de código a seguir mostra uma implementação simples do 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 ver um exemplo mais detalhado, consulte o método onGetRoot() no app de amostra Universal Android Music Player no GitHub (link em inglês).

Adicionar validação de pacote para onGetRoot()

Quando uma chamada é feita para o método onGetRoot() do seu serviço, o pacote de chamada transmite informações de identificação para o serviço. Ele pode usar essas informações para decidir se o pacote pode acessar seu conteúdo. Por exemplo, é possível restringir o acesso ao conteúdo do app a uma lista de pacotes aprovados comparando o clientPackageName à lista de permissões e verificando o certificado usado para assinar o APK do pacote. Se não for possível verificar o pacote, retorne null para negar acesso ao seu conteúdo.

Para fornecer acesso ao seu conteúdo para os apps do sistema (como o Android Auto e o Android Automotive OS), seu serviço precisa sempre retornar um valor BrowserRoot não nulo quando esses apps do sistema chamarem o método onGetRoot().

O snippet de código a seguir mostra como seu serviço pode validar que o pacote de chamada é um app do 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 snippet de código é um trecho da classe PackageValidator no app de amostra Android Universal Music Player no GitHub (link em inglês). Veja essa classe para um exemplo mais detalhado de como implementar a validação de pacote para o método onGetRoot().

Implementar onLoadChildren()

Depois de receber seu objeto de nó raiz, o Android Auto e o Android Automotive OS criam um menu de nível superior chamando onLoadChildren() no objeto de nó raiz para receber os filhos. Os apps clientes criam submenus chamando esse mesmo método usando objetos de um nó filho.

Cada nó da sua hierarquia de conteúdo é representado por um objeto MediaBrowserCompat.MediaItem. Cada um desses itens de mídia é identificado por uma string de ID exclusivo. Os apps clientes tratam essas strings de código como tokens opacos. Quando um app cliente quer navegar para um submenu ou abrir um item de mídia, ele transmite o token. Seu app é responsável por associar o token ao respectivo item de mídia.

Observação: o Android Auto e o Android Automotive OS têm limites rígidos para a quantidade de itens de mídia que eles podem exibir em cada nível do menu. Esses limites minimizam as distrações para os motoristas e ajudam a operar o app com comandos de voz. Para mais informações, consulte Navegar nos detalhes de conteúdo e Gaveta de apps Android Auto (links em inglês).

O snippet de código a seguir mostra uma implementação simples do 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 um exemplo completo, consulte o método onLoadChildren() no app de amostra Universal Android Music Player no GitHub (link em inglês).

Aplicar estilos de conteúdo

Depois de criar sua hierarquia de conteúdo usando itens navegáveis ou reproduzíveis, você pode aplicar estilos de conteúdo que determinam como esses itens serão exibidos no carro.

Você pode usar os seguintes estilos de conteúdo:

Itens em lista

Esse estilo de conteúdo dá prioridade a títulos e metadados, em vez de imagens.

Itens em grade

Esse estilo de conteúdo dá prioridade a imagens, em vez de títulos e metadados.

Definir estilos de conteúdo padrão

Você pode definir padrões globais para a forma como seus itens de mídia são exibidos incluindo certas constantes no pacote de extras BrowserRoot do método onGetRoot() do seu serviço. O Android Auto e o Android Automotive OS leem o pacote de extras associado a cada item na árvore de navegação e procuram essas constantes para determinar o estilo adequado.

Use o código a seguir para declarar essas constantes no seu 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;
    

Depois de declarar essas constantes, elas precisam ser incluídas no pacote de extras do método onGetRoot() do seu serviço para definir o estilo de conteúdo padrão. O snippet de código a seguir mostra como configurar o estilo de conteúdo padrão para itens navegáveis como grades e para itens reproduzíveis como 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);
    }
    

Definir estilos de conteúdo por item

A API Content Style permite a modificação do estilo de conteúdo padrão para os filhos de qualquer item de mídia navegável. Para modificar o padrão, crie um pacote de extras no MediaDescription de um item de mídia.

O snippet de código a seguir mostra como criar um MediaItem navegável que modifica o estilo de conteúdo padrão:

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)
        mediaDescriptionBuilder.setExtras(extras)
        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);
       mediaDescriptionBuilder.setExtras(extras);
       return new MediaBrowser.MediaItem(
           mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
    }
    

Agrupar itens usando dicas de título

Para agrupar itens de mídia relacionados, use uma dica por item. Cada item de mídia do grupo precisa declarar um pacote de extras na MediaDescription que usa uma string idêntica. Essa string é usada como o título do grupo e pode ser localizada.

O snippet de código a seguir mostra como criar um MediaItem com um título de subgrupo de "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")
        mediaDescriptionBuilder.setExtras(extras)
        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");
       mediaDescriptionBuilder.setExtras(extras);
       return new MediaBrowser.MediaItem(
           mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
    }
    

É necessário que o app transmita todos os itens de mídia que você quer agrupar como um bloco contíguo. Por exemplo, suponha que você queira exibir dois grupos de itens de mídia, "Songs" e "Albums" (nessa ordem), e seu app tenha transmitido cinco itens de mídia na seguinte ordem:

  1. Item de mídia A com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  2. Item de mídia B com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
  3. Item de mídia C com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  4. Item de mídia D com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  5. Item de mídia E com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")

Como os itens de mídia dos grupos "Songs" e "Albums" não são mantidos juntos em blocos contíguos, o Android Auto e o Android Automotive OS interpretariam isso na forma dos quatro grupos a seguir:

  • Grupo 1 chamado "Songs" e contendo o item de mídia A
  • Grupo 2 chamado "Albums" e contendo o item de mídia B
  • Grupo 3 chamado "Songs" e contendo itens de mídia C e D
  • Grupo 4 chamado "Albums" e contendo o item de mídia E

Para exibir esses itens em dois grupos, seu app deve transmitir os apps na seguinte ordem:

  1. Item de mídia A com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  2. Item de mídia C com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  3. Item de mídia D com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  4. Item de mídia B com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
  5. Item de mídia E com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")

Exibir mais indicadores de metadados

Figura 3. Visualização de reprodução com metadados que identificam a música e o artista

Você pode incluir mais indicadores de metadados para fornecer informações resumidas sobre o conteúdo da árvore de navegação de mídia e durante a reprodução. O Android Auto e o Android Automotive OS leem os extras associados a um item e na árvore de navegação e procuram certas constantes para determinar quais indicadores exibir. Durante a reprodução de mídia, o Android Auto e o Android Automotive OS leem os metadados da sessão de mídia e procuram certas constantes para determinar os indicadores a serem exibidos.

Use o código a seguir para declarar constantes do indicador de metadados no seu 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;
    

Depois de declarar essas constantes, você pode usá-las para exibir indicadores de metadados. Para exibir indicadores que aparecerão enquanto o usuário estiver navegando na árvore de navegação, crie um pacote de extras que inclua uma ou mais dessas constantes e transmita esse pacote para o método MediaDescription.Builder.setExtras().

O snippet de código a seguir mostra como exibir indicadores para um item de mídia explícito, parcialmente reproduzido:

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

Para exibir os indicadores de um item de mídia que está sendo reproduzido no momento, declare valores longos para EXTRA_IS_EXPLICIT ou EXTRA_IS_DOWNLOADED no método MediaMetadata.Builder() da sua mediaSession. Não é possível exibir o indicador EXTRA_PLAY_COMPLETION_STATE na visualização de reprodução.

O snippet de código a seguir mostra como indicar que a música atual na visualização de reprodução é explícita e foi salva:

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());
    

Para ajudar um usuário a navegar no conteúdo, seu app pode permitir que ele navegue em um grupo de resultados de pesquisa relacionados à consulta de pesquisa sempre que fizer uma pesquisa por voz. O Android Auto e o Android Automotive OS exibem esses resultados como uma barra "Mostrar mais resultados" na interface.

Figura 4. Opção "Mais resultados" para visualizar resultados de pesquisa relacionados

Para exibir resultados de pesquisa navegáveis, crie uma constante e a inclua no pacote de extras do método onGetRoot() do seu serviço.

O snippet de código a seguir mostra como ativar a compatibilidade no 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 começar a fornecer resultados de pesquisa, modifique o método onSearch() no seu serviço de navegação de mídia. O Android Auto e o Android Automotive OS encaminham os termos de pesquisa de um usuário para esse método sempre que ele chama a opção de uso "Mostrar mais resultados". Você pode organizar os resultados da pesquisa do método onSearch() do seu serviço usando itens de título para torná-los mais navegáveis. Por exemplo, se seu app toca música, você pode organizar os resultados da pesquisa por "Álbum", "Artista" e "Músicas".

O snippet de código a seguir mostra uma implementação simples do 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
    }
    

Ativar controle de reprodução

O Android Auto e o Android Automotive OS enviam comandos de controle de reprodução por meio do MediaSessionCompat do seu serviço. Registre uma sessão e implemente os métodos de callback associados.

Registrar uma sessão de mídia

No método onCreate() do seu serviço de navegação de mídia, crie um MediaSessionCompat e registre a sessão de mídia chamando setSessionToken().

O snippet de código a seguir mostra como criar e registrar uma sessão de mídia:

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());

        ...
    }
    

Ao criar o objeto de sessão de mídia, você define um objeto de callback usado para gerenciar solicitações de controle de reprodução. Crie esse objeto de callback fornecendo uma implementação da classe MediaSessionCompat.Callback para o app. A próxima seção discute como implementar esse objeto.

Implementar comandos de reprodução

Quando um usuário solicita a reprodução de um item de mídia no seu app, o Android Automotive OS e o Android Auto usam a classe MediaSessionCompat.Callback no objeto MediaSessionCompat recebido do serviço de navegação de mídia do app. Quando um usuário quer controlar a reprodução de conteúdo, como pausar a reprodução ou pular para a próxima faixa, o Android Auto e o Android Automotive OS invocam um dos métodos de callback do objeto.

Para gerenciar a reprodução de conteúdo, seu app precisa estender a classe abstrata MediaSessionCompat.Callback e implementar os métodos compatíveis.

Implemente todos os métodos de callback a seguir que estejam relacionados ao tipo de conteúdo que seu app oferece:

onPrepare()
Invocado quando a fonte de mídia é alterada. O Android Automotive OS também chama esse método imediatamente após a inicialização. Esse método precisa ser implementado pelo seu app de música.
onPlay()
Invocado se o usuário quer ativar a reprodução sem escolher um item específico. Seu app tocará o conteúdo padrão. Caso a reprodução tenha sido pausada com onPause(), seu app precisa retomá-la.

Observação: não é recomendado que seu app comece automaticamente a tocar músicas quando o Android Automotive OS ou o Android Auto se conectarem ao serviço de navegação de mídia. Para mais informações, consulte Configurar o estado inicial da reprodução.

onPlayFromMediaId()
Invocado quando o usuário escolhe reproduzir um item específico. O método recebe o ID que o serviço de navegação de mídia atribuiu ao item de mídia na sua hierarquia de conteúdo.
onPlayFromSearch()
Invocado quando o usuário escolhe reproduzir em uma consulta de pesquisa. O app precisa fazer uma escolha apropriada com base na string de pesquisa que foi transmitida.
onPause()
Invocado quando o usuário decide pausar a reprodução.
onSkipToNext()
Invocado quando o usuário decide pular para o próximo item.
onSkipToPrevious()
Invocado quando o usuário decide pular para o item anterior.
onStop()
Invocado quando o usuário decide parar a reprodução.

É necessário que seu app modifique esses métodos para oferecer qualquer funcionalidade desejada. Você não precisa implementar um método se ele não for compatível com seu app. Por exemplo, se o app fizer uma transmissão ao vivo (por exemplo, esportiva), a implementação do método onSkipToNext() não faz sentido, e você pode usar a implementação padrão de onSkipToNext().

Seu app não precisa de nenhuma lógica especial para reproduzir o conteúdo pelos alto-falantes do carro. Quando o app recebe uma solicitação para reproduzir conteúdo, ele precisa reproduzir o áudio como faz normalmente, por exemplo, reproduzindo o conteúdo pelos alto-falantes do smartphone ou fones de ouvido do usuário. O Android Auto e o Android Automotive OS enviam automaticamente o conteúdo de áudio ao sistema do carro para tocar nos alto-falantes do carro.

Para mais informações sobre como reproduzir conteúdo de áudio, consulte Reprodução de mídia, Gerenciar a reprodução de áudio e ExoPlayer.

Definir ações de reprodução padrão

O Android Auto e o Android Automotive OS exibem controles de reprodução com base nas ações ativadas no objeto PlaybackStateCompat.

Por padrão, seu app precisa ser compatível com as seguintes ações:

Além disso, você pode criar uma fila de reprodução que possa ser exibida para o usuário. Para fazer isso, você precisa chamar os métodos setQueue() e setQueueTitle(), ativar a ação ACTION_SKIP_TO_QUEUE_ITEM e definir o callback onSkipToQueueItem().

O Android Auto e o Android Automotive OS exibem botões para cada ação ativada, bem como a fila de reprodução, se você decidir criar uma.

Reservar espaço não utilizado

O Android Auto e o Android Automotive OS reservam espaço na IU para as ações ACTION_SKIP_TO_PREVIOUS e ACTION_SKIP_TO_NEXT. Além disso, o Android Auto reserva espaço para a fila de reprodução. Se o app não for compatível com uma dessas funções, o Android Auto e o Android Automotive OS usarão o espaço para exibir as ações personalizadas que você criar.

Se você não quiser preencher esses espaços com ações personalizadas, poderá reservá-los para que o Android Auto e o Android Automotive OS deixem o espaço em branco sempre que o app não for compatível com a função correspondente. Para fazer isso, chame o método setExtras() com um pacote de extras que contenha constantes correspondentes a cada uma das funções reservadas. Defina cada constante para a qual você quer reservar espaço como true.

O snippet de código a seguir mostra as constantes que podem ser usadas para reservar espaço não utilizado:

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 PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT =
            "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"
    private const val PLAYBACK_SLOT_RESERVATION_SKIP_TO_PREV =
            "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"
    private const val PLAYBACK_SLOT_RESERVATION_QUEUE =
            "android.media.playback.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 PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT =
        "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
    private static final String PLAYBACK_SLOT_RESERVATION_SKIP_TO_PREV =
        "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
    private static final String PLAYBACK_SLOT_RESERVATION_QUEUE =
        "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";
    

Configurar o PlaybackState inicial

À medida que o Android Auto e o Android Automotive OS se comunicam com o serviço de navegação de mídia, sua sessão de mídia informa o status da reprodução do conteúdo usando o PlaybackState. Não é recomendado que seu app comece automaticamente a tocar músicas quando o Android Automotive OS ou o Android Auto se conectarem ao seu serviço de navegação de mídia. Em vez disso, o Android Auto e o Android Automotive OS precisam retomar ou iniciar a reprodução com base no estado do carro ou nas ações do usuário.

Para isso, configure o PlaybackState inicial da sua sessão de mídia como STATE_STOPPED, STATE_PAUSED, STATE_NONE ou STATE_ERROR.

Adicionar ações de reprodução personalizadas

Você pode adicionar ações de reprodução personalizadas para exibir mais ações compatíveis com seu app de música. Se o espaço permitir (e não estiver reservado), o Android adicionará ações personalizadas aos controles de transporte. Caso contrário, as ações personalizadas serão exibidas no menu flutuante. As ações personalizadas são exibidas na ordem em que são adicionadas ao PlaybackState.

Adicione essas ações usando o método addCustomAction() da classe PlaybackStateCompat.Builder.

O snippet de código a seguir mostra como adicionar uma ação personalizada "Iniciar um canal de rádio":

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 ver um exemplo mais detalhado, consulte o método setCustomAction() no app de amostra Universal Android Music Player no GitHub (link em inglês).

Após criar sua ação personalizada, sua sessão de mídia poderá responder à ação modificando o método onCustomAction().

O snippet de código a seguir mostra como seu app pode responder a uma ação "Iniciar um canal de rádio":

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 ver um exemplo mais detalhado, consulte o método onCustomAction no app de amostra Universal Android Music Player no GitHub (link em inglês).

Ícones para ações personalizadas

Cada ação personalizada que você cria requer um recurso de ícone. Os apps de carros podem ser executados em diferentes tamanhos e densidades de tela. Portanto, os ícones que você fornece precisam ser drawables vetoriais. Um drawable vetorial permite dimensionar recursos sem perder os detalhes. Ele também facilita o alinhamento de bordas e cantos a limites de pixels em resoluções menores.

Oferecer estilo alternativo de ícone para ações desativadas

Para as situações em que uma ação personalizada não estiver disponível para o contexto atual, troque o ícone da ação personalizada por um ícone alternativo que mostre que a ação está desativada.

Figura 5. Exemplos de ícones personalizados de ações desativadas.

Oferecer compatibilidade com ações por voz

Seu app de música precisa ser compatível com ações por voz para ajudar a fornecer aos motoristas uma experiência segura e conveniente que minimize as distrações. Por exemplo, se o app já estiver reproduzindo um item de mídia, o usuário poderá dizer Reproduzir Bohemian Rhapsody para solicitar ao app a reprodução de um item diferente sem olhar ou tocar na tela do carro.

Para ver um exemplo mais detalhado de como implementar a pesquisa por voz para tocar conteúdo de áudio no app, consulte Google Assistente e apps de música.

Implementar salvaguardas de distração

Como o smartphone do usuário está conectado aos alto-falantes do carro enquanto o Android Auto é usado, tome outras precauções para ajudar a evitar a distração do motorista.

Detectar o modo carro

Os apps de música do Android Auto não devem começar a reproduzir áudio pelos alto-falantes do carro, a menos que o usuário inicie a reprodução conscientemente (por exemplo, pressionando play no app). Mesmo um alarme do seu app de música programado pelo usuário não pode começar a tocar música pelos alto-falantes do carro. Para atender a esse requisito, seu app precisa determinar se o smartphone está no modo carro antes de tocar qualquer áudio. O app pode verificar se o smartphone está no modo carro chamando o método getCurrentModeType().

Se o smartphone do usuário estiver no modo carro, os apps de música compatíveis com alarmes precisarão executar uma das seguintes ações:

  • Desativar o alarme.
  • Tocar o alarme em STREAM_ALARM e fornecer uma IU na tela do smartphone para desativar o alarme.

O snippet de código a seguir mostra como verificar se um app está sendo executado no modo carro:

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;
            }
      }
    

Gerenciar anúncios de mídia

Por padrão, o Android Auto exibe uma notificação quando os metadados de mídia são alterados durante uma sessão de reprodução de áudio. Quando um app de música muda da reprodução de música para a exibição de um anúncio, exibir uma notificação para o usuário pode causar distração e não é necessário. Para impedir que o Android Auto mostre uma notificação nesse caso, configure a chave de metadados de mídia android.media.metadata.ADVERTISEMENT como 1, conforme mostrado no snippet de código a seguir:

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());
    }
    

Gerenciar erros gerais

Quando o app apresentar um erro, você precisará configurar o estado de reprodução como STATE_ERROR e fornecer uma mensagem de erro usando o método setErrorMessage(). As mensagens de erro precisarão ser voltadas para o usuário e traduzidas para a localidade dele. O Android Auto e o Android Automotive OS poderão, então, exibir a mensagem de erro para o usuário.

Para saber mais sobre estados de erro, consulte Trabalhar com sessões de mídia: estados e erros.

Se um usuário do Android Auto precisar abrir seu app para smartphones para resolver um erro, sua mensagem precisará fornecer essa informação a ele. Por exemplo, sua mensagem de erro diria "Faça login no [nome do seu app]", em vez de "Faça login".

Outros recursos