Android Auto と Android Automotive OS は、メディアアプリのコンテンツを自動車に乗っているユーザーに届けるのに役立ちます。自動車向けメディアアプリは、Android Auto と Android Automotive OS(またはメディア ブラウザ機能を持つ別のアプリ)がコンテンツを見つけて表示できるよう、メディア ブラウザ サービスを提供する必要があります。
このガイドは、音声を再生するメディアアプリがスマートフォンにすでにインストールされており、そのメディアアプリが Android メディアアプリ アーキテクチャに準拠していることを前提としています。
このガイドでは、アプリが Android Auto または Android Automotive OS 上で動作するうえで必須のコンポーネントである MediaBrowserService
と MediaSession
について説明します。核となるメディア インフラストラクチャが完成すると、Android Auto のサポートおよび Android Automotive OS のサポートをメディアアプリに追加できるようになります。
始める前に
- Android メディア API のドキュメントを確認してください。
- Android Automotive OS アプリの設計ガイドラインと Android Auto アプリの設計ガイドラインを確認してください。
- このセクションに記載されている主な用語と概念を確認してください。
主な用語と概念
- メディア ブラウザ サービス
MediaBrowserServiceCompat
API に準拠するメディアアプリによって実装される Android サービス。アプリはこのサービスを使用してコンテンツを公開します。- メディア ブラウザ
- メディア ブラウザ サービスを見つけてコンテンツを表示するためにメディアアプリで使用される API。Android Auto と Android Automotive OS は、アプリのメディア ブラウザ サービスを見つけるためにメディア ブラウザを使用します。
- メディア アイテム
メディア ブラウザはそのコンテンツを
MediaItem
オブジェクトのツリーにまとめます。メディア アイテムには、次の 2 つのフラグのいずれかまたは両方を含めることができます。- 再生可能: このフラグは、アイテムがコンテンツ ツリーのリーフであることを示します。アイテムは、単一のサウンド ストリーム(アルバムの曲、オーディオ ブックのチャプター、ポッドキャストのエピソードなど)を表します。
- ブラウズ可能: このフラグは、アイテムがコンテンツ ツリーのノードであり、アイテムに子があることを示します(たとえば、アイテムがアルバムを表し、その子がアルバムの曲である場合など)。
ブラウズと再生の両方が可能なメディア アイテムは、プレイリストのような役割を果たします。アイテム自体を選択してそのすべての子を再生することも、アイテムの子をブラウズすることもできます。
- 自動車向け最適化
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 にアプリを表示するために使用できるアプリアイコンを指定する必要があります。
次のマニフェスト宣言を使用して、アプリを表すために使用するアイコンを指定できます。
<!--The android:icon attribute is used by Android Automotive OS-->
<application
...
android:icon="@mipmap/ic_launcher">
...
<!--Used by Android Auto-->
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_auto_icon" />
...
<application>
メディア ブラウザ サービスを作成する
メディア ブラウザ サービスを作成するには、MediaBrowserServiceCompat
クラスを拡張します。これにより、Android Auto と Android Automotive OS の両方で、サービスを使用して次の処理を実行することができます。
- ユーザーにメニューを表示するために、アプリのコンテンツ階層を参照する。
- 音声の再生を制御するために、アプリの
MediaSessionCompat
オブジェクトのトークンを取得する。
メディア ブラウザ サービスのワークフロー
このセクションでは、一般的なユーザー ワークフローで Android Automotive OS と Android Auto がメディア ブラウザ サービスと対話する仕組みについて説明します。
- ユーザーが Android Automotive OS または Android Auto でアプリを起動します。
- Android Automotive OS または Android Auto は、
onCreate()
メソッドを使用してアプリのメディア ブラウザ サービスにコンタクトします。onCreate()
メソッドの実装では、MediaSessionCompat
オブジェクトとそのコールバック オブジェクトを作成して登録する必要があります。 - Android Automotive OS または Android Auto は、サービスの
onGetRoot()
メソッドを呼び出して、コンテンツ階層のルートのメディア アイテムを取得します。ルートのメディア アイテムは表示されませんが、アプリからより多くのコンテンツを取得するために使用されます。 - Android Automotive OS または Android Auto は、サービスの
onLoadChildren()
メソッドを呼び出して、ルートのメディア アイテムの子を取得します。Android Automotive OS と Android Auto は、これらのメディア アイテムを最上位のコンテンツ アイテムとして表示します。 最上位のコンテンツ アイテムはブラウズ可能でなければなりません。 - ユーザーがブラウズ可能なメディア アイテムを選択すると、サービスの
onLoadChildren()
メソッドが再度呼び出され、選択されたメニュー アイテムの子が取得されます。 - ユーザーが再生可能なメディア アイテムを選択すると、Android Automotive OS または Android Auto は、適切なメディア セッション コールバック メソッドを呼び出して、その操作を実行します。
- アプリでサポートされていれば、ユーザーはコンテンツを検索することもできます。この場合、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); }
このメソッドの詳細な例については、GitHub の Universal Android Music Player サンプルアプリの onGetRoot()
メソッドをご覧ください。
onGetRoot() のパッケージ検証を追加する
サービスの onGetRoot()
メソッドが呼び出されると、呼び出し元のパッケージは識別情報をサービスに渡します。サービスはこの情報を使用して、そのパッケージがコンテンツにアクセスできるかどうかを判断できます。たとえば、clientPackageName
をホワイトリストと照合して、パッケージの APK の署名に使用される証明書を確認することにより、アプリのコンテンツへのアクセスを承認済みパッケージのリストに限定できます。パッケージを検証できない場合は、null
を返してコンテンツへのアクセスを拒否します。
システムアプリ(Android Auto や Android Automotive OS など)にコンテンツへのアクセスを許可するには、システムアプリが onGetRoot()
メソッドを呼び出すときに、常にサービスが null でない BrowserRoot
を返す必要があります。
次のコード スニペットは、呼び出し元のパッケージがシステムアプリであることをサービスが検証する方法を示しています。
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()
メソッドのパッケージ検証を実装する方法の詳細な例については、このクラスを参照してください。
onLoadChildren() を実装する
Android Auto と Android Automotive OS は、ルートノードのオブジェクトを受け取った後、そのオブジェクトで onLoadChildren()
を呼び出してその子を取得することにより、最上位メニューを構築します。クライアント アプリは、子ノードのオブジェクトを使用してこの同じメソッドを呼び出すことにより、サブメニューを構築します。
コンテンツ階層の各ノードは、MediaBrowserCompat.MediaItem
オブジェクトで表されます。これらの各メディア アイテムは、一意の ID 文字列によって識別されます。クライアント アプリは、このような ID 文字列を不透明なトークンとして扱います。クライアント アプリがサブメニューのブラウズまたはメディア アイテムの再生を行いたいときは、トークンを渡します。トークンを適切なメディア アイテムに関連付ける処理はアプリが行います。
注: Android Auto と Android Automotive OS には、メニューの各レベルで表示できるメディア アイテムの数に厳格な制限があります。この制限により、ドライバーが注意散漫になる危険が最小限に抑えられ、音声コマンドによるアプリの操作が容易になります。 詳細については、コンテンツの詳細のブラウズと Android Auto アプリドロワーをご覧ください。
次のコード スニペットは、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 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 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 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 children of that menu in the mediaItems list } result.sendResult(mediaItems); }
このメソッドの完全な例については、GitHub の Universal Android Music Player サンプルアプリの onLoadChildren()
メソッドをご覧ください。
コンテンツ スタイルを適用する
ブラウズ可能または再生可能なアイテムでコンテンツ階層を構築した後、コンテンツ スタイルを適用できます。自動車内でアイテムがどのように表示されるかは、このコンテンツ スタイルによって決まります。
使用可能なコンテンツ スタイルは次のとおりです。
- リストアイテム
-
このコンテンツ スタイルは、画像よりもタイトルとメタデータを優先します。
- グリッド アイテム
-
このコンテンツ スタイルは、タイトルとメタデータよりも画像を優先します。
デフォルトのコンテンツ スタイルを設定する
サービスの onGetRoot()
メソッドの BrowserRoot
extras バンドルに特定の定数を含めることにより、メディア アイテムをどのように表示するかに関するグローバルなデフォルトを設定できます。Android Auto と Android Automotive OS は、ブラウザツリー内の各アイテムに関連付けられた extras バンドル内の定数を読み取って、適切なスタイルを決定します。
アプリでこのような定数を宣言するには、次のコードを使用します。
Kotlin
/** Declares that ContentStyle is supported */ val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED" /** * Bundle extra indicating the presentation hint for playable media items. */ val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT" /** * Bundle extra indicating the presentation hint for browsable media items. */ val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT" /** * Specifies the corresponding items should be presented as lists. */ val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1 /** * Specifies that the corresponding items should be presented as grids. */ val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
Java
/** Declares that ContentStyle is supported */ public static final String CONTENT_STYLE_SUPPORTED =; "android.media.browse.CONTENT_STYLE_SUPPORTED"; /** * Bundle extra indicating the presentation hint for playable media items. */ public static final String CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"; /** * Bundle extra indicating the presentation hint for browsable media items. */ public static final String CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"; /** * Specifies the corresponding items should be presented as lists. */ public static final int CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1; /** * Specifies that the corresponding items should be presented as grids. */ public static final int CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2;
上記の定数を宣言した後、サービスの onGetRoot()
メソッドの extras バンドルにそれらを含めて、デフォルトのコンテンツ スタイルを設定します。次のコード スニペットは、ブラウズ可能なアイテムと再生可能なアイテムのデフォルトのコンテンツ スタイルを、それぞれグリッドとリストに設定する方法を示しています。
Kotlin
@Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean(CONTENT_STYLE_SUPPORTED, true) extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE) extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE) return BrowserRoot(ROOT_ID, extras) }
Java
@Nullable @Override public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean(CONTENT_STYLE_SUPPORTED, true); extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE); extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE); return new BrowserRoot(ROOT_ID, extras); }
アイテムごとのコンテンツ スタイルを設定する
Content Style API を使用すると、ブラウズ可能なメディア アイテムの子のデフォルトのコンテンツ スタイルをオーバーライドできます。デフォルトをオーバーライドするには、メディア アイテムの MediaDescription
に extras バンドルを作成します。
次のコード スニペットは、デフォルトのコンテンツ スタイルをオーバーライドするブラウズ可能な MediaItem
を作成する方法を示しています。
Kotlin
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(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE) extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
Java
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(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE); extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
タイトルのヒントを使用してアイテムをグループ化する
関連するメディア アイテムをグループ化するには、アイテムごとにヒントを使用します。グループ内のすべてのメディア アイテムで、同じ文字列を使用する MediaDescription
で extras バンドルを宣言する必要があります。この文字列はグループのタイトルとして使用され、ローカライズが可能です。
次のコード スニペットは、サブグループの見出しが "Songs"
の MediaItem
を作成する方法を示しています。
Kotlin
val EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT" 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(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
Java
public static final String EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"; 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(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
アプリは、連続したブロックとしてグループ化するメディア アイテムをすべて渡す必要があります。たとえば、メディア アイテムの 2 つのグループ「Songs」と「Albums」をこの順序で表示したいとします。そこで、アプリで 5 つのメディア アイテムを次の順序で渡しました。
- メディア アイテム A(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
を使用) - メディア アイテム B(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
を使用) - メディア アイテム C(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
を使用) - メディア アイテム D(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
を使用) - メディア アイテム E(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
を使用)
「Songs」グループと「Albums」グループのメディア アイテムは連続したブロックにならないため、Android Auto と Android Automotive OS はこれを次の 4 つのグループと解釈します。
- メディア アイテム A を含む「Songs」というグループ 1
- メディア アイテム B を含む「Albums」というグループ 2
- メディア アイテム C と D を含む「Songs」というグループ 3
- メディア アイテム E を含む「Albums」というグループ 4
これらのアイテムを 2 つのグループで表示するには、アプリで次の順序でメディア アイテムを渡します。
- メディア アイテム A(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
を使用) - メディア アイテム C(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
を使用) - メディア アイテム D(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
を使用) - メディア アイテム B(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
を使用) - メディア アイテム E(
extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
を使用)
追加のメタデータ インジケーターを表示する

