The Android Developer Challenge is back! Submit your idea before December 2.

自動車用の Android メディアアプリを作成する

Android Automotive OS と Android Auto は、メディアアプリのコンテンツを自動車に乗っているユーザーに届けるのに役立ちます。Android が運転中のアプリ エクスペリエンスを実現する仕組みの概要については、Android for Cars の概要をご覧ください。

このガイドは、スマートフォン向けのメディアアプリをすでにお持ちであることを前提としています。このガイドでは、Android Automotive OS 用のアプリを作成する方法と、Android Auto 用にスマートフォン アプリを拡張する方法について説明します。

始める前に

アプリの作成を開始する前に、Android for Cars のスタートガイドの手順を実施してから、このセクションの情報を確認してください。

主な用語と概念

メディア ブラウズ サービス
MediaBrowseServiceCompat API に準拠するメディアアプリによって実装される Android サービス。アプリはこのサービスを使用して、メディア ブラウズ コンテンツを Android Automotive OS と Android Auto に公開します。
メディア ブラウズ
Android Automotive OS と Android Auto にコンテンツを公開するためにメディアアプリによって使用される API。
メディア アイテム
メディア ブラウズ ツリー内の個別の MediaBrowserCompat.MediaItem オブジェクト。メディア アイテムのタイプは次のいずれかです。
  • 再生可能: このタイプのアイテムは、アルバムの曲、本のチャプター、ポッドキャストのエピソードなど、実際のサウンド ストリームを表します。
  • ブラウズ可能: このタイプのアイテムは、再生可能なメディア アイテムをグループに編成したものです。たとえば、チャプターを本に、曲をアルバムに、エピソードをポッドキャストにグループ化できます。

注: ブラウズ可能かつ再生可能なメディア アイテムは、再生可能として扱われます。

自動車向け最適化

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

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

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

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

アプリのマニフェスト ファイルを構成して、アプリが Android Automotive OS で利用可能であり、スマートフォン アプリが Android Auto 用のメディア サービスをサポートしていることを示す必要があります。

Android Automotive OS のサポートを宣言する

Android Automotive OS に配信するアプリは、スマートフォン アプリとは別にする必要があります。モジュールAndroid App Bundle を使用し、コードを再利用してアプリを簡単にビルドおよびリリースできるようにすることをおすすめします。Android Automotive OS モジュールのマニフェスト ファイルに次のエントリを追加して、モジュールのコードが Android Automotive OS に制限されていることを示します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.media">
       <uses-feature
               android:name="android.hardware.type.automotive"
               android:required="true"/>
    </manifest>
    

Android Auto のメディア サポートを宣言する

Android Auto は、スマートフォン アプリを使用して、ドライバー向けに最適化されたエクスペリエンスをユーザーに提供します。次のマニフェスト エントリを使用して、スマートフォン アプリが Android Auto をサポートすることを宣言します。

<application>
        ...
        <meta-data android:name="com.google.android.gms.car.application"
            android:resource="@xml/automotive_app_desc"/>
        ...
    <application>
    

このマニフェスト エントリは、アプリがサポートする自動車機能を宣言する XML ファイルを参照します。メディアアプリが存在することを示すには、automotive_app_desc.xml という名前の XML ファイルをプロジェクトの res/xml/ ディレクトリに追加します。このファイルには次の内容が含まれている必要があります。

<automotiveApp>
        <uses name="media"/>
    </automotiveApp>
    

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

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

次のコード スニペットは、マニフェストでメディア ブラウズ サービスを宣言する方法を示しています。このコードは、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 Automotive OS と Android Auto は、ユーザーがアプリを使用して自動車と対話する際に、さまざまな場面でアプリのアイコンをユーザーに表示します。たとえば、ユーザーがナビゲーション アプリを実行しているときに、1 つの曲が終了して新しい曲がスタートすると、アプリのアイコンを含む通知がユーザーに表示されます。Android Auto および Android Automotive OS は、ユーザーがメディア コンテンツをブラウズしているとき、他の場面でもアプリのアイコンを表示します。

次のマニフェスト宣言を使用して、アプリを表すために使用するアイコンを指定できます。

