Kontrol media

Kontrol media di Android terdapat di dekat Setelan Cepat. Sesi dari beberapa aplikasi disusun dalam carousel yang dapat digeser. Carousel mencantumkan sesi dalam urutan ini:

  • Streaming yang diputar secara lokal di ponsel
  • Streaming jarak jauh, seperti yang terdeteksi pada perangkat eksternal atau sesi transmisi
  • Sesi sebelumnya yang dapat dilanjutkan, sesuai urutan pemutaran terakhirnya

Mulai Android 13 (API level 33), untuk memastikan pengguna dapat mengakses serangkaian kontrol media untuk aplikasi yang memutar media, tombol tindakan pada kontrol media berasal dari status Player.

Dengan cara ini, Anda dapat menyajikan kumpulan kontrol media yang konsisten dan pengalaman kontrol media di seluruh perangkat.

Gambar 1 menunjukkan contoh tampilannya di perangkat ponsel dan tablet, secara berurutan.

Dalam hal tampilannya di perangkat ponsel dan tablet, kontrol media
            menggunakan contoh jalur sampel yang menunjukkan tampilan tombol
Gambar 1: Kontrol media di perangkat ponsel dan tablet

Sistem menampilkan hingga lima tombol tindakan berdasarkan status Player sebagai dijelaskan dalam tabel berikut. Dalam mode rapat, hanya tiga tindakan pertama slot yang ditampilkan. Hal ini sejalan dengan cara kontrol media dirender di Platform Android seperti Auto, Asisten, dan Wear OS.

Slot Kriteria Tindakan
1 playWhenReady salah atau pemutaran saat ini statusadalah STATE_ENDED. Putar
playWhenReady benar dan status pemutaran saat ini adalah STATE_BUFFERING. Memuat indikator lingkaran berputar
playWhenReady benar dan status pemutaran saat ini adalah STATE_READY. Jeda
2 Perintah pemutar COMMAND_SEEK_TO_PREVIOUS atau COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM tersedia. Sebelumnya
Perintah pemutar COMMAND_SEEK_TO_PREVIOUS maupun COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM tidak tersedia, dan perintah kustom dari tata letak kustom yang belum ditempatkan tersedia untuk mengisi slot. Kustom
(belum didukung dengan Media3) tambahan PlaybackState menyertakan nilai boolean true untuk kunci EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV. Kosong
3 Perintah pemutar COMMAND_SEEK_TO_NEXT atau COMMAND_SEEK_TO_NEXT_MEDIA_ITEM tersedia. Berikutnya
Perintah pemutar COMMAND_SEEK_TO_NEXT maupun COMMAND_SEEK_TO_NEXT_MEDIA_ITEM tidak tersedia, dan perintah kustom dari tata letak kustom yang belum ditempatkan tersedia untuk mengisi slot. Kustom
(belum didukung dengan Media3) tambahan PlaybackState menyertakan nilai boolean true untuk kunci EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT. Kosong
4 Perintah kustom dari tata letak kustom yang belum ditempatkan tersedia untuk mengisi slot. Kustom
5 Perintah kustom dari tata letak kustom yang belum ditempatkan tersedia untuk mengisi slot. Kustom

Perintah kustom ditempatkan sesuai urutan penambahannya ke dan tata letak kustom.

Menyesuaikan tombol perintah

Untuk menyesuaikan kontrol media sistem dengan Jetpack Media3, Anda dapat mengatur tata letak khusus sesi dan perintah yang tersedia dari yang sesuai, saat mengimplementasikan MediaSessionService:

  1. Di onCreate(), buat MediaSession dan menentukan tata letak kustom tombol perintah.

  2. Di MediaSession.Callback.onConnect(), memberikan otorisasi kepada pengontrol dengan menentukan perintah yang tersedia, termasuk perintah khusus, di ConnectionResult.

  3. Di MediaSession.Callback.onCustomCommand(), merespons perintah{i> <i}khusus yang dipilih oleh pengguna.

Kotlin

class PlaybackService : MediaSessionService() {
  private val customCommandFavorites = SessionCommand(ACTION_FAVORITES, Bundle.EMPTY)
  private var mediaSession: MediaSession? = null

  override fun onCreate() {
    super.onCreate()
    val favoriteButton =
      CommandButton.Builder()
        .setDisplayName("Save to favorites")
        .setIconResId(R.drawable.favorite_icon)
        .setSessionCommand(customCommandFavorites)
        .build()
    val player = ExoPlayer.Builder(this).build()
    // Build the session with a custom layout.
    mediaSession =
      MediaSession.Builder(this, player)
        .setCallback(MyCallback())
        .setCustomLayout(ImmutableList.of(favoriteButton))
        .build()
  }

