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 oferecer 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 mostrar 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 um MediaBrowserService e de uma 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 suporte ao Android Auto e ao Android Automotive OS no seu app de música.

Antes de começar

  1. Leia a documentação da API de mídia do Android.
  2. Consulte Criar apps de mídia para orientações sobre design.
  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 seu conteúdo.
Navegador de mídia
Uma API usada por apps de música para descobrir serviços de navegador de mídia e mostrar 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 uma ou as duas flags a seguir:

  • FLAG_PLAYABLE: indica que o item é uma folha na árvore de conteúdo. O item representa um único fluxo de som, como uma música de um álbum, o capítulo de um audiolivro ou o episódio de um podcast.
  • FLAG_BROWSABLE: 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 Android Automotive OS que segue as diretrizes de design do Android Automotive OS. A interface das atividades não é desenhada por esse sistema. Portanto, verifique se o seu app segue as diretrizes de design. Normalmente, isso inclui áreas de toque e tamanhos de fonte maiores, suporte para 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 app

Antes de criar o serviço de navegador de mídia, é necessário configurar os arquivos de manifesto do 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 pelo 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 descubram o serviço e se conectem ao seu app.

O snippet de código abaixo mostra como declarar o serviço de navegador de mídia no seu manifesto. Inclua este código no arquivo de manifesto do módulo do 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 ícones do app

Você precisa especificar ícones que o Android Auto e o Android Automotive OS possam usar para representar seu app na interface do sistema. Dois tipos de ícone são necessários:

  • Ícone na tela de início
  • Ícone de atribuição
.

Ícone na tela de início

O ícone na tela de início representa seu app na interface do sistema, como na tela de início e na bandeja de ícones. Você pode especificar que quer usar o ícone do app para dispositivos móveis para representar o app de música do carro usando a seguinte declaração de manifesto:

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

Para usar um ícone diferente do seu app para dispositivos móveis, defina a propriedade android:icon no elemento <service> do serviço de navegação de mídia no manifesto:

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

Ícone de atribuição

Figura 1. Ícone de atribuição no card de mídia.

O ícone de atribuição é usado em locais em que o conteúdo de mídia tem precedência, como cards de mídia. Reutilize a versão pequena usada nas notificações. Esse ícone precisa ser monocromático. Especifique o ícone que vai ser usado para representar seu app com a seguinte declaração de manifesto:

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

Criar o serviço de navegador de mídia

Crie um serviço de navegador de mídia ampliando 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.

Você também pode usar o serviço de navegação de mídia para permitir que outros clientes acessem o conteúdo de mídia do seu app. Esses clientes de mídia podem ser outros apps no smartphone de um usuário ou outros clientes remotos.

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. O 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. Esses sistemas mostram os itens de mídia como o nível superior dos itens de conteúdo. Consulte a seção Estruturar o menu raiz desta página para ver mais informações sobre o que o sistema espera nesse nível.
  5. Se o usuário selecionar um item de mídia navegável, o método onLoadChildren() será chamado novamente para extrair 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 adequado para executar essa ação.
  7. Se o app permitir, o usuário também vai 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 suporte a 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 conferir um exemplo mais detalhado, consulte o método onGetRoot() no 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, é 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 que os apps do sistema (como o Android Auto e o Android Automotive OS) acessem o conteúdo, seu serviço precisa sempre retornar um valor BrowserRoot não nulo quando esses apps chamam o método onGetRoot(). A assinatura do app para Android Automotive OS pode variar de acordo com a marca e o modelo do carro. Portanto, é necessário permitir conexões de todos os apps do sistema para oferecer suporte ao Android Automotive OS.

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 no app de exemplo Universal Android Music Player no GitHub (link em inglês). Confira nessa classe um exemplo mais detalhado de como implementar a validação de pacote para o método onGetRoot() do seu serviço.

Além de permitir apps do sistema, você precisa permitir que o Google Assistente se conecte ao MediaBrowserService. Perceba que o Google Assistente tem nomes de pacote separados para o smartphone (que inclui o Android Auto) e o Android Automotive OS.

Implementar onLoadChildren()

Depois que recebem seu objeto de nó raiz, o Android Auto e o Android Automotive OS criam um menu de nível superior chamando onLoadChildren() nesse objeto para receber os filhos dele. 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 exclusiva. Os apps clientes tratam essas strings de ID como tokens opacos. Quando um app cliente quer navegar para um submenu ou abrir um item de mídia, ele transmite o token. O app é responsável por associar o token ao respectivo item de mídia.

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 whether this is the root menu:
    if (MY_MEDIA_ROOT_ID == parentMediaId) {

        // Build the MediaItem objects for the top level
        // and put them in the mediaItems list.
    } else {

        // Examine the passed parentMediaId to see which submenu we're at
        // and put the children of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems)
}

