Membuat aplikasi media Android untuk mobil

Android Automotive OS dan Android Auto membantu Anda menampilkan konten aplikasi media Anda kepada pengguna di mobil mereka. Untuk ringkasan cara Android memberikan pengalaman aplikasi di jalan, lihat Ringkasan Android untuk Mobil.

Panduan ini mengasumsikan Anda sudah memiliki aplikasi telepon media. Panduan ini menjelaskan cara membuat aplikasi untuk Android Automotive OS dan cara memperluas aplikasi ponsel Anda untuk Android Auto.

Sebelum memulai

Sebelum mulai membuat aplikasi, pastikan untuk mengikuti langkah-langkah dalam Memulai Android untuk Mobil, lalu pelajari informasi di bagian ini.

Istilah dan konsep utama

Media Browse Service
Layanan Android yang diterapkan oleh aplikasi media Anda yang mematuhi API MediaBrowseServiceCompat. Aplikasi Anda menggunakan layanan ini untuk menampakkan konten Media Browse ke Android Automotive OS dan Android Auto.
Media Browse
API yang digunakan oleh aplikasi media untuk menampakkan kontennya ke Android Automotive OS dan Android Auto.
Item Media
Objek MediaBrowserCompat.MediaItem tertentu dalam struktur Media Browse. Item media dapat berupa salah satu jenis berikut:
  • Dapat diputar (playable): Item ini menunjukkan stream suara aktual seperti lagu dari album, bab dari buku, atau episode dari podcast.
  • Dapat dijelajahi (browseable): Item ini mengelompokkan item media yang dapat diputar. Misalnya, Anda dapat mengelompokkan beberapa bab ke dalam sebuah buku, beberapa lagu ke dalam sebuah album, atau beberapa episode ke dalam sebuah podcast.

Catatan: Item media yang dapat dijelajahi dan dapat diputar akan diperlakukan sebagai dapat diputar.

Dioptimalkan untuk Kendaraan

Aktivitas untuk aplikasi Android Automotive OS yang mematuhi pedoman desain Android Automotive OS. Antarmuka untuk aktivitas ini tidak dibuat oleh Android Automotive OS; karena itu, Anda harus memastikan bahwa aplikasi Anda mematuhi pedoman desain. Biasanya, antarmuka ini memiliki target ketuk dan ukuran font yang lebih besar, dukungan untuk mode siang dan malam, dan rasio kontras yang lebih tinggi.

Antarmuka pengguna yang dioptimalkan untuk kendaraan hanya boleh ditampilkan jika Car User Experience Restrictions (CUXR) tidak diberlakukan, karena antarmuka ini dapat meminta perhatian atau interaksi yang lama dari pengguna. CUXR tidak diberlakukan saat mobil berhenti atau diparkir, tetapi selalu diberlakukan saat mobil bergerak.

Anda tidak perlu mendesain aktivitas untuk Android Auto karena Android Auto menggambar sendiri antarmuka yang dioptimalkan untuk kendaraan menggunakan informasi dari Media Browse service.

Mengonfigurasi file manifes aplikasi Anda

Anda perlu mengonfigurasi file manifes aplikasi untuk menunjukkan bahwa aplikasi Anda tersedia untuk Android Automotive OS dan bahwa aplikasi ponsel Anda mendukung layanan media untuk Android Auto.

Mendeklarasikan dukungan untuk Android Automotive OS

Aplikasi yang didistribusikan ke Android Automotive OS harus terpisah dari aplikasi ponsel. Kami merekomendasikan penggunaan modul dan Android App Bundle untuk membantu Anda menggunakan kembali kode dan membuat serta merilis aplikasi dengan mudah. Tambahkan entri berikut ke file manifes dalam modul Android Automotive OS Anda untuk menunjukkan bahwa kode modul dibatasi ke 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>
    

Mendeklarasikan dukungan media untuk Android Auto

Android Auto menggunakan aplikasi ponsel Anda untuk memberikan pengalaman yang dioptimalkan bagi pengemudi. Gunakan entri manifes berikut untuk mendeklarasikan bahwa aplikasi ponsel Anda mendukung Android Auto:

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

Entri manifes ini merujuk ke sebuah file XML yang mendeklarasikan kapabilitas otomotif yang didukung aplikasi Anda. Untuk menunjukkan bahwa Anda memiliki aplikasi media, tambahkan file XML dengan nama automotive_app_desc.xml ke direktori res/xml/ project Anda. File ini harus mencakup konten berikut:

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

Mendeklarasikan Media Browse service

Android Automotive OS dan Android Auto terhubung ke aplikasi Anda melalui layanan browser media agar dapat menjelajahi item media. Deklarasikan Media Browse service dalam manifes Anda agar Android Automotive OS dan Android Auto dapat menemukan layanan dan terhubung ke aplikasi Anda.

Cuplikan kode berikut menunjukkan cara mendeklarasikan Media Browse service dalam manifes. Anda harus memasukkan kode ini ke dalam file manifes untuk modul Android Automotive OS dan dalam file manifes untuk aplikasi ponsel Anda.

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

Menentukan ikon aplikasi