  private inner class MyCallback : MediaSession.Callback {
    override fun onConnect(
      session: MediaSession,
      controller: MediaSession.ControllerInfo
    ): ConnectionResult {
    // Set available player and session commands.
    return AcceptedResultBuilder(session)
      .setAvailablePlayerCommands(
        ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
          .remove(COMMAND_SEEK_TO_NEXT)
          .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
          .remove(COMMAND_SEEK_TO_PREVIOUS)
          .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
          .build()
      )
      .setAvailableSessionCommands(
        ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
          .add(customCommandFavorites)
          .build()
      )
      .build()
    }

    override fun onCustomCommand(
      session: MediaSession,
      controller: MediaSession.ControllerInfo,
      customCommand: SessionCommand,
      args: Bundle
    ): ListenableFuture {
      if (customCommand.customAction == ACTION_FAVORITES) {
        // Do custom logic here
        saveToFavorites(session.player.currentMediaItem)
        return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
      }
      return super.onCustomCommand(session, controller, customCommand, args)
    }
  }
}

Java

public class PlaybackService extends MediaSessionService {
  private static final SessionCommand CUSTOM_COMMAND_FAVORITES =
      new SessionCommand("ACTION_FAVORITES", Bundle.EMPTY);
  @Nullable private MediaSession mediaSession;

  public void onCreate() {
    super.onCreate();
    CommandButton favoriteButton =
        new CommandButton.Builder()
            .setDisplayName("Save to favorites")
            .setIconResId(R.drawable.favorite_icon)
            .setSessionCommand(CUSTOM_COMMAND_FAVORITES)
            .build();
    Player player = new ExoPlayer.Builder(this).build();
    // Build the session with a custom layout.
    mediaSession =
        new MediaSession.Builder(this, player)
            .setCallback(new MyCallback())
            .setCustomLayout(ImmutableList.of(favoriteButton))
            .build();
  }

  private static class MyCallback implements MediaSession.Callback {
    @Override
    public ConnectionResult onConnect(
        MediaSession session, MediaSession.ControllerInfo controller) {
      // Set available player and session commands.
      return new AcceptedResultBuilder(session)
          .setAvailablePlayerCommands(
              ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
                .remove(COMMAND_SEEK_TO_NEXT)
                .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
                .remove(COMMAND_SEEK_TO_PREVIOUS)
                .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
                .build())
          .setAvailableSessionCommands(
              ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
                .add(CUSTOM_COMMAND_FAVORITES)
                .build())
          .build();
    }

    public ListenableFuture onCustomCommand(
        MediaSession session,
        MediaSession.ControllerInfo controller,
        SessionCommand customCommand,
        Bundle args) {
      if (customCommand.customAction.equals(CUSTOM_COMMAND_FAVORITES.customAction)) {
        // Do custom logic here
        saveToFavorites(session.getPlayer().getCurrentMediaItem());
        return Futures.immediateFuture(new SessionResult(SessionResult.RESULT_SUCCESS));
      }
      return MediaSession.Callback.super.onCustomCommand(
          session, controller, customCommand, args);
    }
  }
}

Untuk mempelajari lebih lanjut cara mengonfigurasi MediaSession agar klien menyukai terhubung ke aplikasi media Anda, lihat Berikan kontrol ke klien lain.

Dengan Jetpack Media3, saat Anda menerapkan MediaSession, PlaybackState Anda terus diperbarui secara otomatis pada pemutar media. Demikian pula, ketika Anda mengimplementasikan MediaSessionService, library akan otomatis memublikasikan Notifikasi MediaStyle untuk Anda dan tetap {i>up-to-date<i}.

Merespons tombol tindakan

Saat pengguna mengetuk tombol tindakan di kontrol media sistem, MediaController mengirim perintah pemutaran ke MediaSession Anda. Tujuan MediaSession kemudian mendelegasikan perintah tersebut ke pemutar. Perintah yang ditentukan dalam Player Media3 secara otomatis ditangani oleh media sesi.

Lihat Menambahkan perintah kustom untuk mendapatkan panduan tentang cara merespons perintah kustom.

Perilaku Pra-Android 13

