Criar apps de mídia Android para carros

O Android Automotive OS e o Android Auto ajudam a levar o conteúdo do seu app de mídia para os usuários no carro. Para ter uma visão geral de como o Android permite experiências com apps no trânsito, consulte Visão geral do Android para carros.

Este guia presume que você já tenha um app de mídia para smartphones. Este guia descreve como criar um app para o Android Automotive OS e como estender seu app para smartphones para o Android Auto.

Antes de começar

Antes de começar a criar seu app, siga as etapas descritas em Primeiros passos com o Android para carros e depois consulte as informações desta seção.

Principais termos e conceitos

Serviço de navegação de mídia
Um serviço do Android implementado pelo seu app de mídia de acordo com a API MediaBrowseServiceCompat. Seu app usa esse serviço para expor o conteúdo da navegação de mídia ao Android Automotive OS e ao Android Auto.
Navegação de mídia
Uma API usada por apps de mídia para expor conteúdo ao Android Automotive OS e ao Android Auto.
Item de mídia
Um único objeto MediaBrowserCompat.MediaItem na árvore de navegação de mídia. Os itens de mídia podem ser de um destes tipos:
  • Reproduzível: esses itens representam streams de som reais, como músicas de um álbum, capítulos de um livro ou episódios de um podcast.
  • Navegável: esses itens organizam itens de mídia reproduzíveis em grupos. Por exemplo, você pode agrupar capítulos em um livro, músicas em um álbum ou episódios em um podcast.

Observação: um item de mídia que seja tanto navegável quanto reproduzível é tratado como reproduzível.

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

É necessário configurar os arquivos de manifesto do seu app para indicar que ele está disponível para o Android Automotive OS e que o app para smartphones é compatível com serviços de mídia do Android Auto.

Declarar compatibilidade com o Android Automotive OS

O app que você distribui para o Android Automotive OS precisa ser separado do app para smartphones. Recomendamos o uso de módulos e de um Android App Bundle para ajudar a reutilizar o código, criar e lançar seu app com facilidade. Adicione a seguinte entrada ao arquivo de manifesto no módulo do Android Automotive OS para indicar que o código do módulo está restrito a esse sistema operacional:

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

Declarar compatibilidade de mídia com o Android Auto

O Android Auto usa seu app para smartphones para fornecer uma experiência de direção otimizada aos usuários. Use a seguinte entrada de manifesto para declarar que seu app para smartphones é compatível com o Android Auto:

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

Essa entrada de manifesto se refere a um arquivo XML que declara com quais recursos automotivos seu app é compatível. Para indicar que você tem um app de mídia, adicione um arquivo XML denominado automotive_app_desc.xml ao diretório res/xml/ do seu projeto. Esse arquivo precisa incluir o seguinte conteúdo:

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

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

Tanto o Android Automotive OS quanto o Android Auto se conectam ao seu app por meio de um serviço de navegador de mídia para procurar itens de mídia. Declare o serviço de navegação de mídia no manifesto para permitir que o Android Automotive OS e o Android Auto descubram 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

O Android Automotive OS e o Android Auto exibem o ícone do seu app para os usuários em diferentes locais, à medida que eles usam o app e interagem com o carro. Por exemplo, se um usuário tiver um app de navegação em execução quando uma música terminar e outra começar, o usuário verá uma notificação com o ícone do seu app. O Android Auto e o Android Automotive OS também exibem o ícone do seu app em outros locais, conforme o usuário navega pelo conteúdo de mídia.

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 um serviço de navegação de mídia

Crie um serviço de navegação de mídia estendendo a classe MediaBrowserServiceCompat. O Android Automotive OS e o Android Auto podem usar seu 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 sua implementação do método onCreate(), crie e registre 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() do serviço 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.

Como os usuários navegam nos apps de mídia