Android Automotive OS dan Android Auto menampilkan ikon aplikasi Anda kepada pengguna di tempat yang berbeda saat mereka menggunakan aplikasi itu dan berinteraksi dengan mobilnya. Misalnya, jika pengguna menjalankan aplikasi navigasi, lalu satu lagu selesai dan lagu berikutnya dimulai, pengguna akan melihat pemberitahuan yang menyertakan ikon aplikasi Anda. Android Auto dan Android Automotive OS juga menampilkan ikon aplikasi Anda di lokasi lain saat pengguna menjelajahi konten media Anda.

Anda dapat menentukan ikon yang digunakan untuk mewakili aplikasi Anda menggunakan deklarasi manifes berikut:

<!--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>
    

Membuat Media Browse service

Buatlah Media Browse service dengan memperluas class MediaBrowserServiceCompat. Selanjutnya, Android Automotive OS dan Android Auto dapat menggunakan layanan Anda untuk melakukan tugas berikut:

  • Menjelajahi hierarki konten aplikasi Anda untuk menampilkan menu kepada pengguna.
  • Mendapatkan token untuk objek MediaSessionCompat aplikasi Anda, untuk mengontrol pemutaran audio.

Alur kerja layanan browser media

Bagian ini menjelaskan interaksi Android Automotive OS dan Android Auto dengan Media Browse service pada alur kerja pengguna standar.

  1. Pengguna meluncurkan aplikasi Anda di Android Automotive OS atau Android Auto.
  2. Android Automotive OS atau Android Auto menghubungi Media Browse service aplikasi Anda menggunakan metode onCreate(). Dalam implementasi metode onCreate(), Anda harus membuat dan mendaftarkan objek MediaSessionCompat dan objek callback-nya.
  3. Android Automotive OS atau Android Auto memanggil metode onGetRoot() layanan Anda untuk mendapatkan item media root dalam hierarki konten Anda. Item media root ini tidak ditampilkan; item ini digunakan untuk mengambil lebih banyak konten dari aplikasi Anda.
  4. Android Automotive OS atau Android Auto memanggil metode onLoadChildren() layanan Anda untuk mendapatkan turunan item media root. Android Automotive OS dan Android Auto menampilkan item media ini sebagai item konten level teratas. Item konten level teratas harus dapat dijelajahi.
  5. Jika pengguna memilih item media yang dapat dijelajahi, metode onLoadChildren() layanan Anda akan dipanggil lagi untuk mengambil turunan item menu yang dipilih.
  6. Jika pengguna memilih item media yang dapat diputar, Android Automotive OS atau Android Auto akan memanggil metode callback sesi media yang sesuai untuk menjalankan tindakan itu.
  7. Jika didukung oleh aplikasi Anda, pengguna juga dapat menelusuri konten Anda. Dalam hal ini, Android Automotive OS atau Android Auto akan memanggil metode onSearch() layanan Anda.

Cara pengguna menjelajah dalam aplikasi media

Agar pengguna dapat menjelajahi konten aplikasi Anda dengan cepat, Android Auto menyertakan kapabilitas penjelajahan yang memungkinkan pengguna memilih huruf dari keyboard pada layar. Selanjutnya, pengguna akan melihat daftar item yang dimulai dengan huruf itu di daftar panel samping yang sedang aktif. Metode ini berfungsi baik pada konten yang diurutkan maupun tidak diurutkan, dan saat ini baru tersedia dalam bahasa Inggris.

Gambar 1. Alat pilih alfa di layar mobil

Gambar 2. Tampilan daftar alfabet di layar mobil

Membuat hierarki konten

Android Automotive OS dan Android Auto memanggil Media Browse service aplikasi Anda untuk mengetahui konten yang tersedia. Anda harus menerapkan dua metode di layanan browser untuk mendukung hal ini: onGetRoot() dan onLoadChildren().

Mengimplementasikan onGetRoot

Metode onGetRoot() layanan Anda akan menampilkan informasi tentang node root hierarki konten Anda. Android Automotive OS dan Android Auto menggunakan node root ini untuk meminta konten Anda lainnya menggunakan metode onLoadChildren().

Cuplikan kode berikut menunjukkan implementasi sederhana metode 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);
    }
    

Untuk contoh yang lebih terperinci dari metode ini, lihat metode onGetRoot() pada contoh aplikasi Universal Android Music Player di GitHub.

Menambahkan validasi paket untuk onGetRoot()

Saat panggilan dilakukan ke metode onGetRoot() layanan Anda, paket panggilan ini meneruskan informasi pengidentifikasi ke layanan Anda. Layanan Anda dapat menggunakan informasi ini untuk menentukan apakah paket itu dapat mengakses konten Anda atau tidak. Misalnya, Anda dapat membatasi akses ke konten aplikasi Anda ke daftar paket yang disetujui dengan membandingkan clientPackageName dengan daftar yang Anda setujui dan memverifikasi sertifikat yang digunakan untuk menandatangani APK paket tersebut. Jika paket tidak dapat diverifikasi, tampilkan null untuk menolak akses ke konten Anda.

Agar aplikasi sistem (seperti Android Automotive OS dan Android Auto) dapat mengakses konten Anda, layanan Anda harus selalu menampilkan BrowserRoot bukan nol saat aplikasi sistem ini memanggil metode onGetRoot(). Cuplikan kode berikut menunjukkan bagaimana layanan Anda dapat memvalidasi bahwa paket panggilan adalah aplikasi sistem:

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
    }
    

Cuplikan kode ini adalah nukilan dari class PackageValidator dalam contoh aplikasi Universal Android Music Player di GitHub. Pelajari class tersebut untuk melihat contoh yang lebih terperinci tentang cara menerapkan validasi paket untuk metode onGetRoot() layanan Anda.

