自動車向けメディアアプリを作成する

Android Auto と Android Automotive OS は、メディアアプリのコンテンツを自動車に乗っているユーザーに届けます。自動車向けメディアアプリは、Android Auto と Android Automotive OS、またはメディア ブラウザ機能を持つ別のアプリがコンテンツを見つけて表示できるよう、メディア ブラウザ サービスを提供する必要があります。

このガイドは、音声を再生するメディアアプリがスマートフォンにすでにインストールされており、そのメディアアプリが Android メディアアプリ アーキテクチャに準拠していることを前提としています。

このガイドでは、アプリが Android Auto または Android Automotive OS 上で動作するうえで必須のコンポーネントである MediaBrowserServiceMediaSession について説明します。核となるメディア インフラストラクチャが完成すると、Android Auto のサポートおよび Android Automotive OS のサポートをメディアアプリに追加できるようになります。

始める前に

  1. Android メディア API のドキュメントを確認してください。
  2. 設計に関するガイダンスについては、メディアアプリの作成を確認してください。
  3. このセクションに記載されている主な用語と概念を確認してください。

主な用語と概念

メディア ブラウザ サービス
MediaBrowserServiceCompat API に準拠するメディアアプリによって実装される Android サービス。アプリはこのサービスを使用してコンテンツを公開します。
メディア ブラウザ
メディア ブラウザ サービスを見つけてコンテンツを表示するためにメディアアプリで使用される API。Android Auto と Android Automotive OS は、アプリのメディア ブラウザ サービスを見つけるためにメディア ブラウザを使用します。
メディア アイテム

メディア ブラウザはそのコンテンツを MediaItem オブジェクトのツリーにまとめます。メディア アイテムには、次のフラグのいずれかまたは両方を含めることができます。

  • FLAG_PLAYABLE: アイテムがコンテンツ ツリーのリーフであることを示します。アイテムは、単一のサウンド ストリーム(アルバムの曲、オーディオ ブックのチャプター、ポッドキャストのエピソードなど)を表します。
  • FLAG_BROWSABLE: アイテムがコンテンツ ツリーのノードであり、子があることを示します(たとえば、アイテムがアルバムを表し、その子がアルバムの曲である場合など)。

ブラウズと再生の両方が可能なメディア アイテムは、プレイリストのような役割を果たします。アイテム自体を選択してそのすべての子を再生することも、アイテムの子をブラウズすることもできます。

自動車向け最適化

Android Automotive OS 設計ガイドラインに従った Android Automotive OS アプリのアクティビティ。このアクティビティのインターフェースは Android Automotive OS によって描画されないため、アプリが設計ガイドラインに従うように注意する必要があります。設計ガイドラインには通常、タップ ターゲットとフォントサイズを大きくする、日中モードと夜間モードをサポートする、コントラスト比を大きくする、といったものがあります。

自動車向けに最適化されたユーザー インターフェースは、自動車ユーザー エクスペリエンス制限(CUXR)が作動中でない場合にのみ表示できます。これらのインターフェースがユーザーに対して、長時間の注意や対話を求めることがあるためです。CUXR は、車が停止または駐車しているときは作動しませんが、車が動いているときは常に作動しています。

Android Auto はメディア ブラウザ サービスからの情報を使用して、独自の自動車向け最適化インターフェースを描画するため、Android Auto 用のアクティビティを設計する必要はありません。

アプリのマニフェスト ファイルを構成する

メディア ブラウザ サービスを作成するには、アプリのマニフェスト ファイルを事前に構成しておく必要があります。

メディア ブラウザ サービスを宣言する

Android Auto と Android Automotive OS はどちらも、メディア アイテムをブラウズするために、メディア ブラウザ サービスを介してアプリに接続します。マニフェストでメディア ブラウザ サービスを宣言し、Android Auto と Android Automotive OS がサービスを検出してアプリに接続できるようにします。

次のコード スニペットは、マニフェストでメディア ブラウザ サービスを宣言する方法を示しています。このコードは、Android Automotive OS モジュールのマニフェスト ファイルと、スマートフォン アプリのマニフェスト ファイルに追加する必要があります。

<application>
    ...
    <service android:name=".MyMediaBrowserService"
             android:exported="true">
        <intent-filter>
            <action android:name="android.media.browse.MediaBrowserService"/>
        </intent-filter>
    </service>
    ...
</application>

アプリアイコンを指定する

Android Auto と Android Automotive OS がシステム UI でアプリを表示するためのアプリアイコンを指定する必要があります。次の 2 つのアイコンタイプが必要です。

  • ランチャー アイコン
  • アトリビューション アイコン

ランチャー アイコン

ランチャー アイコンは、ランチャーやアイコンのトレイなどのシステム UI で、アプリを表すアイコンです。次のマニフェスト宣言を使用することで、モバイルアプリのアイコンを使用して自動車メディアアプリを表すように指定できます。

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

モバイルアプリとは異なるアイコンを使用するには、マニフェストでメディア ブラウザ サービスの <service> 要素に android:icon プロパティを設定します。

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

アトリビューション アイコン

図 1. メディアカードのアトリビューション アイコン。

アトリビューション アイコンは、メディアカードなど、メディア コンテンツが優先される場所で使用されます。通知用の小さなアイコンを再利用することを検討してください。 このアイコンはモノクロにする必要があります。次のマニフェスト宣言を使用して、アプリを表すために使用するアイコンを指定できます。

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

メディア ブラウザ サービスを作成する