Untuk kompatibilitas mundur, UI Sistem terus menyediakan tata letak alternatif yang menggunakan tindakan notifikasi untuk aplikasi yang tidak diupdate untuk menargetkan Android 13, atau yang tidak menyertakan informasi PlaybackState. Tombol tindakan adalah berasal dari daftar Notification.Action yang dilampirkan pada MediaStyle notifikasi. Sistem menampilkan hingga lima tindakan dalam urutan ditambahkan. Dalam mode rapat, hingga tiga tombol ditampilkan, ditentukan oleh nilai yang diteruskan ke setShowActionsInCompactView().

Tindakan kustom ditempatkan sesuai urutan penambahannya ke PlaybackState.

Contoh kode berikut mengilustrasikan cara menambahkan tindakan ke MediaStyle notifikasi :

Kotlin

import androidx.core.app.NotificationCompat
import androidx.media3.session.MediaStyleNotificationHelper

var notification = NotificationCompat.Builder(context, CHANNEL_ID)
        // Show controls on lock screen even when user hides sensitive content.
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        // Add media control buttons that invoke intents in your media service
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
        // Apply the media style template
        .setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build()

Java

import androidx.core.app.NotificationCompat;
import androidx.media3.session.MediaStyleNotificationHelper;

NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_ID)
        .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
        .setSmallIcon(R.drawable.ic_stat_player)
        .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent)
        .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)
        .addAction(R.drawable.ic_next, "Next", nextPendingIntent)
        .setStyle(new MediaStyleNotificationHelper.MediaStyle(mediaSession)
                .setShowActionsInCompactView(1 /* #1: pause button */))
        .setContentTitle("Wonderful music")
        .setContentText("My Awesome Band")
        .setLargeIcon(albumArtBitmap)
        .build();

Mendukung melanjutkan media

Dengan melanjutkan media, pengguna dapat memulai ulang sesi sebelumnya dari carousel tanpa harus memulai aplikasi. Saat pemutaran dimulai, pengguna berinteraksi dengan mengontrol media seperti biasa.

Fitur lanjutkan pemutaran dapat diaktifkan dan dinonaktifkan menggunakan aplikasi Setelan, di bagian Sound > Opsi media. Pengguna juga dapat mengakses Setelan dengan mengetuk ikon roda gigi yang muncul setelah menggeser pada carousel yang diperluas.

Media3 menawarkan API untuk mempermudah dukungan melanjutkan media. Lihat Melanjutkan pemutaran dengan Media3 untuk mendapatkan panduan menerapkan fitur ini.

Menggunakan API media lama

Bagian ini menjelaskan cara mengintegrasikan dengan kontrol media sistem menggunakan MediaCompat API lama.

Sistem mengambil informasi berikut dari MediaMetadata MediaSession, lalu menampilkannya saat tersedia:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (Jika durasi tidak disetel, bilah geser tidak tampilkan kemajuan)

Untuk memastikan Anda memiliki notifikasi kontrol media yang valid dan akurat, tetapkan nilai METADATA_KEY_TITLE atau METADATA_KEY_DISPLAY_TITLE metadata ke judul media yang sedang diputar.

Pemutar media menampilkan waktu berlalu untuk media yang sedang diputar, beserta kolom pencari yang dipetakan ke MediaSession PlaybackState.

Pemutar media menampilkan progres media yang sedang diputar, beserta bilah geser yang dipetakan ke MediaSession PlaybackState. Bilah geser memungkinkan pengguna mengubah posisi dan menampilkan waktu yang berlalu untuk media yang bermanfaat. Agar bilah geser diaktifkan, Anda harus mengimplementasikan PlaybackState.Builder#setActions dan menyertakan ACTION_SEEK_TO.

Slot Tindakan Kriteria
1 Putar Status PlaybackState saat ini adalah salah satu dari berikut:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Memuat indikator lingkaran berputar Status PlaybackState saat ini adalah salah satu dari berikut:
  • STATE_CONNECTING
  • STATE_BUFFERING
Jeda Status PlaybackState saat ini bukan satu pun dari yang disebutkan di atas.
2 Sebelumnya Tindakan PlaybackState menyertakan ACTION_SKIP_TO_PREVIOUS.
Kustom Tindakan PlaybackState tidak menyertakan ACTION_SKIP_TO_PREVIOUS dan tindakan kustom PlaybackState menyertakan tindakan kustom yang belum dilakukan.
Kosong Tambahan PlaybackState menyertakan nilai boolean true untuk kunci SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 Berikutnya Tindakan PlaybackState menyertakan ACTION_SKIP_TO_NEXT.
Kustom Tindakan PlaybackState tidak menyertakan ACTION_SKIP_TO_NEXT dan tindakan kustom PlaybackState menyertakan tindakan kustom yang belum dilakukan.
Kosong Tambahan PlaybackState menyertakan nilai boolean true untuk kunci SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Kustom Tindakan kustom PlaybackState menyertakan tindakan kustom yang belum dilakukan.
5 Kustom Tindakan kustom PlaybackState menyertakan tindakan kustom yang belum dilakukan.