Java

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

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

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

    // Check whether this is the root menu:
    if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {

        // Build the MediaItem objects for the top level
        // and put them in the mediaItems list.
    } else {

        // Examine the passed parentMediaId to see which submenu we're at
        // and put the children of that menu in the mediaItems list.
    }
    result.sendResult(mediaItems);
}

Para conferir um exemplo completo, consulte o método onLoadChildren() no app de exemplo Universal Android Music Player no GitHub (link em inglês).

Estruturar o menu raiz

Figura 2. Conteúdo raiz mostrado como guias de navegação.

O Android Auto e o Android Automotive OS têm restrições específicas sobre a estrutura do menu raiz. Elas são comunicados ao MediaBrowserService por dicas raiz, que podem ser lidas pelo argumento Bundle transmitido para onGetRoot(). Seguindo essas dicas, o sistema pode exibir o conteúdo raiz da maneira ideal como guias de navegação. Se você não seguir essas dicas, alguns conteúdos raiz poderão ser descartados ou passar a ser menos detectáveis pelo sistema. Duas dicas são enviadas:

Use o código abaixo para ler as dicas raiz relevantes:

Kotlin

import androidx.media.utils.MediaConstants

// Later, in your MediaBrowserServiceCompat.
override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle
): BrowserRoot {

  val maximumRootChildLimit = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
      /* defaultValue= */ 4)
  val supportedRootChildFlags = rootHints.getInt(
      MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
      /* defaultValue= */ MediaItem.FLAG_BROWSABLE)

  // Rest of method...
}

Java

import androidx.media.utils.MediaConstants;

// Later, in your MediaBrowserServiceCompat.
@Override
public BrowserRoot onGetRoot(
    String clientPackageName, int clientUid, Bundle rootHints) {

    int maximumRootChildLimit = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
        /* defaultValue= */ 4);
    int supportedRootChildFlags = rootHints.getInt(
        MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
        /* defaultValue= */ MediaItem.FLAG_BROWSABLE);

    // Rest of method...
}

Você pode optar por ramificar a lógica da estrutura de sua hierarquia de conteúdo com base nos valores dessas dicas, especialmente se a hierarquia variar entre integrações de MediaBrowser fora do Android Auto e do Android Automotive OS. Por exemplo, se você normalmente mostra um item raiz reproduzível, convém aninhá-lo em um item navegável raiz devido ao valor da dica de flag aceita.

Além das dicas raiz, há algumas outras diretrizes a serem seguidas para garantir que as guias sejam renderizadas de maneira ideal:

  • Forneça ícones monocromáticos (preferencialmente brancos) para cada item da guia.
  • Forneça etiquetas curtas, mas significativas, para cada item da guia. Etiquetas curtas reduzem a chance de truncamento das strings.

Exibir arte de mídia

A arte em itens de mídia precisa ser transmitida como um URI local usando ContentResolver.SCHEME_CONTENT ou ContentResolver.SCHEME_ANDROID_RESOURCE. Esse URI local precisa ser resolvido para um bitmap ou um drawable vetorial nos recursos do aplicativo. Para objetos MediaDescriptionCompat que representam itens na hierarquia de conteúdo, transmita o URI usando setIconUri(). Para objetos MediaMetadataCompat que representam o item em reprodução no momento, transmita o URI com putString(), usando uma das chaves abaixo:

As etapas a seguir descrevem como fazer o download da arte de um URI da Web e exibi-la usando um URI local. Para conferir um exemplo mais completo, consulte a implementação de openFile() e os métodos próximos no app de exemplo Universal Android Music.

  1. Crie um URI content:// correspondente ao URI da Web. O serviço de navegador de mídia e a sessão de mídia transmitem esse URI de conteúdo ao Android Auto e o Android Automotive OS.

    Kotlin

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

    Java

    public static Uri asAlbumArtContentURI(Uri webUri) {
      return new Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(CONTENT_PROVIDER_AUTHORITY)
        .appendPath(webUri.getPath()) // Make sure you trust the URI!
        .build();
    }
  2. Na sua implementação de ContentProvider.openFile(), verifique se existe um arquivo para o URI correspondente. Caso contrário, faça o download e armazene o arquivo de imagem em cache. O snippet de código abaixo usa o Glide.

    Kotlin

    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
      val context = this.context ?: return null
      val file = File(context.cacheDir, uri.path)
      if (!file.exists()) {
        val remoteUri = Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.path)
            .build()
        val cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
    
        cacheFile.renameTo(file)
        file = cacheFile
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
    }

    Java

    @Nullable
    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
      Context context = this.getContext();
      File file = new File(context.getCacheDir(), uri.getPath());
      if (!file.exists()) {
        Uri remoteUri = new Uri.Builder()
            .scheme("https")
            .authority("my-image-site")
            .appendPath(uri.getPath())
            .build();
        File cacheFile = Glide.with(context)
            .asFile()
            .load(remoteUri)
            .submit()
            .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS);
    
        cacheFile.renameTo(file);
        file = cacheFile;
      }
      return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }

Para mais detalhes sobre provedores de conteúdo, consulte Como criar um provedor de conteúdo.

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 vão ser mostrados 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 mostrados 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 esse pacote e procuram essas constantes para determinar o estilo adequado.

Os seguintes extras podem ser usados como chaves no pacote:

As chaves podem ser correspondidas com os seguintes valores constantes inteiros para influenciar a apresentação desses itens:

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM: os itens correspondentes são apresentados como itens de lista.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM: os itens correspondentes são apresentados como itens de grade.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: os itens correspondentes são apresentados como itens de lista de "categoria". Eles são iguais aos de lista comuns, mas as margens são aplicadas em torno dos ícones dos itens, já que eles ficam melhores quando são pequenos. Os ícones precisam ser drawables vetoriais tingíveis. Essa dica precisa ser fornecida apenas para itens navegáveis.
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: os itens correspondentes são apresentados como itens da grade de "categoria". Eles são iguais aos itens de grade comuns, mas as margens precisam ser aplicadas em torno dos ícones dos itens, já que os ícones ficam melhores pequenos. Os ícones precisam ser drawables vetoriais tingíveis. Essa dica precisa ser fornecida apenas para itens navegáveis.

O snippet de código abaixo mostra como configurar o estilo de conteúdo padrão para itens navegáveis como grades e para itens reproduzíveis como listas:

Kotlin

import androidx.media.utils.MediaConstants

@Nullable
override fun onGetRoot(
    @NonNull clientPackageName: String,
    clientUid: Int,
    @Nullable rootHints: Bundle
): BrowserRoot {
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    return BrowserRoot(ROOT_ID, extras)
}

Java

import androidx.media.utils.MediaConstants;

@Nullable
@Override
public BrowserRoot onGetRoot(
    @NonNull String clientPackageName,
    int clientUid,
    @Nullable Bundle rootHints) {
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    return new BrowserRoot(ROOT_ID, extras);
}

Definir estilos de conteúdo por item

A API Content Style permite substituir o estilo de conteúdo padrão para filhos de qualquer item de mídia navegável e para qualquer item de mídia.

Para substituir o estilo padrão de filhos de um item de mídia navegável, crie um pacote extra na MediaDescription do item de mídia e adicione as mesmas dicas mencionadas anteriormente. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE é aplicado a filhos reproduzíveis desse item, ao passo que DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE se aplica a filhos navegáveis desse item.

Para substituir o padrão de um item de mídia específico em si (e não dos filhos), crie um pacote de extras na MediaDescription do item de mídia e adicione uma dica com a chave DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM. Use os mesmos valores descritos anteriormente para especificar a apresentação desse item.

O snippet de código abaixo mostra como criar um MediaItem navegável que substitui o estilo de conteúdo padrão nele e nos filhos dele. Ele é definido como um item de lista de categorias, os filhos navegáveis como itens de lista e os filhos reproduzíveis como itens de grade:

Kotlin

import androidx.media.utils.MediaConstants

private fun createBrowsableMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)
}