メディア ブラウザ サービスを作成するには、MediaBrowserServiceCompat クラスを拡張します。これにより、Android Auto と Android Automotive OS の両方で、サービスを使用して次の処理を実行できます。

  • アプリのコンテンツ階層を参照して、ユーザーにメニューを表示する。
  • アプリの MediaSessionCompat オブジェクトのトークンを取得して、音声の再生を制御する。

メディア ブラウザ サービスを使用して、他のクライアントがアプリからメディア コンテンツにアクセスできるようにすることも可能です。このメディア クライアントは、ユーザーのスマートフォン上の他のアプリでも、他のリモート クライアントでもかまいません。

メディア ブラウザ サービスのワークフロー

このセクションでは、一般的なユーザー ワークフローで Android Automotive OS と Android Auto がメディア ブラウザ サービスと対話する仕組みについて説明します。

  1. ユーザーが Android Automotive OS または Android Auto でアプリを起動します。
  2. Android Automotive OS または Android Auto は、onCreate() メソッドを使用してアプリのメディア ブラウザ サービスにコンタクトします。onCreate() メソッドの実装では、MediaSessionCompat オブジェクトとそのコールバック オブジェクトを作成して登録する必要があります。
  3. Android Automotive OS または Android Auto は、サービスの onGetRoot() メソッドを呼び出して、コンテンツ階層のルートのメディア アイテムを取得します。ルートのメディア アイテムは表示されませんが、アプリからより多くのコンテンツを取得するために使用されます。
  4. Android Automotive OS または Android Auto は、サービスの onLoadChildren() メソッドを呼び出して、ルートのメディア アイテムの子を取得します。Android Automotive OS と Android Auto は、これらのメディア アイテムを最上位のコンテンツ アイテムとして表示します。このレベルでシステムが表示するコンテンツについて詳しくは、このページのルートメニューを構造化するをご覧ください。
  5. ユーザーがブラウズ可能なメディア アイテムを選択すると、サービスの onLoadChildren() メソッドが再度呼び出され、選択されたメニュー アイテムの子が取得されます。
  6. ユーザーが再生可能なメディア アイテムを選択すると、Android Automotive OS または Android Auto は、適切なメディア セッション コールバック メソッドを呼び出して、その操作を実行します。
  7. アプリでサポートされていれば、ユーザーはコンテンツを検索することもできます。この場合、Android Automotive OS または Android Auto は、サービスの onSearch() メソッドを呼び出します。

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

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

onGetRoot を実装する

サービスの onGetRoot() メソッドは、コンテンツ階層のルートノードに関する情報を返します。Android Auto と Android Automotive OS はこのルートノードを使用して、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);
}

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

onGetRoot() のパッケージ検証を追加する

サービスの onGetRoot() メソッドが呼び出されると、呼び出し元のパッケージは識別情報をサービスに渡します。サービスはこの情報を使用して、そのパッケージがコンテンツにアクセスできるかどうかを判断できます。たとえば、clientPackageName を許可リストと照合して、パッケージの APK の署名に使用される証明書を確認することにより、アプリのコンテンツへのアクセスを承認されたパッケージのリストに限定できます。パッケージを検証できない場合は、null を返してコンテンツへのアクセスを拒否します。

システムアプリ(Android Auto や Android Automotive OS など)にコンテンツへのアクセスを許可するには、システムアプリが onGetRoot() メソッドを呼び出すときに、常にサービスが null でない BrowserRoot を返す必要があります。Android Automotive OS システムアプリの署名は自動車のメーカーやモデルによって異なる可能性があるため、確実に Android Automotive OS をサポートできるように、すべてのシステムアプリからの接続を許可する必要があります。

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

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
}

このコード スニペットは PackageValidator からの抜粋です。 クラスをご覧ください。サービスの onGetRoot() メソッドのパッケージ検証を実装する方法の詳細な例については、このクラスを参照してください。

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

onLoadChildren() を実装する

Android Auto と Android Automotive OS は、ルートノードのオブジェクトを受け取った後、そのオブジェクトで 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<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);
}

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

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

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

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

関連するルートヒントを読み取るには、次のコードを使用します。

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

特に、階層の値が Android Auto と Android Automotive OS の外部の MediaBrowser 統合によって異なる場合に、これらのヒントの値に基づいてコンテンツ階層の構造のロジックを分岐することもできます。たとえば、ルートの再生可能なアイテムを通常どおり表示する場合、サポートされるフラグヒントの値に従って、ルートのブラウズ可能なアイテムの下にネストできます。

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

  • 各タブアイテムに、モノクロのアイコン(白を推奨)を付けます。
  • 各タブアイテムに、簡潔でわかりやすいラベルを付けます。ラベルを短くすることにより、文字列が切り捨てられる可能性が低くなります。

メディアのアートワークを表示する

メディア アイテムのアートワークは、ContentResolver.SCHEME_CONTENT または ContentResolver.SCHEME_ANDROID_RESOURCE を使用してローカル URI として渡す必要があります。このローカル URI は、アプリのリソースでビットマップまたはベクター型ドローアブルに解決される必要があります。コンテンツ階層内のアイテムを表す MediaDescriptionCompat オブジェクトの場合は、setIconUri() を介して URI を渡します。現在再生中のアイテムを表す MediaMetadataCompat オブジェクトの場合は、putString() を介して、次のいずれかのキーを使用して URI を渡します。