Menambahkan tindakan standar

Contoh kode berikut menggambarkan cara menambahkan PlaybackState standar dan tindakan kustom Anda.

Untuk putar, jeda, sebelumnya, dan berikutnya, tetapkan tindakan ini di PlaybackState untuk sesi media.

Kotlin

val session = MediaSessionCompat(context, TAG)
val playbackStateBuilder = PlaybackStateCompat.Builder()
val style = NotificationCompat.MediaStyle()

// For this example, the media is currently paused:
val state = PlaybackStateCompat.STATE_PAUSED
val position = 0L
val playbackSpeed = 1f
playbackStateBuilder.setState(state, position, playbackSpeed)

// And the user can play, skip to next or previous, and seek
val stateActions = PlaybackStateCompat.ACTION_PLAY
    or PlaybackStateCompat.ACTION_PLAY_PAUSE
    or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    or PlaybackStateCompat.ACTION_SEEK_TO // adding the seek action enables seeking with the seekbar
playbackStateBuilder.setActions(stateActions)

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build())
style.setMediaSession(session.sessionToken)
notificationBuilder.setStyle(style)

Java

MediaSessionCompat session = new MediaSessionCompat(context, TAG);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle();

// For this example, the media is currently paused:
int state = PlaybackStateCompat.STATE_PAUSED;
long position = 0L;
float playbackSpeed = 1f;
playbackStateBuilder.setState(state, position, playbackSpeed);

// And the user can play, skip to next or previous, and seek
long stateActions = PlaybackStateCompat.ACTION_PLAY
    | PlaybackStateCompat.ACTION_PLAY_PAUSE
    | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
    | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
    | PlaybackStateCompat.ACTION_SEEK_TO; // adding this enables the seekbar thumb
playbackStateBuilder.setActions(stateActions);

// ... do more setup here ...

session.setPlaybackState(playbackStateBuilder.build());
style.setMediaSession(session.getSessionToken());
notificationBuilder.setStyle(style);

Jika Anda tidak ingin tombol apa pun di slot sebelumnya atau berikutnya, jangan tambahkan ACTION_SKIP_TO_PREVIOUS atau ACTION_SKIP_TO_NEXT, dan tambahkan informasi ekstra ke sesi:

Kotlin

session.setExtras(Bundle().apply {
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
    putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
})

Java

Bundle extras = new Bundle();
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true);
extras.putBoolean(SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true);
session.setExtras(extras);

Menambahkan tindakan kustom

Untuk tindakan lain yang ingin Anda tampilkan di kontrol media, Anda dapat membuat PlaybackStateCompat.CustomAction dan menambahkannya ke PlaybackState sebagai gantinya. Tindakan ini ditampilkan di urutannya ditambahkan.

Kotlin

val customAction = PlaybackStateCompat.CustomAction.Builder(
    "com.example.MY_CUSTOM_ACTION", // action ID
    "Custom Action", // title - used as content description for the button
    R.drawable.ic_custom_action
).build()

playbackStateBuilder.addCustomAction(customAction)

Java

PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction.Builder(
        "com.example.MY_CUSTOM_ACTION", // action ID
        "Custom Action", // title - used as content description for the button
        R.drawable.ic_custom_action
).build();

playbackStateBuilder.addCustomAction(customAction);

Merespons tindakan PlaybackState

Saat pengguna mengetuk tombol, SystemUI akan menggunakan MediaController.TransportControls untuk mengirim perintah kembali ke MediaSession. Anda harus mendaftarkan callback yang dapat merespons kejadian ini dengan baik.

Kotlin

val callback = object: MediaSession.Callback() {
    override fun onPlay() {
        // start playback
    }

    override fun onPause() {
        // pause playback
    }

    override fun onSkipToPrevious() {
        // skip to previous
    }

    override fun onSkipToNext() {
        // skip to next
    }

    override fun onSeekTo(pos: Long) {
        // jump to position in track
    }

    override fun onCustomAction(action: String, extras: Bundle?) {
        when (action) {
            CUSTOM_ACTION_1 -> doCustomAction1(extras)
            CUSTOM_ACTION_2 -> doCustomAction2(extras)
            else -> {
                Log.w(TAG, "Unknown custom action $action")
            }
        }
    }

}