<!--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 Automotive OS と Android Auto の両方が、サービスを使用して次の処理を実行できます。

  • ユーザーにメニューを表示するために、アプリのコンテンツ階層を参照する。
  • 音声の再生を制御するために、アプリの 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 には、ユーザーが画面キーボードから文字を選択できるブラウジング機能が含まれています。現在のドロワーリストに、ユーザーが選択した文字で始まるアイテムのリストが表示されます。この機能は、並べ替えられたコンテンツと並べ替えられていないコンテンツの両方で動作し、現在は英語でのみ利用可能です。

図 1. 車の画面上のアルファ ピッカー

図 2. 車の画面上のアルファベット順のリストビュー

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

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

onGetRoot を実装する

サービスの onGetRoot() メソッドは、コンテンツ階層のルートノードに関する情報を返します。Android Automotive OS と Android Auto は、このルートノードを使用して、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 Automotive OS や Android Auto など)にコンテンツへのアクセスを許可するには、システムアプリが 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 Automotive OS と Android Auto は、ルートノードのオブジェクトを受け取った後、ルートノードのオブジェクトで onLoadChildren() を呼び出してその子を取得することにより、最上位メニューを構築します。クライアント アプリは、子ノードのオブジェクトを使用してこの同じメソッドを呼び出すことにより、サブメニューを構築します。

コンテンツ階層内の各ノードは、MediaBrowserCompat.MediaItem オブジェクトで表されます。これらの各メディア アイテムは、一意の ID 文字列によって識別されます。クライアント アプリは、このような ID 文字列を不透明なトークンとして扱います。クライアント アプリがサブメニューのブラウズまたはメディア アイテムの再生を行いたいときは、トークンを渡します。トークンを適切なメディア アイテムに関連付ける処理はアプリが行います。

注: Android Automotive OS と Android Auto には、メニューの各レベルで表示できるメディア アイテムの数に厳格な制限があります。この制限により、ドライバーが注意散漫になる危険が最小限に抑えられ、音声コマンドによるアプリの操作が容易になります。詳細については、コンテンツ詳細のブラウズ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 Automotive OS と Android Auto は、ブラウズツリー内の各アイテムに関連付けられた 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)
        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);
       return new MediaBrowser.MediaItem(
           mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
    }
    

タイトル アイテムを追加する

メディア アイテムをタイトル アイテムとして表示するには、アイテムをグループ化するアイテムごとのコンテンツ スタイルを使用します。グループ内のすべてのメディア アイテムで、同じ文字列を使用する MediaDescription で extras バンドルを宣言する必要があります。この文字列はグループのタイトルとして使用され、ローカライズが可能です。

Android Automotive OS と Android Auto は、この方法でグループ化されたアイテムを並べ替えません。メディア アイテムを表示したい順序で、まとめて渡す必要があります。

たとえば、アプリが次の順序で 3 つのメディア アイテムを渡したとします。

  • メディア アイテム 1: extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  • メディア アイテム 2: extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
  • メディア アイテム 3: extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")

Android Automotive OS と Android Auto は、メディア アイテム 1 とメディア アイテム 3 を "Songs" グループにマージしません。各メディア アイテムは別々に保持されます。

次のコード スニペットは、サブグループの見出しが "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")
        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");
       return new MediaBrowser.MediaItem(
           mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
    }
    

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

図 3. メタデータ インジケーター付きの再生ビュー

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

アプリでメタデータ インジケーターの定数を宣言するには、次のコードを使用します。

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 形式の値を MediaMetadata.Builder()mediaSession のメソッド)で宣言します。再生ビューに 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 Automotive OS と Android Auto は、このような結果をインターフェースの [結果をもっと表示] バーとして表示します。

図 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 Automotive OS と Android Auto は、ユーザーが「結果をもっと表示」アフォーダンスを呼び出すたびに、ユーザーの検索キーワードをこのメソッドに転送します。サービスの 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 Automotive OS と Android Auto は、サービスの 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.Callback クラス(アプリのメディア ブラウズ サービスから取得した、アプリの MediaSessionCompat オブジェクトから取得)を使用します。ユーザーがコンテンツ再生を制御する場合(再生の一時停止や次のトラックへのスキップなど)、Android Automotive OS と Android Auto は、コールバック オブジェクトのメソッドの 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 Automotive OS と Android Auto は、音声コンテンツを車載システムに自動的に送信して、車載スピーカーで再生します。