図 3. メタデータで曲とアーティストを識別する再生ビュー
追加のメタデータ インジケーターを設定して、メディア ブラウザツリー内に、またはメディアの再生中に、コンテンツが一目でわかる情報を提供できます。ブラウザツリー内では、Android Auto と Android Automotive OS はアイテムに関連付けられた extras を読み取り、特定の定数を見つけて、表示するインジケーターを決定します。メディアの再生中には、Android Auto と Android Automotive OS はメディア セッションのメタデータを読み取り、特定の定数を見つけて、表示するインジケーターを決定します。
アプリでメタデータ インジケーターの定数を宣言するには、次のコードを使用します。
Kotlin
// Bundle extra indicating that a song contains explicit content. var EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT" /** * Bundle extra indicating that a media item is available offline. * Same as MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS. */ var EXTRA_IS_DOWNLOADED = "android.media.extra.DOWNLOAD_STATUS" /** * Bundle extra value indicating that an item should show the corresponding * metadata. */ var EXTRA_METADATA_ENABLED_VALUE:Long = 1 /** * Bundle extra indicating the played state of long-form content (such as podcast * episodes or audiobooks). */ var EXTRA_PLAY_COMPLETION_STATE = "android.media.extra.PLAYBACK_STATUS" /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * not been played at all. */ var STATUS_NOT_PLAYED = 0 /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * been partially played (i.e. the current position is somewhere in the middle). */ var STATUS_PARTIALLY_PLAYED = 1 /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * been completed. */ var STATUS_FULLY_PLAYED = 2
Java
// Bundle extra indicating that a song contains explicit content. String EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT"; /** * Bundle extra indicating that a media item is available offline. * Same as MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS. */ String EXTRA_IS_DOWNLOADED = "android.media.extra.DOWNLOAD_STATUS"; /** * Bundle extra value indicating that an item should show the corresponding * metadata. */ long EXTRA_METADATA_ENABLED_VALUE = 1; /** * Bundle extra indicating the played state of long-form content (such as podcast * episodes or audiobooks). */ String EXTRA_PLAY_COMPLETION_STATE = "android.media.extra.PLAYBACK_STATUS"; /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * not been played at all. */ int STATUS_NOT_PLAYED = 0; /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * been partially played (i.e. the current position is somewhere in the middle). */ int STATUS_PARTIALLY_PLAYED = 1; /** * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has * been completed. */ int STATUS_FULLY_PLAYED = 2;
上記の定数を宣言したら、それらを使用してメタデータ インジケーターを表示できます。ユーザーがメディア ブラウザ ツリーをブラウズしているときにインジケーターを表示するには、上記の定数を 1 つ以上含む extras バンドルを作成し、そのバンドルを MediaDescription.Builder.setExtras()
メソッドに渡します。
次のコード スニペットは、部分的に再生される明示的なメディア アイテム用のインジケーターを表示する方法を示しています。
Kotlin
val extras = Bundle() extras.putLong(EXTRA_IS_EXPLICIT, 1) extras.putInt(EXTRA_PLAY_COMPLETION_STATE, STATUS_PARTIALLY_PLAYED) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
Java
Bundle extras = new Bundle(); extras.putLong(EXTRA_IS_EXPLICIT, 1); extras.putInt(EXTRA_PLAY_COMPLETION_STATE, STATUS_PARTIALLY_PLAYED); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
現在再生中のメディア アイテムのインジケーターを表示するには、EXTRA_IS_EXPLICIT
または EXTRA_IS_DOWNLOADED
の LONG 形式の値を mediaSession
の MediaMetadata.Builder()
メソッドで宣言します。再生ビューに EXTRA_PLAY_COMPLETION_STATE
インジケーターを表示することはできません。
次のコード スニペットは、再生ビュー内の現在の曲が明示的でありかつダウンロードされていることを示す方法を示しています。
Kotlin
mediaSession.setMetadata( MediaMetadata.Builder() .putString( MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE) .putLong( EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE) .build())
Java
mediaSession.setMetadata( new MediaMetadata.Builder() .putString( MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE) .putLong( EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE) .build());
ブラウズ可能な検索結果を表示する
アプリでは、ユーザーがコンテンツをブラウズしやすくするため、ユーザーが音声検索を実行するたびに、検索クエリに関連する検索結果のグループをブラウズできるようにすることが可能です。Android Auto と Android Automotive OS は、このような結果をインターフェースの [検索結果をさらに表示する] バーとして表示します。
図 4. 関連する検索結果を表示するための「その他の結果」オプション
ブラウズ可能な検索結果を表示するには、定数を作成し、その定数をサービスの onGetRoot()
メソッドの extras バンドルに含める必要があります。
次のコード スニペットは、onGetRoot()
メソッドでサポートを有効にする方法を示しています。
Kotlin
// Bundle extra indicating that onSearch() is supported val EXTRA_MEDIA_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED" @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean(EXTRA_MEDIA_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
Java
public static final String EXTRA_MEDIA_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED"; @Nullable @Override public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean(EXTRA_MEDIA_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<ArrayList<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive) result.detach(); new AsyncTask<Void, Void, Void>() { ArrayList<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); } return 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 }
再生コントロールを有効にする
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 to handle play control requests, which // implements MediaSession.Callback 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 to handle play control requests, which // implements MediaSession.Callback session.setCallback(new MyMediaSessionCallback()); ... }
メディア セッション オブジェクトを作成する際に、再生コントロール リクエストの処理に使用されるコールバック オブジェクトを設定します。このコールバック オブジェクトを作成するには、アプリの MediaSessionCompat.Callback
クラスの実装を提供します。次のセクションで、このオブジェクトを実装する方法を説明します。
再生コマンドを実装する
ユーザーがアプリにメディア アイテムの再生をリクエストすると、Android Automotive OS と Android Auto は、アプリの MediaSessionCompat
オブジェクト(アプリのメディア ブラウザ サービスから取得したオブジェクト)の MediaSessionCompat.Callback
クラスを使用します。ユーザーがコンテンツ再生を制御する場合(再生の一時停止や次のトラックへのスキップなど)、Android Auto と Android Automotive OS は、コールバック オブジェクトのメソッドの 1 つを呼び出します。
コンテンツ再生を処理するには、アプリで 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 は、音声コンテンツを車載システムに自動的に送信して、車載スピーカーで再生します。
音声コンテンツの再生の詳細については、メディアの再生、音声再生の管理、ExoPlayer をご覧ください。
標準の再生操作を設定する
Android Auto と Android Automotive OS は、PlaybackStateCompat
オブジェクトで有効化されている操作に基づいて再生コントロールを表示します。
デフォルトでは、アプリは次の操作をサポートする必要があります。
ACTION_PLAY
ACTION_PAUSE
ACTION_STOP
ACTION_SKIP_TO_PREVIOUS
ACTION_SKIP_TO_NEXT
ACTION_PLAY_FROM_MEDIA
ACTION_PLAY_FROM_SEARCH
さらに、ユーザーに表示できる再生キューの作成も可能です。そのためには、setQueue()
メソッドと setQueueTitle()
メソッドを呼び出し、ACTION_SKIP_TO_QUEUE_ITEM
操作を有効にして、コールバック onSkipToQueueItem()
を定義する必要があります。
Android Auto と Android Automotive OS は、有効化された操作ごとにボタンを表示し、再生キューを作成する場合は再生キューも表示します。
未使用のスペースを予約する
Android Auto と Android Automotive OS は、ACTION_SKIP_TO_PREVIOUS
操作と ACTION_SKIP_TO_NEXT
操作用に UI のスペースを予約します。さらに、Android Auto は再生キュー用のスペースも予約します。アプリがこれらの機能のいずれかをサポートしていない場合、Android Auto と Android Automotive OS はこのスペースを使用して、デベロッパーが作成したカスタム操作を表示します。
このスペースにカスタム操作を表示したくない場合、アプリが対応する機能をサポートしていないときは常に Android Auto と Android Automotive OS がスペースを空白のままにしておくように、スペースを予約することが可能です。そのためには、予約された各機能に対応する定数を含む extras バンドルを指定して、setExtras()
メソッドを呼び出します。スペースを予約するための各定数は、true
に設定します。
次のコード スニペットは、未使用のスペースを予約するために使用できる定数を示しています。
Kotlin
// Use these extras to show the transport control buttons for the corresponding actions, // even when they are not enabled in the PlaybackState. private const val PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT" private const val PLAYBACK_SLOT_RESERVATION_SKIP_TO_PREV = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS" private const val PLAYBACK_SLOT_RESERVATION_QUEUE = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE"
Java
// Use these extras to show the transport control buttons for the corresponding actions, // even when they are not enabled in the PlaybackState. private static final String PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"; private static final String PLAYBACK_SLOT_RESERVATION_SKIP_TO_PREV = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"; private static final String PLAYBACK_SLOT_RESERVATION_QUEUE = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";
最初の PlaybackState を設定する
Android Auto と Android Automotive OS はメディア ブラウザ サービスと通信するので、メディア セッションは PlaybackState
を使用してコンテンツ再生のステータスをやり取りします。Android Automotive OS または Android Auto がメディア ブラウザ サービスに接続したとき、アプリは自動的に音楽の再生を開始してはなりません。Android Auto と Android Automotive OS に処理をまかせ、車の状態またはユーザーの操作に基づいて再生を再開または開始してください。
これを行うには、メディア セッションの最初の PlaybackState
を、STATE_STOPPED
、STATE_PAUSED
、STATE_NONE
、STATE_ERROR
のいずれかに設定します。
カスタム再生操作を追加する
カスタム再生操作を追加して、メディアアプリがサポートする追加の操作を表示できます。スペースに余裕がある(予約されていない)場合、Android はトランスポート コントロールにカスタム操作を追加します。それ以外の場合、カスタム操作はオーバーフロー メニューに表示されます。カスタム操作は、PlaybackState
に追加された順序で表示されます。
これらの操作は、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());
このメソッドの詳細な例については、GitHub の Universal Android Music Player サンプルアプリの setCustomAction()
メソッドをご覧ください。
カスタム操作を作成した場合、メディア セッションは 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)) { ... } }
このメソッドの詳細な例については、GitHub の Universal Android Music Player サンプルアプリの onCustomAction
メソッドをご覧ください。
カスタム操作のアイコン
作成する各カスタム操作には、アイコン リソースが必要です。車載アプリはさまざまな画面サイズと密度で実行される可能性があるので、提供するアイコンはベクター ドローアブルでなければなりません。ベクター ドローアブルを使用すると、細部を犠牲にすることなくアセットを拡大 / 縮小できます。また、低い解像度でも、アイコンの端と隅をピクセル境界に簡単に合わせられます。
無効な操作に対して代替アイコン スタイルを提供する
現在のコンテキストでカスタム操作を使用できない場合は、カスタム操作のアイコンを、操作が無効であることを示す代替アイコンに切り替えます。
音声操作をサポートする
メディアアプリでは、ドライバーが注意散漫になる危険を最小限に抑える安全で便利なエクスペリエンスを提供するために、音声操作をサポートする必要があります。たとえば、アプリがあるメディア アイテムを再生しているときに、ユーザーが車載ディスプレイを見たり触れたりせずに、声(「[Bohemian Rhapsody] を再生」など)で別の曲の再生をアプリに指示できるようにします。
音声対応の再生操作をアプリに実装する方法の詳細な例については、Google アシスタントとメディアアプリをご覧ください。
注意散漫に対する安全保護対策を実装する
Android Auto の使用中、ユーザーのスマートフォンは車載スピーカーに接続されているため、ドライバーが注意散漫になるのを防ぐ追加の予防措置を講じる必要があります。
運転モードを検出する
Android Auto メディアアプリは、ユーザーが意識的に再生を開始した場合(アプリの再生ボタンを押した場合など)を除き、車載スピーカーで音声の再生を開始してはなりません。メディアアプリでユーザーがアラームをスケジュールした場合であっても、それによって車載スピーカーで音楽を再生してはいけません。この要件を満たすために、アプリは音声を再生する前に、スマートフォンが運転モードになっているかどうかを確認する必要があります。スマートフォンが運転モードになっているかどうかを確認するには、getCurrentModeType()
メソッドを呼び出します。
ユーザーのスマートフォンが運転モードになっている場合、アラームをサポートするメディアアプリは次のいずれかを実行する必要があります。
- アラームを無効にする。
STREAM_ALARM
でアラームを再生し、アラームを無効にする UI をスマートフォン画面に表示する。
次のコード スニペットは、アプリが運転モードで実行されているかどうかを確認する方法を示しています。
Kotlin
fun isCarUiMode(c: Context): Boolean { val uiModeManager = c.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager return if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) { LogHelper.d(TAG, "Running in Car mode") true } else { LogHelper.d(TAG, "Running in a non-Car mode") false } }
Java
public static boolean isCarUiMode(Context c) { UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE); if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { LogHelper.d(TAG, "Running in Car mode"); return true; } else { LogHelper.d(TAG, "Running in a non-Car mode"); return false; } }
メディア広告を処理する
デフォルトでは、Android Auto は、音声再生セッション中にメディア メタデータが変更されると、通知を表示します。メディアアプリが音楽の再生から広告の掲載に切り替えたときに通知を表示すると、ユーザーの注意が散漫になります(これは不要でもあります)。この場合に Android Auto が通知を表示しないようにするには、次のコード スニペットに示すように、メディア メタデータのキー android.media.metadata.ADVERTISEMENT
を 1
に設定する必要があります。
Kotlin
const val EXTRA_METADATA_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT" ... override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { // ... if (isAd(mediaId)) { putLong(EXTRA_METADATA_ADVERTISEMENT, 1) } // ... mediaSession.setMetadata(build()) } }
Java
public static final String EXTRA_METADATA_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT"; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); // ... if (isAd(mediaId)) { builder.putLong(EXTRA_METADATA_ADVERTISEMENT, 1); } // ... mediaSession.setMetadata(builder.build()); }
一般的なエラーを処理する
アプリでエラーが発生した場合、再生状態を STATE_ERROR
に設定し、setErrorMessage()
メソッドを使用してエラー メッセージを提供する必要があります。エラー メッセージはユーザー向けであるため、ユーザーの現在のロケールに合わせてローカライズする必要があります。これにより、Android Auto と Android Automotive OS はユーザーにエラー メッセージを表示できます。
エラー状態の詳細については、メディア セッションの操作: 状態とエラーをご覧ください。
Android Auto ユーザーがエラーを解決するためにスマートフォン アプリを開く必要がある場合、メッセージはその情報をユーザーに提供する必要があります。たとえば、エラー メッセージは、「ログインしてください」ではなく、「[アプリ名] にログインしてください」とします。