Para ajudar os usuários a navegar rapidamente pelo conteúdo do seu app, o Android Auto inclui um recurso de navegação que permite aos usuários selecionarem uma letra em um teclado na tela. Uma lista de itens que começam com essa letra é exibida na lista de gavetas atual. Isso funciona em conteúdo classificado e não classificado e está disponível apenas em inglês no momento.

Figura 1. Seletor alfabético na tela do carro.

Figura 2. Visualização em lista alfabética na tela do carro.

Criar sua hierarquia de conteúdo

O Android Automotive OS e o Android Auto chamam o serviço de navegação de mídia do seu app para descobrir o conteúdo disponível. É necessário implementar dois métodos no serviço do navegador 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 Automotive OS e o Android Auto 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 desse método, consulte o método onGetRoot() do app de exemplo 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, você pode restringir o acesso ao conteúdo do seu app a uma lista de pacotes aprovados comparando clientPackageName à sua lista de permissões e verificando o certificado usado para assinar o APK do pacote. Se o pacote não puder ser verificado, retorne null para negar acesso ao seu conteúdo.

Para fornecer acesso ao seu conteúdo para os apps do sistema (como o Android Automotive OS e o Android Auto), seu serviço precisa sempre retornar um 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
    }
    

Esse snippet de código é um trecho da classe PackageValidator do app de exemplo Universal Android 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() do serviço.

Implementar onLoadChildren()

Depois de receber o objeto do nó raiz, o Android Automotive OS e o Android Auto criam um menu de nível superior chamando onLoadChildren() no objeto do 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 código exclusiva. 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 Automotive OS e o Android Auto 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 desse método, consulte o método onLoadChildren() no app de exemplo 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.

Itens por título

Esse estilo de conteúdo exibe ainda mais metadados que o estilo de conteúdo de itens em lista. Para usar esse estilo de conteúdo, mais metadados precisam ser fornecidos para cada item de mídia.

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 do seu serviço onGetRoot(). O Android Automotive OS e o Android Auto 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 na MediaDescription de um item de mídia.

O snippet de código a seguir mostra como criar um MediaItem navegável que substitui 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)
        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);
    }
    

Adicionar itens de título

Para apresentar seus itens de mídia como título, use um estilo de conteúdo por item que agrupe os itens. 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 traduzida.

O Android Automotive OS e o Android Auto não classificam itens agrupados dessa maneira. É necessário transmitir os itens de mídia juntos e na ordem em que você quer que sejam exibidos.

Por exemplo, suponha que seu app tenha passado três itens de mídia na seguinte ordem:

  • Item de mídia 1 com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  • Item de mídia 2 com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
  • Item de mídia 3 com extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")

O Android Automotive OS e o Android Auto não mesclariam os itens de mídia 1 e 3 em um grupo, "Songs". Em vez disso, os itens de mídia seriam mantidos separados.

O snippet de código a seguir mostra como criar um MediaItem com um cabeçalho 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")
        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*/);
    }
    

Exibir mais indicadores de metadados

Figura 3. Visualização de reprodução com indicadores de metadados.

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 Automotive OS e o Android Auto leem os extras associados a um item na árvore de navegação e procuram certas constantes para determinar quais indicadores exibir. Durante a reprodução da mídia, o Android Automotive OS e o Android Auto 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 passe 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 indicadores para 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 exibiçã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 Automotive OS e o Android Auto exibem esses resultados como uma barra "Mostrar mais resultados" na interface.

Figura 4. Opção "Mostrar mais resultados" na tela do carro.

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 Automotive OS e o Android Auto encaminham os termos de pesquisa de um usuário para esse método sempre que ele chama a affordance "Mostrar mais resultados". Organize 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 Automotive OS e o Android Auto 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 retorno de chamada fornecendo uma implementação da classe MediaSessionCompat.Callback para seu 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 do seu app, o Android Automotive OS e o Android Auto usam a classe MediaSessionCompat.Callback do objeto MediaSessionCompat do seu app que eles receberam no serviço de navegação de mídia do app. Quando um usuário quer controlar a reprodução do conteúdo, como pausar a reprodução ou pular para a próxima faixa, o Android Automotive OS e o Android Auto 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 com que seu app é compatível.

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ídia.
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 seu 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 a partir de 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 seu app faz uma transmissão ao vivo (como uma transmissão 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 seu 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 ou fones de ouvido do smartphone do usuário). O Android Automotive OS e o Android Auto 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 Automotive OS e o Android Auto 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, é necessário chamar os métodos setQueue() e setQueueTitle(), ativar a ação ACTION_SKIP_TO_QUEUE_ITEM e definir o callback onSkipToQueueItem().

