コンテンツ階層を構築する

Android Auto と Android Automotive OS(AAOS)は、アプリのメディア ブラウザ サービスを呼び出して、利用可能なコンテンツを見つけます。これをサポートするには、メディア ブラウザ サービスにこれらの 2 つのメソッドを実装します。

onGetRoot を実装する

サービスの onGetRoot メソッドは、コンテンツ階層のルートノードに関する情報を返します。Android Auto と AAOS はこのルートノードを使用して、onLoadChildren メソッドで残りのコンテンツをリクエストします。次のコード スニペットは、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);
}

このメソッドの詳細な例については、GitHub の Universal Android Music Player サンプルアプリの onGetRoot をご覧ください。

パッケージ検証を追加

サービスの onGetRoot メソッドが呼び出されると、呼び出し元のパッケージは識別情報をサービスに渡します。サービスはこの情報を使用して、そのパッケージがコンテンツにアクセスできるかどうかを判断できます。

たとえば、アプリのコンテンツへのアクセスを承認済みパッケージのリストに限定できます。

  • clientPackageName を許可リストと比較します。
  • パッケージの APK の署名に使用された証明書を検証します。

パッケージを検証できない場合は、null を返してコンテンツへのアクセスを拒否します。

システムアプリ(Android Auto や AAOS など)にコンテンツへのアクセスを許可するには、システムアプリが onGetRoot メソッドを呼び出すときに、サービスが null でない BrowserRoot を返す必要があります。

AAOS システムアプリの署名は、自動車のメーカーやモデルによって異なります。AAOS をサポートするには、すべてのシステムアプリからの接続を許可してください。

次のコード スニペットは、呼び出し元のパッケージがシステムアプリであることをサービスが検証する方法を示しています。

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
}

このコード スニペットは、GitHub の Universal Android Music Player サンプルアプリの PackageValidator クラスからの抜粋です。サービスの onGetRoot メソッドのパッケージ検証を実装する方法の詳細な例については、このクラスを参照してください。

システムアプリを許可するだけでなく、Google アシスタントが MediaBrowserService に接続できるようにする必要もあります。Google アシスタントには、スマートフォン(Android Auto を含む)用と Android AAOS 用の個別のパッケージ名があります。

onLoadChildren を実装する

Android Auto と AAOS は、ルートノードのオブジェクトを受け取った後、そのオブジェクトで onLoadChildren を呼び出してその子孫を取得することにより、最上位メニューを構築します。クライアント アプリは、子孫ノードのオブジェクトを使用してこの同じメソッドを呼び出すことにより、サブメニューを構築します。

コンテンツ階層の各ノードは、MediaBrowserCompat.MediaItem オブジェクトで表されます。これらの各メディア アイテムは、一意の ID 文字列によって識別されます。クライアント アプリは、このような ID 文字列を不透明なトークンとして扱います。

クライアント アプリがサブメニューのブラウズまたはメディア アイテムの再生を行いたいときは、トークンを渡します。トークンを適切なメディア アイテムに関連付ける処理はアプリが行います。

このコード スニペットは、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);
}

このメソッドの例については、GitHub の Universal Android Music Player サンプルアプリの onLoadChildren をご覧ください。

ルートメニューを構造化する

Android Auto と Android Automotive OS には、ルートメニューの構造に関する特定の制約があります。これらの制約はルートヒントを介して MediaBrowserService に伝えられます。ルートヒントは、onGetRoot() に渡される Bundle 引数を介して読み取ることができます。これらのヒントに従うことで、システムはルート コンテンツをナビゲーション タブとして表示できます。これらのヒントに従わないと、一部のルート コンテンツが破棄されたり、システムによって検出されにくくなったりすることがあります。

ナビゲーション タブとして表示されるルート コンテンツ

図 1. ナビゲーション タブとして表示されるルート コンテンツ。

これらのヒントを適用することで、システムはルート コンテンツをナビゲーション タブとして表示します。これらのヒントを適用しないと、一部のルート コンテンツが破棄されたり、検出されにくくなったりすることがあります。これらのヒントは次のように送信されます。

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

特に、階層の値が Android Auto と AAOS の外部の MediaBrowser 統合によって異なる場合に、これらのヒントの値に基づいてコンテンツ階層の構造のロジックを分岐することもできます。

たとえば、ルートの再生可能なアイテムを通常どおり表示する場合、サポートされるフラグヒントの値に従って、ルートのブラウズ可能なアイテムの下にネストできます。

タブを適切に表示するには、ルートヒントとは別に次のガイドラインにも従う必要があります。

  • 各タブアイテムのモノクロ(白を推奨)アイコン

  • 各タブアイテムに簡潔でわかりやすいラベルを付ける(ラベルが短ければ、ラベルが切り捨てられる可能性が低くなる)