ウェブ URI からアートをダウンロードして、ローカル URI で公開する手順は次のとおりです。より詳細な例については、 実装 openFile() と Universal Android Music の関連メソッド Player サンプルアプリ。

  1. ウェブ URI に対応する content:// URI を作成します。メディア ブラウザ サービスとメディア セッションは、このコンテンツ URI を Android Auto と 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. ContentProvider.openFile() の実装で、対応する URI のファイルが存在するかどうかを確認します。存在しない場合、画像ファイルをダウンロードしてキャッシュに保存します。次のコード スニペットでは 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);
    }
    

コンテンツ プロバイダについて詳しくは、コンテンツ プロバイダの作成をご覧ください。

コンテンツ スタイルを適用する

ブラウズ可能または再生可能なアイテムでコンテンツ階層を構築した後、コンテンツ スタイルを適用できます。自動車内でアイテムがどのように表示されるかは、このコンテンツ スタイルによって決まります。

使用可能なコンテンツ スタイルは次のとおりです。

リストアイテム

このコンテンツ スタイルは、画像よりもタイトルとメタデータを優先します。

グリッド アイテム

このコンテンツ スタイルは、タイトルとメタデータよりも画像を優先します。

デフォルトのコンテンツ スタイルを設定する

サービスの onGetRoot() メソッドの BrowserRoot エクストラ バンドルに特定の定数を含めることにより、メディア アイテムをどのように表示するかに関するグローバルなデフォルトを設定できます。Android Auto と Android Automotive OS はこのバンドルを読み取り、そうした定数を見つけて、適切なスタイルを決定します。

次のエクストラはバンドルでキーとして使用できます。

キーは、次の整数定数値にマッピングして、こうしたアイテムの表示に影響を与えます。

  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM: 対応するアイテムはリストアイテムとして表示されます。
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM: 対応するアイテムはグリッド アイテムとして表示されます。
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM: 対応するアイテムは、「カテゴリ」リストアイテムとして表示されます。これらは通常のリストアイテムと同じですが、アイテムのアイコンは小さい方が見栄えがよいため、周囲に余白を付けます。アイコンは、着色可能なベクター型ドローアブルにする必要があります。このヒントはブラウズ可能なアイテムに対してのみ提供されます。
  • DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM: 対応するアイテムは、「カテゴリ」グリッド アイテムとして表示されます。これらは通常のグリッド アイテムと同じですが、アイテムのアイコンは小さい方が見栄えがよいため、周囲に余白を付けます。アイコンは、着色可能なベクター型ドローアブルにする必要があります。このヒントはブラウズ可能なアイテムに対してのみ提供されます。

次のコード スニペットは、ブラウズ可能なアイテムと再生可能なアイテムのデフォルトのコンテンツ スタイルを、それぞれグリッドとリストに設定する方法を示しています。

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);
}

アイテムごとのコンテンツ スタイルを設定する

Content Style API を使用すると、ブラウズ可能なメディア アイテムの子やメディア アイテム自体の、デフォルトのコンテンツ スタイルをオーバーライドできます。

ブラウズ可能なメディア アイテムの子のデフォルトをオーバーライドするには、そのメディア アイテムの MediaDescription にエクストラ バンドルを作成し、前述のヒントを追加します。DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE はそのアイテムの再生可能な子に適用され、DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE はそのアイテムのブラウズ可能な子に適用されます。

特定のメディア アイテム自体(子ではない)のデフォルトをオーバーライドするには、そのメディア アイテムの MediaDescription にエクストラ バンドルを作成し、キー DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM を使用してヒントを追加します。そのアイテムの表示を指定するには、前述の値と同じ値を使用します。

次のコード スニペットは、自身と子のデフォルトのコンテンツ スタイルをオーバーライドするブラウズ可能な MediaItem を作成する方法を示しています。自身をカテゴリ リストアイテムとして、ブラウズ可能な子をリストアイテムとして、再生可能な子をグリッド アイテムとしてスタイル設定します。

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);
}

タイトルのヒントを使用してアイテムをグループ化する

関連するメディア アイテムをグループ化するには、アイテムごとにヒントを使用します。グループ内のすべてのメディア アイテムで、キー DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE と同一の文字列値を持つマッピングを含む MediaDescription でエクストラ バンドルを宣言する必要があります。グループのタイトルとして使用されるこの文字列をローカライズします。

次のコード スニペットは、サブグループの見出しが "Songs"MediaItem を作成する方法を示しています。

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*/);
}