O Android Automotive OS e o Android Auto 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 Automotive OS e o Android Auto 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 seu app não for compatível com uma dessas funções, o Android Automotive OS e o Android Auto 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 Automotive OS e o Android Auto deixem o espaço em branco sempre que seu 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. Configure 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 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";
    

Configurar o PlaybackState inicial

À medida que o Android Automotive OS e o Android Auto 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 Automotive OS e o Android Auto precisam retomar ou iniciar a reprodução com base no estado do carro ou nas ações do usuário.

Para fazer 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ídia. 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 desse método, consulte o método setCustomAction() do app de exemplo 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 desse método, consulte o método onCustomAction do app de exemplo 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ídia 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 seu app já estiver reproduzindo um item de mídia, o usuário poderá dizer "Reproduzir [item]" para dizer ao seu app para reproduzir um item diferente sem olhar ou tocar na tela do carro.

Declarar compatibilidade com ações por voz

O snippet de código a seguir mostra como declarar compatibilidade com ações por voz nos arquivos de manifesto do seu app. Inclua esse código no arquivo de manifesto do seu módulo Android Automotive OS e do seu app para smartphones.

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

Analisar consultas de pesquisa por voz

Quando um usuário procura por um item de mídia específico, como "Tocar jazz no [nome do seu app]" ou "Ouvir [título da música]", o método de callback onPlayFromSearch() recebe os resultados da pesquisa por voz no parâmetro de consulta e um pacote de extras.

Seu app pode analisar a consulta de pesquisa por voz e iniciar a reprodução seguindo estas etapas:

  1. Use o pacote de extras e a string de consulta de pesquisa retornada da pesquisa por voz para filtrar os resultados.
  2. Crie uma fila de reprodução com base nesses resultados.
  3. Reproduza o item de mídia mais relevante dos resultados.

O método onPlayFromSearch() usa um parâmetro de extras com informações mais detalhadas da pesquisa por voz. Esses extras ajudam a encontrar o conteúdo de áudio a ser tocado no seu app. Se os resultados da pesquisa não fornecerem esses dados, você poderá implementar uma lógica para analisar a consulta de pesquisa bruta e reproduzir as faixas adequadas com base na consulta.

Os seguintes extras são compatíveis com o Android Automotive OS e o Android Auto:

O snippet de código a seguir mostra como modificar o método onPlayFromSearch() na sua implementação de MediaSession.Callback para analisar a consulta de pesquisa por voz e iniciar a reprodução:

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

Para um exemplo mais detalhado de como implementar a pesquisa por voz para tocar conteúdo de áudio no seu app, consulte o exemplo do Universal Media Player (link em inglês).

Gerenciar consultas vazias

Quando um usuário diz "Reproduzir música no [nome do seu app]", o Android Automotive OS ou Android Auto tenta iniciar seu app e tocar áudio chamando o método onPlayFromSearch() do app. No entanto, como o usuário não disse o nome do item de mídia, o método onPlayFromSearch() recebe um parâmetro de consulta vazio. Nesses casos, seu app precisa responder imediatamente reproduzindo áudio, como uma música da playlist mais recente ou de uma fila aleatória.

Implementar ações de reprodução ativadas por voz

Para fornecer uma experiência de viva-voz enquanto os usuários dirigem e ouvem conteúdo de mídia, seu app precisa permitir que os usuários controlem a reprodução do conteúdo com ações por voz. Quando os usuários falam comandos como “Próxima música”, “Pausar música” ou “Retomar música”, o sistema aciona o método de callback correspondente em que você implementou a ação de controle de reprodução.