Java

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createBrowsableMediaItem(
    String mediaId,
    String folderName,
    Uri iconUri) {
    MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
    mediaDescriptionBuilder.setMediaId(mediaId);
    mediaDescriptionBuilder.setTitle(folderName);
    mediaDescriptionBuilder.setIconUri(iconUri);
    Bundle extras = new Bundle();
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
    extras.putInt(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
        MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
    mediaDescriptionBuilder.setExtras(extras);
    return new MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}

Agrupar itens usando dicas de título

Para agrupar itens de mídia relacionados, use uma dica por item. Cada item de mídia de um grupo precisa declarar um pacote de extras na MediaDescription que inclui um mapeamento com a chave DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE e um valor de string idêntico. Localize essa string, que é usada como título do grupo.

O snippet de código a seguir mostra como criar um MediaItem com o título de subgrupo "Songs":

Kotlin

import androidx.media.utils.MediaConstants

private fun createMediaItem(
    mediaId: String,
    folderName: String,
    iconUri: Uri
): MediaBrowser.MediaItem {
    val mediaDescriptionBuilder = MediaDescription.Builder()
    mediaDescriptionBuilder.setMediaId(mediaId)
    mediaDescriptionBuilder.setTitle(folderName)
    mediaDescriptionBuilder.setIconUri(iconUri)
    val extras = Bundle()
    extras.putString(
        MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
        "Songs")
    mediaDescriptionBuilder.setExtras(extras)
    return MediaBrowser.MediaItem(
        mediaDescriptionBuilder.build(), /* playable or browsable flag*/)
}

Java

import androidx.media.utils.MediaConstants;

private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) {
   MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
   mediaDescriptionBuilder.setMediaId(mediaId);
   mediaDescriptionBuilder.setTitle(folderName);
   mediaDescriptionBuilder.setIconUri(iconUri);
   Bundle extras = new Bundle();
   extras.putString(
       MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
       "Songs");
   mediaDescriptionBuilder.setExtras(extras);
   return new MediaBrowser.MediaItem(
       mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
}

É 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 que seu app tenha transmitido cinco itens de mídia na seguinte ordem:

  1. Item de mídia A com extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  2. Item de mídia B com extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums")
  3. Item de mídia C com extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  4. Item de mídia D com extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs")
  5. Item de mídia E com extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "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 interpretam 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, o app precisa transmitir os itens de mídia na seguinte ordem:

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

Exibir mais 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 Auto e o Android Automotive OS leem os extras associados a um item na árvore de navegação e procuram certas constantes para determinar quais indicadores mostrar. 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.

Figura 3. Visualização de reprodução com metadados que identificam a música e o artista, além de um ícone que indica conteúdo explícito.

Figura 4. Visualização de navegação com um ponto para o conteúdo não reproduzido no primeiro item e uma barra de progresso para conteúdo parcialmente reproduzido no segundo item.

As constantes abaixo podem ser usadas nos extras de descrição MediaItem e nos extras de MediaMetadata:

As constantes a seguir podem ser usadas apenas nos extras de descrição MediaItem:

Para mostrar indicadores que aparecem enquanto o usuário estiver percorrendo a árvore de navegação, crie um pacote de extras que inclua uma ou mais dessas constantes e transmita o pacote ao método MediaDescription.Builder.setExtras().

O snippet de código abaixo mostra como exibir indicadores para um item de mídia explícito com 70% de conteúdo reproduzido:

Kotlin

import androidx.media.utils.MediaConstants

val extras = Bundle()
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED)
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7)
val description =
    MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build()
return MediaBrowserCompat.MediaItem(description, /* flags */)

Java

import androidx.media.utils.MediaConstants;

Bundle extras = new Bundle();
extras.putLong(
    MediaConstants.METADATA_KEY_IS_EXPLICIT,
    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
extras.putInt(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED);
extras.putDouble(
    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7);
MediaDescriptionCompat description =
    new MediaDescriptionCompat.Builder()
        .setMediaId(/*...*/)
        .setTitle(resources.getString(/*...*/))
        .setExtras(extras)
        .build();
return new MediaBrowserCompat.MediaItem(description, /* flags */);

Para exibir os indicadores de um item de mídia que está sendo reproduzido no momento, você pode declarar valores Long para METADATA_KEY_IS_EXPLICIT ou EXTRA_DOWNLOAD_STATUS no método MediaMetadataCompat da mediaSession. Não é possível exibir os indicadores DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS ou DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE 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

import androidx.media.utils.MediaConstants

mediaSession.setMetadata(
    MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
            albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build())

Java

import androidx.media.utils.MediaConstants;

mediaSession.setMetadata(
    new MediaMetadataCompat.Builder()
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
        .putString(
            MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
            albumArtUri.toString())
        .putLong(
            MediaConstants.METADATA_KEY_IS_EXPLICIT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        .putLong(
            MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
            MediaDescriptionCompat.STATUS_DOWNLOADED)
        .build());

Atualizar a barra de progresso na visualização de navegação enquanto o conteúdo é reproduzido

Conforme mencionado anteriormente, você pode usar o extra DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE para mostrar uma barra de progresso para conteúdo parcialmente reproduzido na visualização de navegação. No entanto, se o usuário continuar reproduzindo esse conteúdo no Android Auto ou no Android Automotive OS, o indicador não vai ser preciso com o passar do tempo.

Para que o Android Auto e o Android Automotive OS mantenham a barra de progresso atualizada, é possível fornecer mais informações em MediaMetadataCompat e PlaybackStateCompat para vincular o conteúdo em andamento a itens de mídia na visualização de navegação. Os requisitos abaixo precisam ser atendidos para que o item de mídia tenha uma barra de progresso com atualização automática:

O snippet de código abaixo mostra como indicar que o item em reprodução no momento está vinculado a um item na visualização de navegação:

Kotlin

import androidx.media.utils.MediaConstants

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

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

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

Java

import androidx.media.utils.MediaConstants;

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

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

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

Figura 5. Visualização de reprodução com uma opção "Resultados da pesquisa" para mostrar itens de mídia relacionados à pesquisa por voz do usuário.

Seu app pode fornecer resultados da pesquisa contextuais que são exibidos para os usuários quando eles iniciam uma consulta de pesquisa. O Android Auto e o Android Automotive OS mostram esses resultados usando interfaces de consulta de pesquisa ou recursos que giram em torno de consultas feitas anteriormente na sessão. Para saber mais, consulte a seção Suporte para comandos de voz neste guia.

Para mostrar resultados de pesquisa navegáveis, inclua a chave de constante BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED no pacote de extras do método onGetRoot() do seu serviço, mapeando para o booleano true.

O snippet de código a seguir mostra como ativar a compatibilidade no método onGetRoot():

Kotlin

import androidx.media.utils.MediaConstants

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

Java

import androidx.media.utils.MediaConstants;

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

Para começar a fornecer resultados da 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 o usuário chama uma interface de consulta de pesquisa ou uma funcionalidade de "resultados da pesquisa".

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 o 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<List<MediaItem>> result) {

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

  new AsyncTask<Void, Void, Void>() {
    List<MediaItem> searchResponse;
    boolean succeeded = false;
    @Override
    protected Void doInBackground(Void... params) {
      searchResponse = new ArrayList<MediaItem>();
      if (doSearch(query, extras, searchResponse)) {
        succeeded = true;
      }
      return null;
    }

    @Override
    protected void onPostExecute(Void param) {
      if (succeeded) {
       // Sending an empty List informs the caller that there were no results.
       result.sendResult(searchResponse);
      } else {
        // This invokes onError() on the search callback.
        result.sendResult(null);
      }
    }
  }.execute()
}

/** Populates resultsToFill with search results. Returns true on success or false on error. */
private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) {
    // Implement this method.
}