アプリは、連続したブロックとしてグループ化するメディア アイテムをすべて渡す必要があります。たとえば、メディア アイテムの 2 つのグループ「Songs」と「Albums」をこの順序で表示するために、アプリで 5 つのメディア アイテムを次の順序で渡したとします。

  1. メディア アイテム A(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") を使用)
  2. メディア アイテム B(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums") を使用)
  3. メディア アイテム C(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") を使用)
  4. メディア アイテム D(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") を使用)
  5. メディア アイテム E(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums") を使用)

「Songs」グループと「Albums」グループのメディア アイテムは連続したブロックにならないため、Android Auto と Android Automotive OS はこれを次の 4 つのグループと解釈します。

  • メディア アイテム A を含む「Songs」というグループ 1
  • メディア アイテム B を含む「Albums」というグループ 2
  • メディア アイテム C と D を含む「Songs」というグループ 3
  • メディア アイテム E を含む「Albums」というグループ 4

これらのアイテムを 2 つのグループで表示するには、アプリで次の順序でメディア アイテムを渡す必要があります。

  1. メディア アイテム A(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") を使用)
  2. メディア アイテム C(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") を使用)
  3. メディア アイテム D(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") を使用)
  4. メディア アイテム B(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums") を使用)
  5. メディア アイテム E(extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums") を使用)

追加のメタデータ インジケーターを表示する

追加のメタデータ インジケーターを設定して、メディア ブラウザツリー内に、またはメディアの再生中に、コンテンツが一目でわかる情報を提供できます。ブラウザツリー内では、Android Auto と Android Automotive OS はアイテムに関連付けられたエクストラを読み取り、特定の定数を見つけて、表示するインジケーターを決定します。メディアの再生中には、Android Auto と Android Automotive OS はメディア セッションのメタデータを読み取り、特定の定数を見つけて、表示するインジケーターを決定します。

図 3. メタデータで曲とアーティストを識別する再生ビューと、明示的なコンテンツを示すアイコン。

図 4. 1 つ目のアイテムに未再生コンテンツのドットが表示され、2 つ目のアイテムに再生途中のコンテンツの進行状況バーが表示されているブラウズビュー。

次の定数は、MediaItem ディスクリプション エクストラと MediaMetadata エクストラの両方で使用できます。

  • EXTRA_DOWNLOAD_STATUS: アイテムのダウンロード ステータスを示します。この定数をキーとして使用します。有効な値は次の long 定数のいずれかです。
  • METADATA_KEY_IS_EXPLICIT: アイテムに露骨な表現のコンテンツが含まれているかどうかを示します。アイテムが露骨な表現であることを示すには、この定数をキーとして使用し、long 型の METADATA_VALUE_ATTRIBUTE_PRESENT を値として使用します。

次の定数は、MediaItem ディスクリプション エクストラでしか使用できません。

ユーザーがメディア ブラウズ ツリーをブラウジングしているときにインジケーターを表示するには、上の定数を 1 つ以上含むエクストラ バンドルを作成し、そのバンドルを MediaDescription.Builder.setExtras() メソッドに渡します。

次のコード スニペットは、70% 完了した露骨な表現のメディア アイテムのインジケーターを表示する方法を示しています。

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 */);

現在再生中のメディア アイテムのインジケーターを表示するには、METADATA_KEY_IS_EXPLICIT または EXTRA_DOWNLOAD_STATUSLong 形式の値を mediaSessionMediaMetadataCompat で宣言します。再生ビューに DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS または DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE インジケーターを表示することはできません。

次のコード スニペットは、再生ビュー内の現在の曲が露骨な表現であり、かつダウンロードされていることを示す方法を示しています。

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());

コンテンツの再生中にブラウズビューの進行状況バーを更新する

前述のように、DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE エクストラを使用することで、再生途中のコンテンツの進行状況バーをブラウズビューに表示できます。ただし、ユーザーが Android Auto または Android Automotive OS から再生途中のコンテンツを再生し続けると、時間の経過とともにインジケーターが不正確になります。

Android Auto と Android Automotive OS で進行状況バーを最新の状態に保つために、MediaMetadataCompatPlaybackStateCompat で追加情報を提供し、ブラウズビューで進行中のコンテンツをメディア アイテムにリンクできます。メディア アイテムで進行状況バーを自動更新するには、次の要件を満たす必要があります。

次のコード スニペットは、現在再生中のアイテムがブラウズビューのアイテムにリンクされていることを示す方法を表しています。

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());

図 5. ユーザーの音声検索に関連するメディア アイテムを表示できる「検索結果」オプションが表示された再生ビュー

アプリは、ユーザーが検索クエリを開始したときに表示されるコンテキスト検索結果を提供できます。Android Auto と Android Automotive OS は、検索結果を、検索クエリ インターフェースや、セッション内で前に実行されたクエリにピボットされたアフォーダンスに表示します。詳細については、このガイドの音声操作をサポートするをご覧ください。

ブラウズ可能な検索結果を表示するには、定数キー BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED をサービスの onGetRoot() メソッドのエクストラ バンドルに含めて、ブール値 true にマッピングします。

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

検索結果の提供を開始するには、メディア ブラウザ サービスの onSearch() メソッドをオーバーライドします。Android Auto と Android Automotive OS は、ユーザーが検索クエリ インターフェースまたは「検索結果」アフォーダンスを呼び出すたびに、ユーザーの検索キーワードをこのメソッドに転送します。

サービスの onSearch() メソッドによる検索結果をタイトル アイテムで整理し、よりブラウズしやすくすることが可能です。たとえば、アプリで音楽を再生する場合、アルバム、アーティスト、曲ごとに検索結果をまとめます。

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

カスタム ブラウズ アクション

1 つのカスタム ブラウズ アクション

図 6. 1 つのカスタム ブラウズ アクション

カスタム ブラウズ アクションを使用して、車のメディアアプリでアプリの MediaItem オブジェクトにカスタムのアイコンとラベルを追加し、それらのアクションのユーザー操作を処理することができます。これにより、[Download]、[Add to Queue]、[Play Radio]、[Favorite]、[Remove] といったアクションの追加など、さまざまな方法でメディアアプリの機能を拡張できます。

カスタム ブラウズ アクションのオーバーフロー メニュー。

図 7. カスタム ブラウズ アクションのオーバーフロー

OEM に表示が許可されているカスタム アクションの数より多い場合は、オーバーフロー メニューがユーザーに表示されます。

仕組み

各カスタム ブラウズ アクションは以下で定義されます。

  • アクション ID(一意の文字列識別子)
  • アクション ラベル(ユーザーに表示されるテキスト)
  • アクション アイコンの URI(色合いを調整できるベクター型ドローアブル)

カスタム ブラウズ アクションのリストは、BrowseRoot の一部としてグローバルに定義します。定義したら、各 MediaItem. にこれらのアクションのサブセットをアタッチできます。

ユーザーがカスタム ブラウズ アクションを操作すると、アプリが onCustomAction() でコールバックを受け取ります。その後、必要に応じて、MediaItem に対してアクションを処理し、アクション リストを更新することができます。これは、[Favorite]、[Download] などのステートフルなアクションに役立ちます。[Play Radio] など、更新が必要ないアクションでは、アクション リストを更新する必要はありません。

ブラウズノード ルートのカスタム ブラウズ アクション。

図 8. カスタム ブラウズ アクションのツールバー

ブラウズノードのルートにカスタム ブラウズ アクションをアタッチすることもできます。アタッチしたアクションは、メイン ツールバーの下のセカンダリ ツールバーに表示されます。

カスタム ブラウズ アクションを実装する方法

以下に、プロジェクトにカスタム ブラウズ アクションを追加する手順を示します。

  1. MediaBrowserServiceCompat の実装で次の 2 つのメソッドをオーバーライドします。
  2. 実行時にアクションの上限を解析します。
    • onGetRoot() で、rootHints Bundle にキー BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT を使用して MediaItem ごとに許可されるアクションの上限数を取得します。上限 0 は、この機能がシステムでサポートされていないことを示します。
  3. カスタム ブラウズ アクションのグローバル リストを作成します。
    • アクションごとに、次のキーを使って Bundle オブジェクトを作成します。 * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID: アクション ID * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL: アクション ラベル * EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI: アクション アイコンの URI * アクションの Bundle オブジェクトすべてをリストに追加します。
  4. BrowseRoot にグローバル リストを追加します。
  5. MediaItem オブジェクトにアクションを追加します。
    • MediaItem オブジェクトにアクションを追加するには、キー DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST を使用して MediaDescriptionCompat エクストラにアクション ID のリストを含めます。このリストは、BrowseRoot で定義したアクションのグローバル リストのサブセットにする必要があります。
  6. アクションを処理し、進行状況または結果を返します。
    • onCustomAction で、アクション ID と必要なその他のデータに基づいてアクションを処理します。アクションをトリガーした MediaItem の ID は、キー EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID を使用してエクストラから取得できます。
    • MediaItem に対するアクションのリストを更新するには、進行状況または結果バンドルにキー EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM を含めます。

次に、カスタム ブラウズ アクションを使い始めるにあたって BrowserServiceCompat に加えることができる変更について説明します。

BrowserServiceCompat をオーバーライドする

MediaBrowserServiceCompat で以下のメソッドをオーバーライドする必要があります。

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

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

アクション数の上限を解析する

サポートされているカスタム ブラウズ アクションの数を確認する必要があります。

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

カスタム ブラウズ アクションを作成する

各アクションは個別の Bundle にパックする必要があります。

  • アクション ID
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID,
                    "<ACTION_ID>")
    
  • アクション ラベル
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL,
                    "<ACTION_LABEL>")
    
  • アクション アイコンの URI
    bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI,
                    "<ACTION_ICON_URI>")
    

Parceable ArrayList にカスタム ブラウズ アクションを追加する

カスタム ブラウズ アクションの Bundle オブジェクトをすべて 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;
}

ブラウズルートにカスタム ブラウズ アクション リストを追加する

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

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);

onCustomAction の結果を作成する

  • Bundle extras から mediaId を解析します。
    @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);
    }
    
  • 非同期の結果の場合は、result.detach() で結果をデタッチする
  • 結果バンドルを作成する
    • ユーザーへのメッセージ
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE,
                mContext.getString(stringRes))
      
    • アイテムの更新(アイテム内のアクションの更新に使用)
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
      
    • 再生ビューを開く
      //Shows user the PBV without changing the playback state
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
      
    • ブラウズノードの更新
      //Change current browse node to mediaId
      mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
      
  • エラーの場合は、result.sendError(resultBundle). を呼び出す
  • 進行状況の更新の場合は、result.sendProgressUpdate(resultBundle) を呼び出す
  • result.sendResult(resultBundle) を呼び出して終了する