Para fornecer ações de reprodução ativadas por voz, primeiro ative os controles de hardware configurando essas sinalizações no objeto MediaSession do seu 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);
    

Depois de definir as sinalizações, implemente os métodos de callback com os controles de reprodução compatíveis com seu app. O Android Automotive OS e o Android Auto são compatíveis com as seguintes ações de reprodução ativadas por voz:

Frase de exemplo Método de callback
"Próxima música" onSkipToNext()
"Música anterior" onSkipToPrevious()
"Pausar música" onPause()
"Parar música" onStop()
"Retomar música" onPlay()

Para um exemplo mais detalhado de como implementar ações de reprodução ativadas por voz no seu app, consulte o exemplo do Universal Media Player (link em inglês).

Implementar atividades de configurações e login no Android Automotive OS

Além do serviço de navegador de mídia, você também pode fornecer atividades de configurações e login otimizadas para veículos no seu app para Android Automotive OS. Essas atividades permitem oferecer funcionalidades de apps que não estão incluídas nas APIs de mídia do Android.

Adicionar uma atividade de configurações

Adicione uma atividade de configurações otimizada para veículos para que os usuários possam definir as configurações do seu app no carro. Sua atividade de configurações também pode fornecer outros fluxos de trabalho, como fazer login ou sair da conta de um usuário ou alternar entre contas.

Fluxos de trabalho da atividade de configurações

Sua atividade de configurações pode fornecer diferentes fluxos de trabalho ao usuário. A imagem a seguir mostra como um usuário interage com sua atividade de configurações usando o Android Automotive OS:

Fluxos de trabalho para uma atividade de configurações

Figura 6. Diagrama de fluxos de trabalho para uma atividade de configurações.

Declarar uma atividade de configurações

Declare sua atividade de configurações no arquivo de manifesto do seu app, conforme mostrado no snippet de código a seguir:

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

Implementar sua atividade de configurações

Quando um usuário inicia seu app, o Android Automotive OS detecta a atividade de configurações que você declarou e exibe uma affordance. O usuário pode selecionar essa affordance ou tocar nela usando o visor do carro para navegar para a atividade. O Android Automotive OS envia o intent ACTION_APPLICATION_PREFERENCES, que solicita que seu app inicie a atividade de configurações.

Adicionar uma atividade de login

Se seu app exigir que o usuário faça login para usá-lo, adicione uma atividade de login otimizada para veículos que gerencie o login e a saída do app. Você também pode adicionar fluxos de trabalho de login e saída a uma atividade de configurações, mas precisa usar uma atividade de login dedicada se o app não puder ser usado até que o usuário faça login.

Fluxo de trabalho da atividade de login

A imagem a seguir mostra como um usuário interage com sua atividade de login usando o Android Automotive OS:

Fluxos de trabalho para uma atividade de login

Figura 7. Diagrama de fluxos de trabalho para uma atividade de login.

Login obrigatório na inicialização do app

Para exigir que um usuário faça login usando sua atividade de login para poder usar seu app, seu serviço de navegação na mídia precisa fazer o seguinte:

  1. Configurar o PlaybackState da sessão de mídia como STATE_ERROR usando o método setState(). Isso informa ao Android Automotive OS que nenhuma outra operação pode ser executada até que o erro seja resolvido.
  2. Configurar o código de erro do PlaybackState da sessão de mídia como ERROR_CODE_AUTHENTICATION_EXPIRED. Isso informa ao Android Automotive OS que o usuário precisa de autenticação.
  3. Definir a mensagem de erro do PlaybackState da sessão de mídia usando o método setErrorMessage(). Como essa mensagem de erro é voltada para o usuário, a mensagem precisa ser traduzida para a localidade dele.
  4. Definir os extras do PlaybackState da sessão de mídia usando o método setExtras(). Inclua as duas chaves a seguir:

    • android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: string que é exibida no botão que inicia o fluxo de trabalho de login. Como essa string é voltada para o usuário, ela precisa ser traduzida para a localidade dele.
    • android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: um PendingIntent que direciona o usuário para sua atividade de login quando ele toca no botão indicado por android.media.extras.ERROR_RESOLUTION_ACTION_LABEL.