音声コンテンツの再生の詳細については、メディアの再生音声再生の管理ExoPlayer をご覧ください。

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

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

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

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

Android Automotive OS と Android Auto は、有効化された操作ごとにボタンを表示し、再生キューを作成する場合は再生キューも表示します。

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

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

このようなスペースにカスタム操作を表示したくない場合、アプリが対応する機能をサポートしていないときは常に Android Automotive OS と Android Auto がスペースを空白のままにしておくように、スペースを予約することが可能です。そのためには、予約された各機能に対応する定数を含む 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 SLOT_RESERVATION_SKIP_TO_NEXT =
            "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"
    private const val SLOT_RESERVATION_SKIP_TO_PREV =
            "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"
    private const val SLOT_RESERVATION_QUEUE =
            "com.google.android.gms.car.media.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 SLOT_RESERVATION_SKIP_TO_NEXT =
        "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
    private static final String SLOT_RESERVATION_SKIP_TO_PREV =
        "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
    private static final String SLOT_RESERVATION_QUEUE =
        "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";
    

最初の PlaybackState を設定する

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

これを行うには、メディア セッションの最初の PlaybackState を、STATE_STOPPEDSTATE_PAUSEDSTATE_NONESTATE_ERROR のいずれかに設定します。

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

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

addCustomAction() メソッド(PlaybackStateCompat.Builder クラスにあります)を使用して、カスタム操作を追加できます。

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

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 メソッドをご覧ください。

カスタム操作のアイコン

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

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

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

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

音声操作をサポートする

メディアアプリでは、ドライバーが注意散漫になる危険を最小限に抑える安全で便利なエクスペリエンスを提供するために、音声操作をサポートする必要があります。たとえば、アプリがあるメディア アイテムを再生しているときに、ユーザーが「[アイテム] を再生」と発声して、車載ディスプレイを見ることも触れることもなしに、別のアイテムの再生をアプリに指示できるようにします。

音声操作のサポートを宣言する

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

<activity>
        <intent-filter>
            <action android:name=
                 "android.media.action.MEDIA_PLAY_FROM_SEARCH" />
            <category android:name=
                 "android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
    

音声検索クエリを解析する

ユーザーが「[アプリ名] でジャズを再生」や「[曲名] を聴く」などの特定のメディア アイテムを検索すると、onPlayFromSearch() コールバック メソッドはクエリ パラメータと extras バンドルで音声検索結果を受け取ります。

アプリでは、次の手順に従って音声検索クエリを解析し、再生を開始できます。

  1. extras バンドルと、音声検索から返された検索クエリ文字列を使用して、結果をフィルタリングする。
  2. その結果に基づいて再生キューを作成する。
  3. 結果に基づいて最も関連性の高いメディア アイテムを再生する。

onPlayFromSearch() メソッドは、音声検索からのより詳細な情報を含む extras パラメータを受け取ります。これらの extras は、再生するアプリ内の音声コンテンツを見つけるのに役立ちます。検索結果がこのデータを提供できない場合は、生の検索クエリを解析し、クエリに基づいて適切なトラックを再生するロジックを実装できます。

Android Automotive OS と Android Auto では、次の extras がサポートされています。

次のコード スニペットは、onPlayFromSearch() メソッド(MediaSession.Callback 実装にあります)をオーバーライドし、音声検索クエリを解析して再生を開始する方法を示しています。

Kotlin

    override fun onPlayFromSearch(query: String?, extras: Bundle?) {
        if (query.isNullOrEmpty()) {
            // The user provided generic string e.g. 'Play music'
            // Build appropriate playlist queue
        } else {
            // Build a queue based on songs that match "query" or "extras" param
            val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS)
            if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) {
                isArtistFocus = true
                artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
            } else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) {
                isAlbumFocus = true
                album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
            }

            // Implement additional "extras" param filtering
        }

        // Implement your logic to retrieve the queue
        var result: String? = when {
            isArtistFocus -> artist?.also {
                searchMusicByArtist(it)
            }
            isAlbumFocus -> album?.also {
                searchMusicByAlbum(it)
            }
            else -> null
        }
        result = result ?: run {
            // No focus found, search by query for song title
            query?.also {
                searchMusicBySongTitle(it)
            }
        }

        if (result?.isNotEmpty() == true) {
            // Immediately start playing from the beginning of the search results
            // Implement your logic to start playing music
            playMusic(result)
        } else {
            // Handle no queue found. Stop playing if the app
            // is currently playing a song
        }
    }
    