Ações de navegação personalizadas

Uma única ação de navegação personalizada.

Figura 6. Uma única ação de navegação personalizada

As ações de navegação personalizadas permitem adicionar ícones e rótulos personalizados aos objetos MediaItem do seu app no app de mídia do carro e processar as interações do usuário com essas ações. Com isso, você pode estender a funcionalidade do app de mídia de várias maneiras, como adicionando ações "Fazer o download", "Adicionar à fila", "Tocar rádio", "Adicionar aos favoritos" ou "Remover".

Um menu flutuante de ações de navegação personalizadas.

Figura 7. Menu flutuante de ações de navegação personalizadas

Se houver mais ações personalizadas do que o OEM permite, um menu flutuante será apresentado ao usuário.

Como elas funcionam?

Cada ação de navegação personalizada é definida por:

  • um ID da ação (um identificador de string exclusivo);
  • um rótulo de ação (o texto exibido para o usuário);
  • um URI de ícone de ação (um drawable vetorial que pode ser colorido).

Você define uma lista de ações de navegação personalizadas globalmente como parte da sua BrowseRoot. Em seguida, é possível anexar um subconjunto dessas ações a cada MediaItem.

Quando um usuário interage com uma ação de navegação personalizada, seu app recebe um callback em onCustomAction(). Em seguida, você pode processar a ação e atualizar a lista de ações para o MediaItem, se necessário. Isso é útil para ações com estado, como "Adicionar aos favoritos" e "Fazer o download". No caso de ações que não precisam ser atualizadas, como "Tocar rádio", não é necessário atualizar a lista.

Ações de navegação personalizadas em uma raiz de nó de navegação.

Figura 8. Barra de ferramentas de ações de navegação personalizadas

Você também pode anexar ações de navegação personalizadas a uma raiz de nó de navegação. Essas ações vão ser mostradas em uma barra de ferramentas secundária abaixo da barra de ferramentas principal.

Como implementar ações de navegação personalizadas

Siga estas etapas para adicionar ações de navegação personalizadas ao projeto:

  1. Substitua dois métodos na implementação de MediaBrowserServiceCompat:
  2. Analise os limites de ação durante a execução:
  3. Crie a lista global de ações de navegação personalizadas:
    • Para cada ação, crie um objeto Bundle com as seguintes chaves: * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID: o ID da ação * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: o rótulo da ação * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: o URI do ícone de ação * Adicione todos os objetos Bundle de ação a uma lista.
  4. Adicione a lista global à BrowseRoot:
  5. Adicione ações aos objetos MediaItem:
    • Você pode adicionar ações a objetos MediaItem individuais incluindo a lista de IDs de ação nos MediaDescriptionCompat extras usando a chave DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST. Essa lista precisa ser um subconjunto da lista global de ações definida na BrowseRoot.
  6. Processe as ações e retorne o progresso ou os resultados:

Confira algumas mudanças que você pode fazer no BrowserServiceCompat para começar a usar as ações de navegação personalizadas.

Substituir BrowserServiceCompat

Substitua os métodos abaixo em MediaBrowserServiceCompat.

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

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

Analisar limite de ações

Confira quantas ações de navegação personalizadas têm suporte.

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

Criar uma ação de navegação personalizada

Cada ação precisa ser empacotada em um Bundle separado.

  • ID da ação
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
  • Rótulo da ação
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
  • URI do ícone de ação
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")

Adicionar ações de navegação personalizadas a Parceable ArrayList