アクションの状態を更新する

EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM キーと result.sendProgressUpdate(resultBundle) メソッドを使用して、MediaItem を更新し、アクションの新しい状態を反映できます。これにより、アクションの進行状況と結果に関するリアルタイムのフィードバックをユーザーに提供できます。

例: Download アクション

以下に、この機能を使用して 3 つの状態を持つ Download アクションを実装する方法の例を示します。

  1. Download: アクションの最初の状態です。このアクションが選択されたら、[Downloading] に切り替え、sendProgressUpdate を呼び出して UI を更新できます。
  2. Downloading: ダウンロード中であることを示す状態です。この状態を使用して、進行状況バーなどのインジケーターをユーザーに表示できます。
  3. Downloaded: ダウンロードが完了したことを示す状態です。ダウンロードが終了したら、[Downloading] を [Downloaded] に切り替え、EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM キーを使用して sendResult を呼び出し、アイテムの更新が必要であることを示します。また、EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE キーを使用してユーザーに完了メッセージを表示することもできます。

この方法により、ダウンロード プロセスとダウンロードの現在の状態についてユーザーに明確なフィードバックを提供できます。25%、50%、75% のダウンロード状態を示すアイコンを使用して、より詳しい情報を追加することもできます。

例: Favorite アクション

もう一つの例は、2 つの状態を持つ Favorite アクションです。

  1. Favorite: ユーザーのお気に入りリストにないアイテムに対して表示されるアクションです。このアクションが選択されたら、[Favorited] に切り替え、EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM キーで sendResult を呼び出して UI を更新できます。
  2. Favorited: ユーザーのお気に入りリストにあるアイテムに対して表示されるアクションです。このアクションが選択されたら、[Favorite] に切り替え、EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM キーで sendResult を呼び出して UI を更新できます。