Mengimplementasikan onLoadChildren()

Setelah menerima objek node root, Android Automotive OS dan Android Auto akan membuat menu level teratas dengan memanggil onLoadChildren() pada objek node root untuk memperoleh turunannya. Aplikasi klien membuat submenu dengan memanggil metode yang sama ini menggunakan objek node turunan.

Setiap node dalam hierarki konten Anda diwakili oleh objek MediaBrowserCompat.MediaItem. Masing-masing item media ini diidentifikasi oleh string ID unik. Aplikasi klien memperlakukan string ID ini sebagai token buram. Saat ingin menjelajah ke sebuah submenu, atau memutar item media, aplikasi klien akan meneruskan token ini. Aplikasi Anda harus mengaitkan token tersebut dengan item media yang sesuai.

Catatan: Android Automotive OS dan Android Auto memiliki batasan ketat terkait jumlah item media yang dapat ditampilkan di setiap level menu. Batasan ini meminimalkan gangguan bagi pengemudi dan membantu mereka mengoperasikan aplikasi Anda dengan perintah suara. Untuk informasi lebih lanjut, lihat Menjelajahi detail konten dan Panel samping di aplikasi Android Auto.

Cuplikan kode berikut ini menunjukkan implementasi sederhana metode 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);
    }
    

Untuk contoh lengkap metode ini, lihat metode onLoadChildren() dalam contoh aplikasi Universal Android Music Player di GitHub.

Menerapkan gaya konten

Setelah membuat hierarki konten menggunakan item yang dapat dijelajahi atau dapat diputar, Anda dapat menerapkan gaya konten yang menentukan bagaimana item tersebut ditampilkan di mobil.

Anda dapat menggunakan gaya konten berikut:

Item daftar

Gaya konten ini lebih memprioritaskan judul dan metadata daripada gambar.

Item petak

Gaya konten ini lebih memprioritaskan gambar daripada judul dan metadata.

Item judul

Gaya konten ini menampilkan metadata yang lebih banyak daripada gaya konten Item Daftar. Untuk menggunakan gaya konten ini, metadata tambahan harus disediakan untuk setiap item media.

Menetapkan gaya konten default

Anda dapat menetapkan gaya default global untuk mengatur cara menampilkan item media dengan menyertakan konstanta tertentu dalam paket tambahan BrowserRoot untuk metode onGetRoot() layanan Anda. Android Automotive OS dan Android Auto membaca paket tambahan yang terkait dengan setiap item dalam struktur jelajahi dan mencari konstanta tersebut untuk menentukan gaya yang sesuai.

Gunakan kode berikut untuk mendeklarasikan konstanta ini dalam aplikasi Anda:

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;
    

Setelah dideklarasikan, sertakan konstanta ini dalam paket tambahan metode onGetRoot() layanan Anda untuk menetapkan gaya konten default. Cuplikan kode berikut menunjukkan cara menetapkan gaya konten default untuk item yang dapat dijelajahi ke petak, dan item yang dapat diputar ke daftar:

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

Menetapkan gaya konten per-item

Content Style API memungkinkan Anda mengganti gaya konten default untuk turunan mana pun dari item media yang dapat dijelajahi. Untuk mengganti gaya default, buat paket tambahan dalam MediaDescription item media.

Cuplikan kode berikut menunjukkan cara membuat MediaItem yang dapat dijelajahi yang akan mengganti gaya konten default:

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

Menambahkan item judul

Untuk menyajikan item media sebagai item judul, gunakan gaya konten per-item yang mengelompokkan semua item tersebut bersama-sama. Setiap item media dalam grup harus mendeklarasikan paket tambahan dalam MediaDescription yang menggunakan string identik. String ini digunakan sebagai judul grup dan dapat dilokalkan.

Android Automotive OS dan Android Auto tidak mengurutkan item yang dikelompokkan dengan cara ini. Anda harus meneruskan item media secara bersama-sama dan dalam urutan yang Anda inginkan untuk menampilkannya.

Misalnya, anggaplah aplikasi Anda meneruskan tiga item media dengan urutan sebagai berikut:

  • Item media 1 dengan extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
  • Item media 2 dengan extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")
  • Item media 3 dengan extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")

Android Automotive OS dan Android Auto tidak akan menggabungkan item media 1 dan item media 3 ke dalam grup "Songs". Sebaliknya, item media tersebut akan disimpan terpisah.

Cuplikan kode berikut menunjukkan cara membuat MediaItem dengan heading subgrup "Songs":

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

Menampilkan indikator metadata tambahan

Gambar 3. Tampilan pemutaran dengan indikator metadata

Anda dapat menyertakan indikator metadata tambahan untuk menambahkan informasi sekilas ke konten dalam struktur Media Browse dan selama pemutaran. Dalam struktur penjelajahan ini, Android Automotive OS dan Android Auto membaca paket tambahan yang terkait dengan sebuah item dan mencari konstanta tertentu untuk menentukan indikator mana yang akan ditampilkan. Saat media diputar, Android Automotive OS dan Android Auto membaca metadata untuk sesi media itu dan mencari konstanta tertentu untuk menentukan indikator yang akan ditampilkan.

Gunakan kode berikut untuk mendeklarasikan konstanta indikator metadata dalam aplikasi Anda:

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;
    

