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 esses 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 nó raiz para solicitar o restante do seu conteúdo usando o método onLoadChildren. 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 ver 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 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:

  • Compare o clientPackageName com sua lista de permissões.
  • Verifique 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 AAOS) acessem o conteúdo, seu serviço precisa retornar um valor BrowserRoot não nulo quando esses apps chamam o método onGetRoot.

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

Este snippet de código 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 essa classe para 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. O Google Assistente usa nomes de pacote separados para o smartphone, que inclui o Android Auto e o Android Automotive OS (AAOS).

Implementar onLoadChildren

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

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. 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 ver um exemplo desse método, consulte o 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(). Seguindo essas dicas, o sistema pode exibir 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 passar a ser 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. Se você não seguir essas dicas, alguns conteúdos raiz poderão ser descartados ou passar a ser menos detectáveis. Estas 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 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 (preferencialmente brancos) para cada item da guia

  • Rótulos curtos e significativos para cada item da guia (rótulos curtos reduzem as chances de serem truncados)