Adicione todos os objetos Bundle das ações de navegação personalizadas a uma ArrayList.

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

Adicionar a lista de ações de navegação personalizadas à raiz de navegação

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

Adicionar ações a um MediaItem

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

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

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

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

Resultado do build onCustomAction

  • Analise o mediaId de Bundle extras:
    @Override
    public void onCustomAction(
              @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){
      String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID);
    }
  • Para resultados assíncronos, remova o resultado. result.detach()
  • Pacote de resultados do build
    • Mensagem para o usuário
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
    • Atualizar item (use para atualizar as ações em um item)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
    • Abrir a visualização de reprodução
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
    • Atualizar nó de navegação
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
  • Se ocorrer um erro, chame result.sendError(resultBundle)..
  • Se o progresso for atualizado, chame result.sendProgressUpdate(resultBundle).
  • Para concluir, chame result.sendResult(resultBundle).

Atualizar estado da ação

Ao usar o método result.sendProgressUpdate(resultBundle) com a chave EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, você pode atualizar o MediaItem para refletir o novo estado da ação. Isso permite fornecer feedback em tempo real ao usuário sobre o progresso e o resultado da ação.

Exemplo: ação de download

Confira um exemplo de como usar esse recurso para implementar uma ação de download com três estados:

  1. Fazer o download: é o estado inicial da ação. Quando o usuário selecionar essa ação, troque por "Fazendo o download" e chame sendProgressUpdate para atualizar a interface.
  2. Fazendo o download: este estado indica que o download está em andamento. É possível usar esse estado para mostrar uma barra de progresso ou outro indicador ao usuário.
  3. Salvo: este estado indica que o download foi concluído. Quando o download for concluído, troque "Fazendo o download" por "Salvo" e chame sendResult com a chave EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM para indicar que o item precisa ser atualizado. Além disso, você pode usar a chave EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE para mostrar uma mensagem de êxito para o usuário.

Essa abordagem permite fornecer um feedback claro ao usuário sobre o processo de download e o estado atual dele. Você pode adicionar ainda mais detalhes com ícones para mostrar os estados de download de 25%, 50% e 75%.

Exemplo: ação de adicionar aos favoritos

Outro exemplo é uma ação de adicionar aos favoritos com dois estados:

  1. Adicionar aos favoritos: esta ação é exibida para itens que não estão na lista de favoritos do usuário. Quando o usuário selecionar essa ação, será possível trocá-la por "Adicionado aos favoritos" e chamar sendResult com a chave EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM para atualizar a interface.
  2. Adicionado aos favoritos: esta ação é exibida para itens que estão na lista de favoritos do usuário. Quando o usuário selecionar essa ação, troque por "Adicionar aos favoritos" e chame sendResult com a chave EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM para atualizar a interface.

Essa abordagem oferece uma maneira clara e consistente para os usuários gerenciarem os itens favoritos.

Esses exemplos mostram a flexibilidade das ações de navegação personalizadas e como você pode usá-las para implementar uma variedade de funcionalidades com feedback em tempo real para melhorar a experiência do usuário no app de mídia do carro.

Para um exemplo completo de implementação desse recurso, consulte o projeto TestMediaApp.

Ativar controle de mídia

O Android Auto e o Android Automotive OS enviam comandos de controle de mídia usando o método 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 that implements MediaSession.Callback
        // to handle play control requests.
        setCallback(MyMediaSessionCallback())
    }
    sessionToken = session.sessionToken
    ...
}

Java

public void onCreate() {
    super.onCreate();
    ...
    // Start a new MediaSession.
    MediaSessionCompat session = new MediaSessionCompat(this, "session tag");
    setSessionToken(session.getSessionToken());

    // Set a callback object that implements MediaSession.Callback
    // to handle play control requests.
    session.setCallback(new MyMediaSessionCallback());
    ...
}

Ao criar o objeto de sessão de mídia, você define um objeto de callback usado para processar solicitações de controle de mídia. 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 do objeto de callback.

Para gerenciar a reprodução de conteúdo, seu app precisa ampliar 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. O app vai precisar abrir o conteúdo padrão ou, se a reprodução tiver sido pausada com onPause(), o app vai 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 ver mais informações, consulte a seção sobre como definir 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.

Modifique esses métodos no seu app para fornecer a funcionalidade desejada. Não é necessário implementar um método caso a funcionalidade dele não ofereça suporte para o app. Por exemplo, se o app fizer uma transmissão ao vivo (como uma esportiva), não será necessário implementar o método onSkipToNext(). Em vez disso, use 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 pode tocar o áudio da mesma forma que reproduzi 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 saber mais sobre como tocar conteúdo de áudio, consulte Visão geral do MediaPlayer, Visão geral do app de áudio e Visão geral do ExoPlayer.

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