Setelah mendeklarasikan konstanta ini, Anda dapat menggunakannya untuk menampilkan indikator metadata. Agar indikator ditampilkan saat pengguna menjelajahi struktur Media Browse, buat paket tambahan yang menyertakan satu atau beberapa konstanta ini dan teruskan ke MediaDescription.Builder.setExtras().

Cuplikan kode berikut menunjukkan cara menampilkan indikator untuk item media eksplisit yang diputar sebagian:

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

Jika ingin menampilkan indikator untuk item media yang sedang diputar, Anda dapat mendeklarasikan nilai Long untuk EXTRA_IS_EXPLICIT atau EXTRA_IS_DOWNLOADED dalam metode MediaMetadata.Builder() untuk mediaSession Anda. Anda tidak dapat menampilkan indikator EXTRA_PLAY_COMPLETION_STATE pada tampilan pemutaran.

Cuplikan kode berikut mengilustrasikan cara menunjukkan bahwa lagu yang sedang dalam tampilan pemutaran adalah lagu eksplisit dan hasil download:

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

Untuk memudahkan pengguna menjelajahi konten, aplikasi Anda dapat mengizinkan pengguna menjelajahi sekelompok hasil penelusuran yang terkait dengan kueri penelusurannya setiap kali pengguna tersebut melakukan penelusuran suara. Android Automotive OS dan Android Auto akan menampilkan hasil ini dalam kolom "Tampilkan hasil lainnya" di antarmuka.

Gambar 4. Opsi Tampilkan hasil lainnya di layar mobil

Untuk menampilkan hasil penelusuran yang dapat dijelajahi, Anda harus membuat sebuah konstanta dan menyertakannya dalam paket tambahan metode onGetRoot() layanan.

Cuplikan kode berikut menunjukkan cara mengaktifkan dukungan dalam metode 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);
    }
    

Untuk mulai menyajikan hasil penelusuran, ganti metode onSearch() dalam Media Browse service Anda. Android Automotive OS dan Android Auto meneruskan istilah penelusuran pengguna ke metode ini setiap kali pengguna memanggil affordance "Tampilkan hasil lainnya". Anda dapat mengatur hasil penelusuran dari metode onSearch() layanan Anda menggunakan item judul agar lebih mudah dijelajahi. Misalnya, jika aplikasi Anda memutar musik, Anda dapat mengatur hasil penelusuran menurut "Album", "Artis", dan "Lagu".

Cuplikan kode berikut menunjukkan implementasi sederhana metode 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
    }
    

Mengaktifkan kontrol pemutaran

Android Automotive OS dan Android Auto mengirim perintah kontrol pemutaran melalui MediaSessionCompat layanan Anda. Anda harus mendaftarkan sesi dan menerapkan metode callback yang terkait.

Mendaftarkan sesi media

Dalam metode onCreate() Media Browse service, buat MediaSessionCompat, lalu daftarkan sesi media tersebut dengan memanggil setSessionToken().

Cuplikan kode berikut menunjukkan cara membuat dan mendaftarkan sesi media:

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

        ...
    }
    

Saat membuat objek sesi media, Anda menetapkan objek callback yang digunakan untuk menangani permintaan kontrol pemutaran. Objek ini dibuat dengan menyediakan implementasi class MediaSessionCompat.Callback untuk aplikasi Anda. Bagian selanjutnya halaman ini membahas cara mengimplementasikan objek ini.

Mengimplementasikan perintah putar

Saat pengguna meminta pemutaran untuk item media dari aplikasi Anda, Android Automotive OS dan Android Auto menggunakan class MediaSessionCompat.Callback dari objek MediaSessionCompat aplikasi Anda yang diperoleh dari Media Browse service aplikasi Anda. Saat pengguna ingin mengontrol pemutaran konten, seperti menjeda pemutaran atau melompat ke trek berikutnya, Android Automotive OS dan Android Auto akan memanggil salah satu metode objek callback tersebut.

Untuk menangani pemutaran konten, aplikasi Anda harus menyediakan class abstrak MediaSessionCompat.Callback dan menerapkan metode yang didukung aplikasi Anda.

Anda harus menerapkan semua metode callback berikut yang sesuai untuk jenis konten yang ditawarkan aplikasi Anda:

onPrepare()
Dipanggil saat sumber media berubah. Android Automotive OS juga memanggil metode ini segera setelah booting. Aplikasi media Anda harus mengimplementasikan metode ini.
onPlay()
Dipanggil jika pengguna memilih untuk memutar item media tanpa memilih item tertentu. Aplikasi Anda akan memutar konten default-nya. Jika pemutaran dijeda dengan onPause(), aplikasi Anda akan melanjutkan pemutaran.

Catatan: Aplikasi Anda tidak boleh mulai memutar musik secara otomatis saat Android Automotive OS atau Android Auto terhubung ke Media Browse service Anda. Untuk informasi selengkapnya, baca Menetapkan status pemutaran awal.

onPlayFromMediaId()
Dipanggil saat pengguna memilih untuk memutar item tertentu. ID yang ditetapkan Media Browse service Anda untuk item media tersebut dalam hierarki konten Anda diteruskan ke metode ini.
onPlayFromSearch()
Dipanggil saat pengguna memilih untuk memutar item media dari kueri penelusuran. Aplikasi harus membuat pilihan yang tepat berdasarkan string penelusuran yang diteruskan.
onPause()
Dipanggil saat pengguna memilih untuk menjeda pemutaran.
onSkipToNext()
Dipanggil saat pengguna memilih untuk melompat ke item berikutnya.
onSkipToPrevious()
Dipanggil saat pengguna memilih untuk melompat ke item sebelumnya.
onStop()
Dipanggil saat pengguna memilih untuk menghentikan pemutaran.