この方法により、ユーザーはお気に入りのアイテムを明確かつ一貫した方法で管理できます。

これらの例を通して、カスタム ブラウズ アクションの柔軟性と、このアクションを使用してリアルタイムのフィードバックを含むさまざまな機能を実装する方法を把握し、車のメディアアプリのユーザー エクスペリエンス向上につなげることができます。

この機能の完全な実装例については、TestMediaApp プロジェクトをご覧ください。

再生コントロールを有効にする

Android Auto と Android Automotive OS は、サービスの MediaSessionCompat を介して再生コントロール コマンドを送信します。セッションを登録し、関連するコールバック メソッドを実装する必要があります。

メディア セッションを登録する

メディア ブラウザ サービスの onCreate() メソッドで MediaSessionCompat を作成し、setSessionToken() を呼び出してメディア セッションを登録します。

次のコード スニペットは、メディア セッションを作成して登録する方法を示しています。

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());
    ...
}

メディア セッション オブジェクトを作成する際に、再生コントロール リクエストの処理に使用されるコールバック オブジェクトを設定します。このコールバック オブジェクトを作成するには、アプリの MediaSessionCompat.Callback クラスの実装を提供します。次のセクションで、このオブジェクトを実装する方法を説明します。

再生コマンドを実装する

ユーザーがアプリにメディア アイテムの再生をリクエストすると、Android Automotive OS と Android Auto は、アプリの MediaSessionCompat オブジェクト(アプリのメディア ブラウザ サービスから取得したオブジェクト)の MediaSessionCompat.Callback クラスを使用します。ユーザーがコンテンツ再生を制御する場合(再生の一時停止や次のトラックへのスキップなど)、Android Auto と Android Automotive OS は、コールバック オブジェクトのメソッドのひとつを呼び出します。

コンテンツ再生を処理するには、アプリで MediaSessionCompat.Callback 抽象クラスを拡張して、アプリがサポートするメソッドを実装する必要があります。

アプリが提供するコンテンツの種類に応じて、以下のコールバック メソッドをすべて実装します。

onPrepare()
メディアソースが変更されたときに呼び出されます。Android Automotive OS は、起動直後にもこのメソッドを呼び出します。メディアアプリは、このメソッドを実装する必要があります。
onPlay()
ユーザーが特定のアイテムを選択せずに再生を選択したときに呼び出されます。アプリはデフォルトのコンテンツを再生する必要があります。また、onPause() で再生が一時停止されていた場合、アプリは再生を再開します。

注: Android Automotive OS または Android Auto がメディア ブラウザ サービスに接続したとき、アプリは自動的に音楽の再生を開始してはなりません。詳細については、最初の PlaybackState を設定するをご覧ください。

onPlayFromMediaId()
ユーザーが特定のアイテムの再生を選択したときに呼び出されます。このメソッドには、メディア ブラウザ サービスがコンテンツ階層内のメディア アイテムに割り当てた ID が渡されます。
onPlayFromSearch()
ユーザーが検索クエリからの再生を選択したときに呼び出されます。アプリは、渡された検索文字列に基づいて適切な選択を行う必要があります。
onPause()
ユーザーが再生の一時停止を選択したときに呼び出されます。
onSkipToNext()
ユーザーが次のアイテムへのスキップを選択したときに呼び出されます。
onSkipToPrevious()
ユーザーが前のアイテムへのスキップを選択したときに呼び出されます。
onStop()
ユーザーが再生の停止を選択したときに呼び出されます。

アプリでこれらのメソッドをオーバーライドして、必要な機能を提供します。メソッドの機能がアプリでサポートされていなければ、そのメソッドを実装する必要はありません。たとえば、アプリがライブ ストリーム(スポーツ放送など)を再生する場合、onSkipToNext() メソッドを実装する必要はありません。代わりに onSkipToNext() のデフォルト実装を使用できます。

アプリには、車載スピーカーからコンテンツを再生するための特別なロジックは不要です。アプリは、コンテンツの再生リクエストを受信したとき、ユーザーのスマートフォンのスピーカーまたはヘッドフォンでコンテンツを再生する場合と同じ方法で音声を再生できます。Android Auto と Android Automotive OS は、音声コンテンツを車載システムに自動的に送信して、車載スピーカーで再生します。

音声コンテンツの再生の詳細については、MediaPlayer の概要オーディオ アプリの概要、ExoPlayer の概要をご覧ください。

標準の再生操作を設定する

Android Auto と Android Automotive OS は、PlaybackStateCompat オブジェクトで有効化されている操作に基づいて再生コントロールを表示します。

デフォルトでは、アプリは次の操作をサポートする必要があります。

アプリのコンテンツに関連する場合、アプリは次の操作もサポートできます。

さらに、ユーザーに表示できる再生キューの作成も可能ですが、必須ではありません。そのためには、setQueue() メソッドと setQueueTitle() メソッドを呼び出し、ACTION_SKIP_TO_QUEUE_ITEM 操作を有効にして、コールバック onSkipToQueueItem() を定義します。

また、現在再生中の曲のインジケーターである「この曲なに?」アイコンのサポートも追加します。そのためには、setActiveQueueItemId() メソッドを呼び出し、現在再生しているアイテムの ID をキューに渡します。キューが変更されるたびに setActiveQueueItemId() を更新する必要があります。

