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

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 アシスタントは、モバイルアプリと AAOS アプリで 異なるパッケージ名を使用します。

onLoadChildren を実装する

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

onGetChildren

コンテンツ階層の各ノードは、 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 統合によって異なる場合に、これらのヒントの値に基づいてコンテンツ階層の構造のロジックを分岐することもできます。

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

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

  • 各タブアイテムのモノクロ(できれば白)のアイコン

  • 各タブアイテムの短く意味のあるラベル(ラベルが短ければ、ラベルが切り捨てられる可能性が低くなります)