Aplikasi Anda harus mengganti metode ini untuk menyediakan fungsionalitas yang diinginkan. Anda tidak perlu menerapkan metode yang tidak didukung oleh aplikasi Anda. Misalnya, jika aplikasi Anda memutar live streaming (seperti siaran olahraga), metode onSkipToNext() tidak cocok diterapkan, dan sebagai gantinya Anda dapat menggunakan implementasi onSkipToNext() default.

Aplikasi Anda tidak memerlukan logika khusus untuk memutar konten melalui speaker mobil. Saat menerima permintaan untuk memutar konten, aplikasi Anda akan memutar audio seperti biasanya (misalnya, memutar konten melalui speaker atau headphone ponsel pengguna). Android Automotive OS dan Android Auto akan otomatis mengirim konten audio ke sistem mobil untuk diputar melalui speaker mobil.

Untuk informasi selengkapnya tentang memutar konten audio, lihat Pemutaran media, Mengelola pemutaran audio, dan ExoPlayer.

Menetapkan tindakan pemutaran standar

Android Automotive OS dan Android Auto menampilkan kontrol pemutaran berdasarkan tindakan yang diaktifkan dalam objek PlaybackStateCompat.

Secara default, aplikasi Anda harus mendukung tindakan berikut:

Selain itu, sebaiknya Anda membuat antrean pemutaran untuk ditampilkan kepada pengguna. Caranya, panggil metode setQueue() dan setQueueTitle(), aktifkan tindakan ACTION_SKIP_TO_QUEUE_ITEM, lalu tentukan callback onSkipToQueueItem().

Android Automotive OS dan Android Auto menampilkan tombol untuk setiap tindakan yang diaktifkan, serta antrean pemutaran jika Anda memilih untuk membuatnya.

Mencadangkan ruang yang tidak terpakai

Android Automotive OS dan Android Auto mencadangan ruang pada UI untuk tindakan ACTION_SKIP_TO_PREVIOUS dan ACTION_SKIP_TO_NEXT. Selain itu, Android Auto mencadangkan ruang untuk antrean pemutaran. Jika aplikasi Anda tidak mendukung salah satu fungsi ini, Android Automotive OS dan Android Auto akan menggunakan ruang itu untuk menampilkan tindakan kustom apa pun yang Anda buat.

Jika tidak ingin mengisi ruang tersebut dengan tindakan kustom, Anda dapat mencadangkannya sehingga Android Automotive OS dan Android Auto akan mengosongkan ruang itu setiap kali aplikasi Anda tidak mendukung fungsi yang terkait. Untuk melakukan ini, panggil metode setExtras() dengan paket tambahan yang berisi konstanta yang terkait dengan setiap fungsi yang dicadangkan. Tetapkan setiap konstanta yang ingin Anda sediakan ruang ke true.

Cuplikan kode berikut menunjukkan konstanta yang dapat Anda gunakan untuk mencadangkan ruang yang tidak terpakai:

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

Menetapkan PlaybackState awal

Saat Android Automotive OS dan Android Auto berkomunikasi dengan Media Browse service, sesi media Anda mengomunikasikan status pemutaran konten menggunakan PlaybackState. Aplikasi Anda tidak boleh mulai memutar musik secara otomatis saat Android Automotive OS atau Android Auto terhubung ke Media Browse service. Sebagai gantinya, andalkan Android Automotive OS dan Android Auto untuk melanjutkan atau memulai pemutaran berdasarkan kondisi mobil atau tindakan pengguna.

Untuk mencapai hal itu, tetapkan PlaybackState awal sesi media Anda ke STATE_STOPPED, STATE_PAUSED, STATE_NONE, atau STATE_ERROR.

Menambahkan tindakan pemutaran kustom

Anda dapat menambahkan tindakan pemutaran kustom untuk menampilkan tindakan tambahan yang didukung aplikasi media Anda. Jika ruangnya memungkinkan (dan tidak dicadangkan), Android akan menambahkan tindakan kustom ke kontrol transport. Jika tidak, tindakan kustom akan ditampilkan di menu tambahan. Tindakan kustom ditampilkan sesuai urutan penambahannya ke PlaybackState.

Anda dapat menambahkan tindakan ini menggunakan metode addCustomAction() dalam class PlaybackStateCompat.Builder.

Cuplikan kode berikut menunjukkan cara menambahkan tindakan kustom "Mulai saluran radio":

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

Untuk contoh yang lebih terperinci dari metode ini, lihat metode setCustomAction() pada contoh aplikasi Universal Android Music Player di GitHub.

Setelah membuat tindakan kustom, sesi media Anda dapat merespons tindakan dengan mengganti metode onCustomAction().

Cuplikan kode berikut menunjukkan cara aplikasi Anda merespons tindakan "Mulai saluran radio":

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

Untuk contoh yang lebih terperinci dari metode ini, lihat metode onCustomAction pada contoh aplikasi Universal Android Music Player di GitHub.

Ikon untuk tindakan kustom