Java

    @Override
    public void onPlayFromSearch(String query, Bundle extras) {
        if (TextUtils.isEmpty(query)) {
            // The user provided generic string e.g. 'Play music'
            // Build appropriate playlist queue
        } else {
            // Build a queue based on songs that match "query" or "extras" param
            String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS);
            if (TextUtils.equals(mediaFocus,
                    MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
                isArtistFocus = true;
                artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST);
            } else if (TextUtils.equals(mediaFocus,
                    MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
                isAlbumFocus = true;
                album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM);
            }

            // Implement additional "extras" param filtering
        }

        // Implement your logic to retrieve the queue
        if (isArtistFocus) {
            result = searchMusicByArtist(artist);
        } else if (isAlbumFocus) {
            result = searchMusicByAlbum(album);
        }

        if (result == null) {
            // No focus found, search by query for song title
            result = searchMusicBySongTitle(query);
        }

        if (result != null && !result.isEmpty()) {
            // Immediately start playing from the beginning of the search results
            // Implement your logic to start playing music
            playMusic(result);
        } else {
            // Handle no queue found. Stop playing if the app
            // is currently playing a song
        }
    }
    

アプリで音声コンテンツを再生するための音声検索の実装方法の詳細な例については、Universal Media Player サンプルをご覧ください。

空のクエリを処理する

ユーザーが「[アプリ名] で音楽を再生」と話しかけると、Android Automotive OS または Android Auto は、アプリの onPlayFromSearch() メソッドを呼び出し、アプリを起動して音声を再生しようとします。しかし、ユーザーがメディア アイテムの名前を言わなかったため、onPlayFromSearch() メソッドは空のクエリ パラメータを受け取ります。この場合、アプリは、これに応えて最新のプレイリストまたはランダムキューの曲をすぐに再生する必要があります。

音声対応の再生操作を実装する

運転中のユーザーがメディア コンテンツを聴くときにハンズフリー エクスペリエンスをアプリで提供するには、ユーザーが音声操作でコンテンツ再生を制御できるようにする必要があります。ユーザーが「次の曲」、「音楽を一時停止」、「音楽を再開」などのコマンドを口にすると、システムは対応するコールバック メソッドをトリガーして、再生制御操作を実装します。

音声対応の再生操作を提供するには、まずアプリの MediaSession オブジェクトに次のフラグを設定して、ハードウェア コントロールを有効にします。

Kotlin

    session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
            or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
    )
    

Java

    session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
        MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
    

フラグを設定したら、アプリでサポートする再生コントロールを含むコールバック メソッドを実装します。Android Automotive OS と Android Auto は、次の音声対応の再生操作をサポートしています。

フレーズの例 コールバック メソッド
「次の曲」 onSkipToNext()
「前の曲」 onSkipToPrevious()
「音楽を一時停止」 onPause()
「音楽を停止」 onStop()
「音楽を再開」 onPlay()

音声対応の再生操作をアプリに実装する方法の詳細な例については、Universal Media Player サンプルをご覧ください。

Android Automotive OS 用の設定およびログイン アクティビティを実装する

メディア ブラウザ サービスに加えて、Android Automotive OS アプリ用に、自動車向けに最適化された設定およびログイン アクティビティも提供できます。これらのアクティビティにより、Android Media API に含まれていないアプリ機能を提供できます。

設定アクティビティを追加する

ユーザーが車載アプリの設定を構成できるように、自動車向けに最適化された設定アクティビティを追加できます。設定アクティビティでは、ユーザーのアカウントへのログインとログアウト、ユーザー アカウントの切り替えなど、他のワークフローも提供できます。