O Android Auto e o Android Automotive OS mostram controles de mídia com base nas ações ativadas no objeto PlaybackStateCompat.

Por padrão, seu app precisa oferecer suporte para as seguintes ações:

O app também pode oferecer suporte às ações abaixo caso elas sejam relevantes para o conteúdo:

Além disso, você tem a opção de criar uma fila de reprodução que pode ser exibida para o usuário, mas não é obrigatória. Para fazer isso, chame os métodos setQueue() e setQueueTitle(), ative a ação ACTION_SKIP_TO_QUEUE_ITEM e defina o callback onSkipToQueueItem().

Além disso, adicione suporte ao ícone Tocando agora, que é um indicador do que está sendo reproduzido. Para fazer isso, chame o método setActiveQueueItemId() e transmita o ID do item em reprodução na fila. É necessário atualizar o setActiveQueueItemId() sempre que houver uma mudança na fila.

O Android Auto e o Android Automotive OS mostram botões para cada ação ativada e para a fila de reprodução. Ao receber um clique no botão, o sistema invoca o callback correspondente de MediaSessionCompat.Callback.

Reservar espaço não utilizado

O Android Auto e o Android Automotive OS reservam espaço na interface para as ações ACTION_SKIP_TO_PREVIOUS e ACTION_SKIP_TO_NEXT. Se o app não oferecer suporte a uma dessas funções, o Android Auto e o Android Automotive OS usarão o espaço para mostrar 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 oferecer suporte para a função correspondente. Para fazer isso, chame o método setExtras() com um pacote de extras que tenha constantes correspondentes às funções reservadas. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT corresponde a ACTION_SKIP_TO_NEXT e SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV corresponde a ACTION_SKIP_TO_PREVIOUS. Use essas constantes como chaves no pacote e use o booleano true para os valores delas.

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, a sessão de mídia informa o status da reprodução do conteúdo usando PlaybackStateCompat. Não é recomendado que o app comece a tocar músicas automaticamente quando o Android Automotive OS ou o Android Auto se conectarem ao 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 PlaybackStateCompat inicial da sessão de mídia como STATE_STOPPED, STATE_PAUSED, STATE_NONE, ou STATE_ERROR.

As sessões de mídia no Android Auto e no Android Automotive OS duram apenas o tempo da viagem, portanto, os usuários iniciam e interrompem essas sessões com frequência. Para promover uma experiência contínua entre viagens, monitore o estado anterior da sessão do usuário para que, quando o app de música receber uma solicitação de retomada, o usuário possa continuar de onde parou (or exemplo, o último item de mídia reproduzido, o PlaybackStateCompat e a fila).

Adicionar ações de reprodução personalizadas

Você pode adicionar ações de reprodução personalizadas para mostrar mais ações com suporte do 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 mostradas no menu flutuante. As ações personalizadas são exibidas na ordem em que são adicionadas ao PlaybackStateCompat.

Use-as para oferecer um comportamento diferente das ações padrão. Não as use para substituir ou duplicar ações padrão.

Adicione ações personalizadas 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 conferir um exemplo mais detalhado, consulte o método setCustomAction() no app de exemplo Universal Android Music Player no GitHub (link em inglês).

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

O snippet de código a seguir mostra como o 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 conferir um exemplo mais detalhado, consulte o método onCustomAction no 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.

Se uma ação personalizada tiver um estado (por exemplo, ativar ou desativar uma configuração de reprodução), forneça um ícone diferente para cada estado. Assim, os usuários poderão notar uma mudança visual ao selecionarem a ação.

Oferecer estilos alternativos de ícones para ações desativadas

Quando 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 6. Exemplos de ícones personalizados de ações desativadas.

Indicar formato de áudio

Para indicar que a mídia em reprodução no momento usa um formato de áudio especial, é possível especificar ícones renderizados em carros com suporte a esse recurso. Você pode definir KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI e KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI no pacote de extras do item de mídia em reprodução (transmitido para MediaSession.setMetadata()). Defina ambos os extras para acomodar layouts diferentes.

Além disso, você pode definir o extra KEY_IMMERSIVE_AUDIO para informar aos OEMs de carro que esse é um áudio imersivo. Eles precisam ter muito cuidado ao decidir se vão aplicar efeitos de áudio que possam interferir no conteúdo imersivo.

Você pode configurar o item de mídia em reprodução para que as legendas, a descrição ou ambos sejam links para outros itens de mídia. Isso permite que o usuário acesse rapidamente itens relacionados. Por exemplo, ele pode pular para outras músicas do mesmo artista, outros episódios desse podcast etc. Se o carro tiver suporte a esse recurso, os usuários poderão tocar no link para navegar até esse conteúdo.

Para adicionar links, configure os metadados KEY_SUBTITLE_LINK_MEDIA_ID, no caso de links que usam a legenda, ou KEY_DESCRIPTION_LINK_MEDIA_ID, nos que usam a descrição. Para mais detalhes, consulte a documentação de referência desses campos de metadados.