Setiap tindakan kustom yang Anda buat memerlukan resource ikon. Aplikasi untuk mobil dapat berjalan di berbagai ukuran dan kepadatan layar, sehingga ikon yang Anda sediakan haruslah berupa vector drawable. Vector drawable memungkinkan Anda mengubah skala aset tanpa kehilangan detailnya. Vector drawable juga memudahkan penyelarasan tepi dan sudut dengan batas piksel pada resolusi yang lebih kecil.

Menyediakan gaya ikon alternatif untuk tindakan yang dinonaktifkan

Apabila tindakan kustom tidak tersedia untuk konteks yang sedang aktif, tukar ikon tindakan kustom dengan ikon alternatif yang menunjukkan bahwa tindakan dinonaktifkan.

Gambar 5. Contoh ikon tindakan kustom tidak aktif.

Mendukung tindakan suara

Aplikasi media Anda harus mendukung tindakan suara untuk memberi pengemudi pengalaman yang aman dan nyaman serta minim gangguan. Misalnya, saat aplikasi sedang memutar sebuah item media, pengguna dapat mengatakan "Putar [item]" untuk memintanya memainkan item lain tanpa melihat atau menyentuh layar mobil.

Mendeklarasikan dukungan untuk tindakan suara

Cuplikan kode berikut menunjukkan cara mendeklarasikan dukungan untuk tindakan suara dalam file manifes aplikasi Anda. Anda harus menyertakan kode ini dalam file manifes untuk modul Android Automotive OS dan dalam file manifes untuk aplikasi ponsel Anda.

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

Mengurai kueri penelusuran suara

Saat pengguna menelusuri item media tertentu, seperti "Putar jazz di [nama aplikasi Anda]" atau "Dengarkan [judul lagu]", metode callback onPlayFromSearch() akan menerima hasil penelusuran suara dalam parameter kueri dan paket tambahan.

Aplikasi Anda dapat mengurai kueri penelusuran suara dan memulai pemutaran dengan mengikuti langkah-langkah berikut:

  1. Gunakan paket tambahan dan string kueri penelusuran yang ditampilkan dari penelusuran suara untuk memfilter hasil.
  2. Buat antrean pemutaran berdasarkan hasil ini.
  3. Putar item media yang paling relevan dari hasil.

Metode onPlayFromSearch() mengambil parameter tambahan yang memuat informasi yang lebih terperinci dari penelusuran suara. Parameter tambahan ini membantu Anda menemukan konten audio di aplikasi Anda untuk diputar. Jika hasil penelusuran tidak dapat menyediakan data ini, Anda dapat menerapkan logika untuk mengurai kueri penelusuran mentah dan memutar trek yang sesuai berdasarkan kueri.

Parameter tambahan berikut didukung di Android Automotive OS dan Android Auto:

Cuplikan kode berikut menunjukkan cara mengganti metode onPlayFromSearch() dalam implementasi MediaSession.Callback Anda untuk mengurai kueri penelusuran suara dan memulai pemutaran:

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

Untuk contoh yang lebih terperinci tentang cara menerapkan penelusuran suara untuk memutar konten audio di aplikasi Anda, lihat contoh Universal Media Player.

Menangani kueri kosong

Saat pengguna mengucapkan "Putar musik di [nama aplikasi Anda]", Android Automotive OS atau Android Auto akan mencoba meluncurkan aplikasi Anda dan memutar audio dengan memanggil metode onPlayFromSearch() pada aplikasi Anda. Namun, karena pengguna tidak menyebutkan nama item medianya, metode onPlayFromSearch() akan menerima parameter kueri kosong. Dalam kasus semacam ini, aplikasi Anda harus segera merespons dengan memutar audio, seperti lagu dari playlist terbaru atau antrean acak.

Menerapkan tindakan pemutaran yang diaktifkan dengan suara

Untuk memberikan pengalaman handsfree selagi pengguna mengemudi dan mendengarkan konten media, aplikasi Anda harus memungkinkan pengguna mengontrol pemutaran konten melalui perintah suara. Saat pengguna mengucapkan perintah seperti "Lagu berikutnya", "Jeda musik", atau "Lanjutkan musik", sistem akan memicu metode callback yang sesuai di mana Anda menerapkan tindakan kontrol pemutaran.

Untuk menyediakan perintah pemutaran yang diaktifkan dengan suara, pertama-tama aktifkan kontrol hardware dengan menetapkan flag berikut dalam objek MediaSession aplikasi Anda:

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

Setelah menetapkan flag, terapkan metode callback dengan kontrol pemutaran yang didukung pada aplikasi Anda. Android Automotive OS dan Android Auto mendukung tindakan pemutaran yang diaktifkan dengan suara berikut ini:

Contoh frasa Metode callback
"Lagu berikutnya" onSkipToNext()
"Lagu sebelumnya" onSkipToPrevious()
"Jeda musik" onPause()
"Hentikan musik" onStop()
"Lanjutkan musik" onPlay()

Untuk contoh yang lebih terperinci tentang cara menerapkan tindakan pemutaran yang diaktifkan dengan suara pada aplikasi Anda, lihat contoh Universal Media Player.

Menerapkan aktivitas setelan dan login untuk Android Automotive OS

Selain layanan browser media, Anda juga dapat menambahkan aktivitas Setelan dan Login yang dioptimalkan untuk kendaraan ke aplikasi Android Automotive OS Anda. Aktivitas ini memungkinkan Anda menyediakan fungsionalitas aplikasi yang tidak disertakan dalam Android Media API.

