Criar sua hierarquia de conteúdo

O Android Auto e o Android Automotive OS (AAOS) chamam o serviço de navegação de mídia do seu app para descobrir qual conteúdo está disponível. Para oferecer suporte a isso, implemente estes dois métodos no serviço de navegação de mídia.

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 AAOS usam esse root nó para solicitar o restante do seu conteúdo usando o onLoadChildren método. Este snippet de código mostra uma implementação 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 um exemplo detalhado desse método, consulte onGetRoot no app de exemplo Universal Android Music Player no GitHub (link em inglês).

Adicionar validação de pacote

Quando uma chamada é feita para o método onGetRoot do serviço, o pacote de chamada transmite informações de identificação para o serviço. O serviço 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:

  • Compare o clientPackageName à sua lista de permissões.
  • Verifique o certificado usado para assinar o APK do pacote.

Se o pacote não puder ser verificado, retorne null para negar o acesso ao conteúdo.

Para fornecer acesso ao conteúdo para apps do sistema, como o Android Auto e o AAOS, o serviço precisa retornar um BrowserRoot não nulo quando esses apps do sistema chamam o método onGetRoot.

A assinatura do app do sistema AAOS varia de acordo com a marca e o modelo de um carro. Permita conexões de todos os apps do sistema para oferecer suporte ao AAOS.

Este snippet de código mostra como o serviço pode validar se o pacote de chamada é um app do sistema:

fun isKnownCaller(
    callingPackage: String,
    callingUid: Int
): Boolean {
    ...
    val isCallerKnown = when {
       // If the system is making the call, allow it.
       callingUid == Process.SYSTEM_UID -> true
       // If the app was signed by the same certificate as the platform
       // itself, also allow it.
       callerSignature == platformSignature -> true
       // ... more cases
    }
    return isCallerKnown
}

Este snippet de código é um trecho da classe PackageValidator no app de exemplo Universal Android Music Player no GitHub (link em inglês). Consulte essa classe para um exemplo mais detalhado de como implementar a validação de pacote para o método onGetRoot do serviço.

Além de permitir apps do sistema, você precisa permitir que o Google Assistente se conecte ao MediaBrowserService. O Google Assistente usa nomes de pacotes diferentes para os apps para dispositivos móveis e AAOS.

Implementar onLoadChildren

Depois que recebem seu objeto de nó raiz, o Android Auto e o AAOS criam um menu de nível superior chamando onLoadChildren nesse objeto para receber os descendentes dele. Os apps clientes criam submenus chamando esse mesmo método usando objetos de um nó descendente.

Cada nó da sua hierarquia de conteúdo é representado por um MediaBrowserCompat.MediaItem objeto. Cada um desses itens de mídia é identificado por uma string de ID exclusiva. Os apps clientes tratam essas strings de código como tokens opacos.

Quando um app cliente quer navegar até um submenu ou reproduzir um item de mídia, ele transmite o token. Seu app é responsável por associar o token ao item de mídia apropriado.

Este snippet de código mostra uma implementação de onLoadChildren

Kotlin

override fun onLoadChildren(
    parentMediaId: String,
    result: Result<List<MediaBrowserCompat.MediaItem>>
) {
    // Assume for example that the music catalog is already loaded/cached.

    val mediaItems: MutableList&lt;MediaBrowserCompat.MediaItem> = mutableListOf()

    // Check if this is the root menu:
    if (MY_MEDIA_ROOT_ID == parentMediaId) {

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

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

Java

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

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

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

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

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

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

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

Estruturar o menu raiz

O Android Auto e o Android Automotive OS têm restrições específicas sobre a estrutura do menu raiz. Elas são comunicadas ao MediaBrowserService por dicas raiz, que podem ser lidas pelo argumento Bundle transmitido para onGetRoot(). Quando seguidas, essas dicas permitem que o sistema mostre o conteúdo raiz como guias de navegação. Se você não seguir essas dicas, alguns conteúdos raiz poderão ser descartados ou tornados menos detectáveis pelo sistema.

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

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

Ao aplicar essas dicas, o sistema mostra o conteúdo raiz como guias de navegação. Ao não aplicar essas dicas, alguns conteúdos raiz poderão ser descartados ou tornados menos detectáveis. Essas dicas são transmitidas:

Kotlin

import androidx.media.utils.MediaConstants

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle
): BrowserRoot {

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

  // Rest of method...
}

Java

import androidx.media.utils.MediaConstants;

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

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

    // Rest of method...
}

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 de fora do Android Auto e do AAOS.

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, use estas diretrizes para renderizar guias de maneira ideal:

  • Ícones monocromáticos (de preferência brancos) para cada item da guia

  • Etiquetas curtas e significativas para cada item da guia (etiquetas curtas reduzem as chances de serem truncadas)