Oferecer compatibilidade com comandos de voz

O app de música precisa oferecer suporte para comandos de voz para ajudar a fornecer aos motoristas uma experiência segura e conveniente que minimize as distrações. Por exemplo, se o app estiver reproduzindo um item de mídia, o usuário poderá dizer "Tocar [título da música]" para solicitar ao app a reprodução de um item diferente sem olhar ou tocar na tela do carro. Os usuários podem iniciar consultas clicando nos botões correspondentes no volante ou falando as hotwords "Ok Google".

Quando o Android Auto ou o Android Automotive OS detecta e interpreta uma ação por voz, ela é enviada ao app pelo onPlayFromSearch(). Ao receber esse callback, o app encontra conteúdo correspondente à string query e inicia a reprodução.

O usuário pode especificar diferentes categorias de termos nas consultas, como gênero, artista, álbum, nome da música, estação de rádio ou playlist, entre outros. Ao desenvolver o suporte à pesquisa, considere todas as categorias que fazem sentido para o app. Se o Android Auto ou o Android Automotive OS detectar que uma consulta se encaixa em algumas categorias, ele anexa extras ao parâmetro extras. Os extras a seguir podem ser enviados:

Considere uma string query vazia, que pode ser enviada pelo Android Auto ou pelo Android Automotive OS se o usuário não especificar termos de pesquisa. Por exemplo, se o usuário disser "Tocar música". Nesse caso, o app pode optar por iniciar uma faixa reproduzida recentemente ou uma faixa sugerida.

Se não for possível processar uma pesquisa rapidamente, não bloqueie onPlayFromSearch(). Em vez disso, defina o estado de reprodução como STATE_CONNECTING e realize a pesquisa em uma linha de execução assíncrona.

Quando a reprodução começar, considere preencher a fila da sessão de mídia com conteúdos relacionados. Por exemplo, se o usuário solicitar a reprodução de um álbum, o app pode preencher a fila com a lista de faixas do álbum. Considere também implementar suporte a resultados de pesquisa navegáveis, para que o usuário possa escolher uma faixa diferente, mas que também corresponde à consulta.

Além de consultas de "tocar", o Android Auto e o Android Automotive OS reconhecem as consultas de voz para controlar a reprodução como "pausar música" e "próxima música", e usam os callbacks de sessão de mídia correspondentes aos comandos, como onPause() e onSkipToNext().

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

Suprimir alarmes no carro

Os apps de música do Android Auto não devem começar a tocar áudio pelos alto-falantes do carro, a menos que o usuário inicie a reprodução, por exemplo, pressionando o botão "play". Nem mesmo um alarme do app de música programado pelo usuário pode começar a tocar música pelos alto-falantes do carro.

Para atender a esse requisito, o app pode usar CarConnection como sinal antes de reproduzir qualquer áudio. Para verificar se o smartphone está projetando a visualização na tela de um carro, o app pode observar o tipo de conexão com o carro em LiveData e conferir se ele é igual a CONNECTION_TYPE_PROJECTION.

Caso o smartphone do usuário esteja sendo projetado na tela do carro, os apps de música que oferecem suporte a alarmes precisam realizar uma destas ações:

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

Processar 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. Para impedir que o Android Auto mostre uma notificação nesse caso, configure a chave de metadados de mídia METADATA_KEY_IS_ADVERTISEMENT como METADATA_VALUE_ATTRIBUTE_PRESENT, conforme indicado no snippet de código abaixo:

Kotlin

import androidx.media.utils.MediaConstants

override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) {
    MediaMetadataCompat.Builder().apply {
        if (isAd(mediaId)) {
            putLong(
                MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
        }
        // ...add any other properties you normally would.
        mediaSession.setMetadata(build())
    }
}

Java

import androidx.media.utils.MediaConstants;

@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
    MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
    if (isAd(mediaId)) {
        builder.putLong(
            MediaConstants.METADATA_KEY_IS_ADVERTISEMENT,
            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT);
    }
    // ...add any other properties you normally would.
    mediaSession.setMetadata(builder.build());
}

Gerenciar erros gerais

Quando o app apresentar um erro, configure o estado de reprodução como STATE_ERROR e forneça uma mensagem de erro usando o método setErrorMessage(). Consulte PlaybackStateCompat para conferir uma lista de códigos de erro que podem ser usados ao definir a mensagem de erro. 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 mostrar a mensagem de erro para o usuário.

Por exemplo, se o conteúdo não estiver disponível na região atual do usuário, use o código ERROR_CODE_NOT_AVAILABLE_IN_REGION ao definir a mensagem de erro.

Kotlin

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

Java

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

Para saber mais sobre estados de erro, consulte Como usar uma sessão de mídia: estados e erros.

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

Outros recursos