session.setCallback(callback)

Java

MediaSession.Callback callback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        // start playback
    }

    @Override
    public void onPause() {
        // pause playback
    }

    @Override
    public void onSkipToPrevious() {
        // skip to previous
    }

    @Override
    public void onSkipToNext() {
        // skip to next
    }

    @Override
    public void onSeekTo(long pos) {
        // jump to position in track
    }

    @Override
    public void onCustomAction(String action, Bundle extras) {
        if (action.equals(CUSTOM_ACTION_1)) {
            doCustomAction1(extras);
        } else if (action.equals(CUSTOM_ACTION_2)) {
            doCustomAction2(extras);
        } else {
            Log.w(TAG, "Unknown custom action " + action);
        }
    }
};

Melanjutkan Media

Agar aplikasi pemutar media muncul di area setelan cepat, Anda harus membuat notifikasi MediaStyle dengan token MediaSession yang valid.

Untuk menampilkan judul notifikasi MediaStyle, gunakan NotificationBuilder.setContentTitle().

Untuk menampilkan ikon merek bagi pemutar media, gunakan NotificationBuilder.setSmallIcon().

Untuk mendukung pelanjutan pemutaran, aplikasi harus mengimplementasikan MediaBrowserService dan MediaSession. MediaSession Anda harus menerapkan callback onPlay().

Implementasi MediaBrowserService

Setelah perangkat melakukan booting, sistem akan mencari lima aplikasi media yang terakhir digunakan, lalu menyediakan kontrol yang dapat digunakan untuk memulai ulang pemutaran dari setiap aplikasi.

Sistem akan mencoba menghubungi MediaBrowserService dengan koneksi dari SystemUI. Aplikasi Anda harus mengizinkan koneksi semacam ini, karena jika tidak, aplikasi tidak dapat mendukung pelanjutan pemutaran.

Koneksi dari SystemUI dapat diidentifikasi dan diverifikasi menggunakan nama paket com.android.systemui dan tanda tangan. SystemUI ditandatangani dengan tanda tangan platform. Lihat contoh cara memeriksa tanda tangan platform di aplikasi UAMP.

Untuk mendukung pelanjutan pemutaran, MediaBrowserService harus mengimplementasikan perilaku berikut:

  • onGetRoot() harus menampilkan root non-null dengan cepat. Logika kompleks lainnya harus ditangani di onLoadChildren()

  • Saat onLoadChildren() dipanggil pada ID media root, hasilnya harus memuat turunan FLAG_PLAYABLE.

  • MediaBrowserService harus menampilkan item media yang terakhir diputar saat menerima kueri EXTRA_RECENT. Nilai yang dihasilkan harus berupa item media aktual, bukan fungsi generik.

  • MediaBrowserService harus menyediakan MediaDescription yang sesuai, dengan judul dan subjudul yang tidak kosong. Class ini juga harus menetapkan URI ikon atau bitmap ikon.

Contoh kode berikut ini menggambarkan cara mengimplementasikan onGetRoot().

Kotlin

override fun onGetRoot(
    clientPackageName: String,
    clientUid: Int,
    rootHints: Bundle?
): BrowserRoot? {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your 
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        rootHints?.let {
            if (it.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                val extras = Bundle().apply {
                    putBoolean(BrowserRoot.EXTRA_RECENT, true)
                }
                return BrowserRoot(MY_RECENTS_ROOT_ID, extras)
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return BrowserRoot(MY_MEDIA_ROOT_ID, null)
    }
    // Return an empty tree to disallow browsing.
    return BrowserRoot(MY_EMPTY_ROOT_ID, null)

Java

@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
    Bundle rootHints) {
    ...
    // Verify that the specified package is SystemUI. You'll need to write your
    // own logic to do this.
    if (isSystem(clientPackageName, clientUid)) {
        if (rootHints != null) {
            if (rootHints.getBoolean(BrowserRoot.EXTRA_RECENT)) {
                // Return a tree with a single playable media item for resumption.
                Bundle extras = new Bundle();
                extras.putBoolean(BrowserRoot.EXTRA_RECENT, true);
                return new BrowserRoot(MY_RECENTS_ROOT_ID, extras);
            }
        }
        // You can return your normal tree if the EXTRA_RECENT flag is not present.
        return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    // Return an empty tree to disallow browsing.
    return new BrowserRoot(MY_EMPTY_ROOT_ID, null);
}