Android Auto と Android Automotive OS は、有効化された操作ごとにボタンを表示し、再生キューを表示します。ボタンがクリックされると、システムは MediaSessionCompat.Callback から対応するコールバックを呼び出します。

未使用のスペースを予約する

Android Auto と Android Automotive OS は、ACTION_SKIP_TO_PREVIOUS アクションと ACTION_SKIP_TO_NEXT アクション用に UI のスペースを予約します。アプリがこれらの機能のいずれかをサポートしていない場合、Android Auto と Android Automotive OS はこのスペースを使用して、デベロッパーが作成したカスタム アクションを表示します。

このスペースにカスタム アクションを表示しない場合、アプリが対応する機能をサポートしていないときは常に Android Auto と Android Automotive OS がスペースを空白のままにしておくように、スペースを予約できます。そのためには、予約された各機能に対応する定数を含むエクストラ バンドルを指定して、setExtras() メソッドを呼び出します。SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXTACTION_SKIP_TO_NEXT に対応し、SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREVACTION_SKIP_TO_PREVIOUS に対応します。これらの定数をバンドルのキーとして使用し、値にブール値 true を使用します。

最初の PlaybackState を設定する

Android Auto と Android Automotive OS がメディア ブラウザ サービスと通信する際、メディア セッションは PlaybackStateCompat を使用してコンテンツ再生のステータスをやり取りします。Android Automotive OS または Android Auto がメディア ブラウザ サービスに接続したとき、アプリは自動的に音楽の再生を開始してはなりません。Android Auto と Android Automotive OS に処理をまかせ、車の状態またはユーザーの操作に基づいて再生を再開または開始してください。

そのためには、メディア セッションの最初の PlaybackStateCompat を、STATE_STOPPEDSTATE_PAUSEDSTATE_NONESTATE_ERROR のいずれかに設定します。

Android Auto と Android Automotive OS のメディア セッションは運転している間だけ持続するため、ユーザーはこれらのセッションの開始と停止を頻繁に行うことになります。運転再開時のエクスペリエンスをよりシームレスなものにするには、ユーザーの以前のセッションの状態(最後に再生したメディア アイテム、PlaybackStateCompat、キューなど)を追跡して、メディアアプリが再開リクエストを受け取ったときに、再生を中断したところから自動的に再開できるようにします。

カスタム再生操作を追加する

カスタム再生操作を追加して、メディアアプリがサポートする追加の操作を表示できます。スペースに余裕がある(予約されていない)場合、Android はトランスポート コントロールにカスタム操作を追加します。それ以外の場合、カスタム操作はオーバーフロー メニューに表示されます。カスタム アクションは、PlaybackStateCompat に追加された順序で表示されます。

カスタム操作は、標準操作とは異なる動作を提供するために使用します。標準の操作の置き換えや複製には使用しないでください。

これらの操作は、PlaybackStateCompat.Builder クラスの addCustomAction() メソッドを使用して追加できます。

次のコード スニペットは、カスタムの「ラジオ チャンネルの開始」操作を追加する方法を示しています。

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());

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

カスタム操作を作成した場合、メディア セッションは onCustomAction() メソッドをオーバーライドすることで操作に応答できます。

次のコード スニペットは、アプリが「ラジオ チャンネルの開始」操作に応答する方法を示しています。

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

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

カスタム操作のアイコン

作成する各カスタム操作には、アイコン リソースが必要です。車載アプリはさまざまな画面サイズと密度で実行される可能性があるので、提供するアイコンはベクター型ドローアブルでなければなりません。ベクター型ドローアブルを使用すると、細部を犠牲にすることなくアセットを拡大 / 縮小できます。また、低い解像度でも、アイコンの端と隅をピクセル境界に簡単に合わせられます。

カスタム操作がステートフルである場合(再生設定のオンとオフを切り替える場合など)は、ユーザーが操作を選択したときに変化に気付けるように、状態ごとに別々のアイコンを提供します。

無効な操作に対して代替アイコン スタイルを提供する

現在のコンテキストでカスタム操作を使用できない場合は、カスタム操作のアイコンを、操作が無効であることを示す代替アイコンに切り替えます。

図 6. カスタム操作がオフになっていることを示すアイコン スタイルのサンプル。

音声形式を指定する

現在再生中のメディアが特別な音声形式を使用していることを示すため、この機能に対応する自動車に表示するアイコンを指定できます。KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URIKEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI は(MediaSession.setMetadata() に渡す)現在再生中のメディア アイテムのエクストラ バンドルで設定できます。さまざまなレイアウトに対応できるよう、これらのエクストラは必ず両方とも設定してください。

さらに、KEY_IMMERSIVE_AUDIO のエクストラを設定すると、これが没入感のある音声であることを自動車の OEM に伝えることができます。自動車の OEM は、没入感のあるコンテンツを妨げる可能性があるオーディオ エフェクトを適用するかどうかを非常に慎重に判断する必要があります。

現在再生中のメディア アイテムのサブタイトル、説明、またはその両方が他のメディア アイテムへのリンクになるように設定できます。これにより、ユーザーは関連するアイテムにすばやくジャンプできます。たとえば、同じアーティストの他の曲や、ポッドキャストの他のエピソードなどにジャンプできます。車がこの機能をサポートしている場合、ユーザーはリンクをタップしてそのコンテンツを参照できます。