O snippet de código a seguir mostra como seu app pode exigir que o usuário faça login antes de usá-lo:

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

Depois que o usuário for autenticado, seu app definirá o PlaybackState para um estado diferente de STATE_ERROR e, em seguida, levará o usuário de volta ao Android Automotive OS chamando o método finish() da atividade.

Implementar sua atividade de login

O Google oferece uma variedade de ferramentas de identidade que podem ser usadas para ajudar os usuários a fazer login no seu app no carro. Algumas ferramentas, como a Firebase Authentication, oferecem kits de ferramentas de pilha completa que podem ajudar a criar experiências de autenticação personalizadas. Outras ferramentas aproveitam as credenciais existentes de um usuário ou outras tecnologias para ajudar a criar experiências de login facilitadas para os usuários.

Recomendamos as seguintes ferramentas para ajudar a criar uma experiência de login mais fácil para usuários já conectados a outro dispositivo:

  • Login do Google: se você já tiver implementado o Login do Google em outros dispositivos (como o app para smartphones), também será necessário implementar o Login do Google no seu app para Android Automotive OS para oferecer compatibilidade a usuários existentes do Login do Google.
  • Preenchimento automático do Google: se os usuários tiverem ativado o preenchimento automático do Google em outros dispositivos Android, as credenciais deles serão salvas no Gerenciador de senhas do Google. Em seguida, quando o usuário fizer login no seu app para Android Automotive OS, o preenchimento automático do Google sugerirá as credenciais salvas relevantes. O uso do preenchimento automático do Google não requer esforço de desenvolvimento de apps. No entanto, os desenvolvedores precisam otimizar os apps para ter resultados de qualidade melhores. O preenchimento automático do Google é compatível com todos os dispositivos que executam o Android Oreo 8.0 (API de nível 26) ou posterior (incluindo o Android Automotive OS).

Gerenciar ações de login protegidas

Alguns apps permitem que um usuário acesse algumas ações anonimamente, mas exigem que ele faça login antes de poder executar outras ações. Por exemplo, um usuário pode tocar música em um app antes de fazer login, mas precisa estar logado para poder pular uma música.

Nesse caso, quando o usuário tenta executar a ação restrita (pular uma música), seu app pode sugerir que ele se autentique emitindo um erro não fatal. Ao usar esse tipo de erro, o sistema exibe a mensagem para o usuário sem interromper a reprodução do item de mídia. Para implementar o gerenciamento de erros não fatais, realize as seguintes etapas:

  1. Configure o errorCode para o PlaybackState da sessão de mídia como ERROR_CODE_AUTHENTICATION_EXPIRED. Isso informa ao Android Automotive OS que o usuário precisa de autenticação.
  2. Mantenha o state do PlaybackState da sessão de mídia como está, não o configure como STATE_ERROR. Isso é o que informa ao sistema que o erro não é fatal.
  3. Defina os extras do PlaybackState da sessão de mídia usando o método setExtras(). Inclua as duas chaves a seguir:

    • android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: string que é exibida no botão que inicia o fluxo de trabalho de login. Como essa string é voltada para o usuário, ela precisa ser traduzida para a localidade dele.
    • android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: um PendingIntent que direciona o usuário para sua atividade de login quando ele toca no botão indicado por android.media.extras.ERROR_RESOLUTION_ACTION_LABEL.
  4. Mantenha o restante do estado PlaybackState da sessão de mídia como está. Isso permite que a reprodução do item de mídia atual continue enquanto o usuário decide se fará login ou não.

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ídia 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ídia 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. Seu 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ídia 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ídia 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 da 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 Automotive OS e o Android Auto 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