設定アクティビティのワークフロー

設定アクティビティは、ユーザーにさまざまなワークフローを提供できます。次の図は、ユーザーが Android Automotive OS を使用して設定アクティビティを操作する仕組みを示しています。

設定アクティビティのワークフロー

図 6. 設定アクティビティのワークフローの図

設定アクティビティを宣言する

次のコード スニペットに示すように、設定アクティビティをアプリのマニフェスト ファイルで宣言する必要があります。

<application>
        ...
        <activity android:name=".AppSettingsActivity"
                  android:exported="true"
                  android:theme="@style/SettingsActivity"
                  android:label="@string/app_settings_activity_title">
            <intent-filter>
                <action android:name="android.intent.action.APPLICATION_PREFERENCES"/>
            </intent-filter>
        </activity>
        ...
    <application>
    

設定アクティビティを実装する

ユーザーがアプリを起動すると、Android Automotive OS は、宣言された設定アクティビティを検出して、アフォーダンスを表示します。ユーザーは、車載ディスプレイでこのアフォーダンスをタップ(選択)して、アクティビティにアクセスできます。Android Automotive OS は、設定アクティビティの開始をアプリに指示する ACTION_APPLICATION_PREFERENCES インテントを送信します。

ログイン アクティビティを追加する

ユーザーがアプリを使用するためにログインする必要がある場合、アプリのログインとログアウトを処理する、自動車向けに最適化されたログイン アクティビティを追加できます。ログインとログアウトのワークフローを設定アクティビティに追加することも可能ですが、ユーザーがログインするまでアプリを使用できない場合は、専用のログイン アクティビティを使用する必要があります。

ログイン アクティビティのワークフロー

次の図は、ユーザーが Android Automotive OS を使用してログイン アクティビティを操作する仕組みを示しています。

ログイン アクティビティのワークフロー

図 7. ログイン アクティビティのワークフローの図

アプリの起動時にログインを要求する

ユーザーがアプリを使用する前にログイン アクティビティでログインすることをユーザーに要求するには、メディア ブラウズ サービスで次の処理を行う必要があります。

  1. setState() メソッドを使用して、メディア セッションの PlaybackStateSTATE_ERROR に設定します。これにより、エラーが解決されるまで他のオペレーションを実行できないことを Android Automotive OS に伝えます。
  2. メディア セッションの PlaybackState エラーコードを ERROR_CODE_AUTHENTICATION_EXPIRED に設定します。これにより、ユーザーが認証する必要があることを Android Automotive OS に伝えます。
  3. setErrorMessage() メソッドを使用して、メディア セッションの PlaybackState エラー メッセージを設定します。このエラー メッセージはユーザー向けであるため、ユーザーの現在のロケールに合わせてローカライズする必要があります。
  4. setExtras() メソッドを使用して、メディア セッションの PlaybackState extras を設定します。次の 2 つのキーを含めます。

    • android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: ログイン ワークフローを開始するボタンに表示される文字列。この文字列はユーザー向けであるため、ユーザーの現在のロケールに合わせてローカライズする必要があります。
    • android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: PendingIntent(ユーザーが android.media.extras.ERROR_RESOLUTION_ACTION_LABEL によって参照されるボタンをタップしたときに、ユーザーをログイン アクティビティに誘導するインテント)。

次のコード スニペットは、ユーザーがアプリを使用する前にログインを要求する方法を示しています。

Kotlin

    val signInIntent = Intent(this, SignInActivity::class.java)
    val signInActivityPendingIntent = PendingIntent.getActivity(this, 0,
        signInIntent, 0)
    val extras = Bundle().apply {
        putString(
            "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL",
            "Sign in"
        )
        putParcelable(
            "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT",
            signInActivityPendingIntent
        )
    }

    val playbackState = PlaybackStateCompat.Builder()
            .setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
            .setErrorMessage(
                PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
                "Authentication required"
            )
            .setExtras(extras)
            .build()
    mediaSession.setPlaybackState(playbackState)
    