リンクを追加するには、KEY_SUBTITLE_LINK_MEDIA_ID メタデータ(サブタイトルからリンクする場合)または KEY_DESCRIPTION_LINK_MEDIA_ID(説明からリンクする場合)を設定します。詳しくは、これらのメタデータ フィールドの参照ドキュメントをご覧ください。

音声操作をサポートする

メディアアプリでは、ドライバーが注意散漫になる危険を最小限に抑える安全で便利なエクスペリエンスを提供するために、音声操作をサポートする必要があります。たとえば、アプリがあるメディア アイテムを再生しているときに、ユーザーが車載ディスプレイを見たり触れたりせずに、声(「[曲名] を再生して」など)で別の曲の再生をアプリに指示できるようにします。ユーザーは、ハンドル上の該当するボタンをクリックするか起動ワード(「OK Google」)を言うことで、クエリを開始できます。

Android Auto または Android Automotive OS が音声操作を検出して解釈すると、その音声操作は onPlayFromSearch() を介してアプリに配信されます。このコールバックを受け取ると、アプリは query 文字列に一致するコンテンツを見つけて再生を開始します。

ユーザーは、ジャンル、アーティスト、アルバム、曲名、ラジオ局、プレイリストなど、クエリにさまざまなカテゴリの用語を指定できます。検索のサポートを構築する際は、アプリに適したすべてのカテゴリを考慮してください。Android Auto または Android Automotive OS は、クエリが特定のカテゴリに当てはまることを検出すると、extras パラメータにエクストラを付加します。送信される可能性のあるエクストラは次のとおりです。

空の query 文字列を考慮に入れます。ユーザーが検索キーワードを指定しなかった場合、Android Auto または Android Automotive OS によってこの文字列が送信されることがあります(ユーザーが「音楽を再生して」と言った場合など)。アプリはその場合、最近再生したトラックまたは新しく提案されたトラックを開始するよう選択できます。

検索の処理に時間がかかる場合は、onPlayFromSearch() でブロックしないでください。代わりに、再生状態を STATE_CONNECTING に設定し、非同期スレッドで検索を行います。

再生が開始されたら、メディア セッションのキューに関連コンテンツを入力することを検討してください。たとえば、ユーザーがアルバムの再生をリクエストした場合、アプリはそのアルバムのトラックリストをキューに入力する可能性があります。ブラウズ可能な検索結果のサポートを実装することも検討してください。これにより、ユーザーはクエリと一致する別のトラックを選択できます。

「再生」クエリに加えて、Android Auto と Android Automotive OS は、「音楽を一時停止」や「次の曲」のような再生制御のための音声クエリを認識します。また、これらのコマンドを onPause()onSkipToNext() のような該当するメディア セッション コールバックと照合します。

音声対応の再生操作をアプリに実装する方法の詳細な例については、Google アシスタントとメディアアプリをご覧ください。

注意散漫に対する安全保護対策を実装する

Android Auto の使用中、ユーザーのスマートフォンは車載スピーカーに接続されているため、ドライバーの注意散漫を防止するために追加の予防措置を講じる必要があります。

車内でのアラームを抑制する

Android Auto メディアアプリは、ユーザーが再生を開始した場合(再生ボタンを押した場合など)を除き、車載スピーカーで音声の再生を開始してはなりません。メディアアプリでユーザーがアラームをスケジュールした場合であっても、それによって車載スピーカーで音楽を再生してはいけません。

この要件を満たすために、アプリは音声を再生する前に、シグナルとして CarConnection を使用できます。アプリは、自動車の接続タイプLiveData を監視し、CONNECTION_TYPE_PROJECTION と合致するかどうかを確認することで、スマートフォンが車載ディスプレイに投影されているかどうかを確認できます。

ユーザーのスマートフォンが投影されている場合、アラームをサポートするメディアアプリは次のいずれかを行う必要があります。

  • アラームを無効にする。
  • STREAM_ALARM でアラームを再生し、アラームを無効にする UI をスマートフォン画面に表示する。

メディア広告を処理する

デフォルトでは、Android Auto は、音声再生セッション中にメディア メタデータが変更されると、通知を表示します。メディアアプリが音楽の再生から広告の掲載に切り替えたときに通知を表示すると、ユーザーの注意が散漫になります。この場合に Android Auto が通知を表示しないようにするには、次のコード スニペットに示すように、メディア メタデータのキー METADATA_KEY_IS_ADVERTISEMENTMETADATA_VALUE_ATTRIBUTE_PRESENT に設定する必要があります。

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());
}

一般的なエラーを処理する

アプリでエラーが発生した場合は、再生状態を STATE_ERROR に設定し、setErrorMessage() メソッドを使用してエラー メッセージを提供します。エラー メッセージの設定時に使用できるエラーコードの一覧については、PlaybackStateCompat をご覧ください。エラー メッセージはユーザー向けであるため、ユーザーの現在のロケールに合わせてローカライズする必要があります。これにより、Android Auto と Android Automotive OS はユーザーにエラー メッセージを表示できます。

たとえば、ユーザーの現在の地域でコンテンツを利用できない場合は、エラー メッセージの設定時に ERROR_CODE_NOT_AVAILABLE_IN_REGION エラーコードを使用できます。

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());

エラー状態の詳細については、メディア セッションの使用: 状態とエラーをご覧ください。

Android Auto ユーザーがエラーを解決するためにスマートフォン アプリを開く必要がある場合は、メッセージ内でユーザーにその情報を提供します。たとえば、エラー メッセージは、「ログインしてください」ではなく、「[アプリ名] にログインしてください」とします。

その他のリソース