Menambahkan aktivitas Setelan

Anda dapat menambahkan aktivitas Setelan yang dioptimalkan untuk kendaraan sehingga pengguna dapat mengonfigurasi setelan untuk aplikasi Anda di mobil mereka. Aktivitas setelan juga dapat menyediakan alur kerja lain, seperti login atau logout dari akun pengguna atau beralih akun pengguna.

Alur kerja aktivitas Setelan

Aktivitas Setelan dapat menyediakan berbagai alur kerja kepada pengguna. Gambar berikut menunjukkan bagaimana pengguna berinteraksi dengan aktivitas Setelan menggunakan Android Automotive OS:

Alur kerja untuk aktivitas Setelan

Gambar 6. Diagram alur kerja untuk aktivitas Setelan

Mendeklarasikan aktivitas Setelan

Anda harus mendeklarasikan aktivitas Setelan dalam file manifes aplikasi, seperti ditunjukkan dalam cuplikan kode berikut:

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

Menerapkan aktivitas Setelan

Saat pengguna meluncurkan aplikasi Anda, Android Automotive OS akan mendeteksi aktivitas Setelan yang Anda deklarasikan dan menampilkan affordance. Pengguna dapat mengetuk atau memilih affordance ini menggunakan layar mobil untuk mengakses aktivitas. Android Automotive OS mengirimkan intent ACTION_APPLICATION_PREFERENCES yang memberi tahu aplikasi Anda untuk memulai aktivitas setelan.

Menambahkan aktivitas Login

Jika aplikasi Anda mengharuskan pengguna untuk login, Anda dapat menambahkan aktivitas Login yang dioptimalkan untuk kendaraan yang akan menangani alur kerja login dan logout dari aplikasi Anda. Anda juga dapat menambahkan alur kerja login dan logout ke aktivitas Setelan, tetapi Anda harus menggunakan aktivitas Login khusus jika aplikasi Anda tidak dapat digunakan sebelum pengguna login.

Alur kerja aktivitas Login

Gambar berikut menunjukkan bagaimana pengguna berinteraksi dengan aktivitas Login menggunakan Android Automotive OS:

Alur kerja untuk aktivitas Login

Gambar 7. Diagram alur kerja untuk aktivitas Login

Mengharuskan login saat aplikasi dimulai

Untuk mengharuskan pengguna login melalui aktivitas Login agar dapat menggunakan aplikasi Anda, Media Browse service harus melakukan hal-hal berikut:

  1. Tetapkan PlaybackState sesi media ke STATE_ERROR menggunakan metode setState(). Kode ini memberi tahu Android Automotive OS bahwa tidak ada operasi lain yang dapat dijalankan sampai error diselesaikan.
  2. Setel kode error PlaybackState sesi media ke ERROR_CODE_AUTHENTICATION_EXPIRED. Kode ini memberi tahu Android Automotive OS bahwa pengguna perlu memberikan autentikasi.
  3. Setel pesan error PlaybackState sesi media menggunakan metode setErrorMessage(). Karena terlihat oleh pengguna, pesan ini harus dilokalkan sesuai bahasa pengguna saat ini.
  4. Setel parameter tambahan PlaybackState sesi media menggunakan metode setExtras(). Sertakan dua kunci berikut:

    • android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: String yang ditampilkan pada tombol yang memulai alur kerja login. Karena terlihat oleh pengguna, string ini harus dilokalkan sesuai bahasa pengguna saat ini.
    • android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: PendingIntent yang mengarahkan pengguna ke aktivitas Login saat mereka mengetuk tombol yang direferensikan oleh android.media.extras.ERROR_RESOLUTION_ACTION_LABEL.

Cuplikan kode berikut menunjukkan bagaimana aplikasi Anda dapat mengharuskan pengguna untuk login sebelum menggunakan aplikasi Anda:

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

Setelah autentikasi pengguna berhasil, aplikasi Anda harus menetapkan PlaybackState kembali ke status selain STATE_ERROR, lalu membawa pengguna kembali ke Android Automotive OS dengan memanggil metode finish() aktivitas.

Menerapkan aktivitas Login

Google menawarkan berbagai fitur identitas yang dapat digunakan untuk membantu pengguna login ke aplikasi Anda di mobil mereka. Beberapa fitur, seperti Firebase Authentication, menyediakan toolkit tumpukan penuh yang dapat membantu Anda membuat pengalaman autentikasi yang disesuaikan. Fitur lainnya memanfaatkan kredensial yang sudah dimiliki pengguna atau teknologi lain untuk membantu Anda memberikan pengalaman login yang lancar.

Kami merekomendasikan fitur berikut untuk membantu Anda mempermudah proses login bagi pengguna yang sebelumnya sudah login di perangkat lain:

  • Login dengan Google: Jika sudah menerapkan Login dengan Google untuk perangkat lain (seperti aplikasi ponsel), Anda juga harus menerapkan Login dengan Google untuk aplikasi Android Automotive OS agar mendukung pengguna yang sudah Login dengan Google.
  • IsiOtomatis dengan Google: Jika pengguna menggunakan IsiOtomatis dengan Google di perangkat Android lain, kredensial mereka akan disimpan ke pengelola sandi Google. Lalu, saat mereka login ke aplikasi Android Automotive OS, IsiOtomatis dengan Google akan menyarankan kredensial tersimpan yang relevan. Penggunaan IsiOtomatis dengan Google tidak memerlukan upaya pengembangan aplikasi; namun, developer harus mengoptimalkan aplikasi mereka untuk memberikan kualitas yang lebih baik. IsiOtomatis dengan Google didukung oleh semua perangkat yang menjalankan Android Oreo 8.0 (API level 26) atau yang lebih tinggi (termasuk Android Automotive OS).