Java

    Intent signInIntent = new Intent(this, SignInActivity.class);
    PendingIntent signInActivityPendingIntent = PendingIntent.getActivity(this, 0,
        signInIntent, 0);
    Bundle extras = new Bundle();
    extras.putString(
        "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL",
        "Sign in");
    extras.putParcelable(
        "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT",
        signInActivityPendingIntent);

    PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder()
        .setState(PlaybackStateCompat.STATE_ERROR, 0, 0f)
        .setErrorMessage(
                PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
                "Authentication required"
        )
        .setExtras(extras)
        .build();
    mediaSession.setPlaybackState(playbackState);
    

ユーザーの認証が成功したら、アプリは PlaybackStateSTATE_ERROR 以外の状態に設定し直し、アクティビティの finish() メソッドを呼び出して、ユーザーを Android Automotive OS に戻す必要があります。

ログイン アクティビティを実装する

Google は、ユーザーが車載アプリにログインする操作をサポートするさまざまな ID ツールを提供しています。Firebase Authentication などのツールは、カスタマイズされた認証エクスペリエンスの構築に役立つフルスタックのツールキットを提供します。また、ユーザーの既存の認証情報や他のテクノロジーを利用して、ユーザーのためにシームレスなログイン エクスペリエンスを構築できるツールもあります。

Google では次のツールをおすすめします。これらは、以前に別のデバイスにログインしたユーザーのために、簡単なログイン エクスペリエンスを構築するのに役立ちます。

  • Google ログイン: 他のデバイス(スマートフォン アプリなど)に Google ログインをすでに実装している場合、既存の Google ログイン ユーザーをサポートするために、Android Automotive OS アプリにも Google ログインを実装する必要があります。
  • Autofill with Google: ユーザーが他の Android デバイスで Autofill with Google をオプトインした場合、ユーザーの認証情報は Google パスワード マネージャーに保存されます。その後、ユーザーが Android Automotive OS アプリにログインすると、Autofill with Google が、保存済みの関連する認証情報を候補として提示します。Autofill with Google を利用すると、アプリを開発する手間が省けます。ただし、アプリのデベロッパーは、より良い結果を得るためにアプリを最適化する必要があります。Autofill with Google は、Android Oreo 8.0(API レベル 26)以上(Android Automotive OS を含む)を実行するすべてのデバイスでサポートされています。

ログインで保護された操作を処理する

アプリによっては、一部の操作では匿名でアクセスすることをユーザーに許可しますが、他の操作を実行する場合はログインを要求することがあります。たとえば、ユーザーがログインしなくてもアプリで音楽を再生することは可能でも、曲をスキップするにはログインが必要、といった場合があります。

この場合、ユーザーが制限された操作(曲のスキップ)を実行しようとしたときに、アプリは致命的でないエラーを発行してユーザーにログイン認証を提案できます。致命的ではないエラーを使用することにより、システムは現在のメディア アイテムの再生を中断することなく、ユーザーにメッセージを表示します。致命的ではないエラーの処理を実装するには、次の手順を実施します。

  1. メディア セッションの PlaybackStateerrorCodeERROR_CODE_AUTHENTICATION_EXPIRED に設定します。これにより、ユーザーが認証する必要があることを Android Automotive OS に伝えます。
  2. メディア セッションの PlaybackStatestateSTATE_ERROR に設定せず、そのままにします。これにより、エラーが致命的でないことをシステムに伝えます。
  3. setExtras() メソッドを使用して、メディア セッションの PlaybackState extras を設定します。次の 2 つのキーを含めます。

    • android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: ログイン ワークフローを開始するボタンに表示される文字列。この文字列はユーザー向けであるため、ユーザーの現在のロケールに合わせてローカライズする必要があります。
    • android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: PendingIntent(ユーザーが android.media.extras.ERROR_RESOLUTION_ACTION_LABEL によって参照されるボタンをタップしたときに、ユーザーをログイン アクティビティに誘導するインテント)。
  4. メディア セッションの残りでは PlaybackState 状態をそのままにします。これにより、ユーザーがログインするかどうかを決定する間、現在のメディア アイテムの再生を継続できます。

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

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.ADVERTISEMENT1 に設定する必要があります。次のコード スニペットをご覧ください。

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 Automotive OS と Android Auto は、ユーザーにエラー メッセージを表示できます。

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

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

その他のリソース