Menangani tindakan yang dilindungi dengan login

Beberapa aplikasi memungkinkan pengguna mengakses tindakan tertentu secara anonim, tetapi mengharuskan pengguna untuk login agar mereka dapat melakukan tindakan lain. Misalnya, pengguna mungkin dapat memutar musik pada aplikasi sebelum login, tetapi mereka harus login agar dapat melompati lagu.

Dalam kasus semacam ini, saat pengguna mencoba melakukan tindakan yang dibatasi (melompati lagu), aplikasi Anda dapat menyarankan pengguna untuk memberikan autentikasi dengan menampilkan error non-fatal. Dengan error non-fatal, sistem akan menampilkan pesan kepada pengguna tanpa menyela pemutaran item media yang sedang aktif. Untuk menerapkan penanganan error non-fatal, selesaikan langkah berikut:

  1. Setel errorCode untuk PlaybackState sesi media ke ERROR_CODE_AUTHENTICATION_EXPIRED. Kode ini memberi tahu Android Automotive OS bahwa pengguna perlu memberikan autentikasi.
  2. Pertahankan state untuk PlaybackState sesi media apa adanya — jangan tetapkan ke STATE_ERROR. Ini digunakan untuk memberi tahu sistem bahwa error tersebut tidak fatal.
  3. Setel parameter tambahan PlaybackState sesi media menggunakan metode setExtras(). Sertakan dua kunci berikut:

    • android.media.extras.ERROR_RESOLUTION_ACTION_LABEL: String yang ditampilkan pada tombol yang memulai alur kerja login. Karena terlihat oleh pengguna, string ini harus dilokalkan sesuai bahasa pengguna saat ini.
    • android.media.extras.ERROR_RESOLUTION_ACTION_INTENT: PendingIntent yang mengarahkan pengguna ke aktivitas Login saat mereka mengetuk tombol yang direferensikan oleh android.media.extras.ERROR_RESOLUTION_ACTION_LABEL.
  4. Pertahankan status PlaybackState sesi media lainnya apa adanya. Hal ini memungkinkan dilanjutkannya pemutaran item media saat ini sementara pengguna memutuskan apakah akan login atau tidak.

Menerapkan pengamanan dari gangguan

Karena ponsel pengguna terhubung ke speaker mobil saat menggunakan Android Auto, Anda harus mengambil tindakan pencegahan tambahan untuk meminimakan gangguan bagi pengemudi.

Mendeteksi mode mobil

Aplikasi media Android Auto tidak boleh memulai pemutaran audio melalui speaker mobil kecuali jika pengguna memulai pemutaran dengan sengaja (misalnya dengan mengetuk tombol putar di aplikasi Anda). Bahkan alarm yang dijadwalkan pengguna dari aplikasi media pun tidak boleh mulai memutar musik melalui speaker mobil. Untuk memenuhi persyaratan ini, aplikasi Anda harus menentukan apakah ponsel sedang dalam mode mobil sebelum memutar audio apa pun. Untuk memeriksa apakah ponsel dalam mode mobil atau tidak, aplikasi Anda dapat memanggil metode getCurrentModeType().

Jika ponsel pengguna sedang dalam mode mobil, aplikasi media yang mendukung alarm harus melakukan salah satu tindakan berikut:

  • Menonaktifkan alarm.
  • Memutar alarm melalui STREAM_ALARM, dan menyediakan UI pada layar ponsel untuk menonaktifkan alarm.

Cuplikan kode berikut menunjukkan cara memeriksa apakah aplikasi berjalan dalam mode mobil:

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

Menangani iklan media

Secara default, Android Auto menampilkan notifikasi saat metadata media berubah selama sesi pemutaran audio. Saat aplikasi media beralih dari memutar musik ke menjalankan iklan, akan sangat mengganggu (dan tidak perlu) jika notifikasi ditampilkan kepada pengguna. Agar Android Auto tidak menampilkan pemberitahuan dalam situasi semacam ini, Anda harus menetapkan kunci metadata media android.media.metadata.ADVERTISEMENT ke 1, seperti ditunjukkan dalam cuplikan kode berikut:

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

Menangani error umum

Saat aplikasi mengalami error, Anda harus menetapkan status pemutarannya ke STATE_ERROR dan menampilkan pesan error menggunakan metode setErrorMessage(). Pesan error harus terlihat oleh pengguna dan dilokalkan sesuai bahasa pengguna saat ini. Selanjutnya Android Automotive OS dan Android Auto akan menampilkan pesan error kepada pengguna.

Untuk informasi lebih lanjut tentang status error, lihat Menangani sesi media: Status dan error.

Jika pengguna Android Auto perlu membuka aplikasi ponsel Anda untuk menyelesaikan error, pesan Anda harus menyampaikan informasi itu kepada pengguna. Misalnya, pesan error Anda akan berbunyi "Login ke [nama aplikasi Anda]" alih-alih "Harap login".

Referensi lainnya