Sterowanie multimediami

Elementy sterujące multimediami na Androidzie znajdują się w pobliżu Szybkich ustawień. Sesje z różnych aplikacji są rozmieszczone w przesuwanej karuzeli. Sesje są wyświetlane w karuzeli w takiej kolejności:

  • Strumienie odtwarzane lokalnie na telefonie
  • strumienie zdalne, np. wykryte na urządzeniach zewnętrznych lub w sesjach przesyłania;
  • Poprzednie wznawialne sesje, w kolejności, w jakiej były ostatnio odtwarzane

Począwszy od Androida 13 (poziom interfejsu API 33), aby użytkownicy mieli dostęp do zestawu elementów sterujących multimediami w aplikacjach odtwarzających multimedia, przyciski poleceń w elementach sterujących multimediami pochodzą ze stanu Player.

Dzięki temu możesz prezentować spójny zestaw opcji sterowania multimediami i lepiej sterować multimediami na różnych urządzeniach.

Ilustracja 1 pokazuje przykład, jak to wygląda na telefonie i tablecie.

Opcje sterowania multimediami pod kątem tego, jak wyglądają na telefonach i tabletach, na przykładzie przykładowej ścieżki audio, na której widać, jak mogą wyglądać przyciski.
Rysunek 1. Opcje sterowania multimediami na telefonach i tabletach

System wyświetla maksymalnie 5 przycisków działań w zależności od stanu Player, zgodnie z opisem w poniższej tabeli. W trybie kompaktowym wyświetlane są tylko 3 pierwsze przedziały czasu działań. Dzięki temu opcje sterowania multimediami są renderowane na innych platformach Androida, takich jak Auto, Asystent i Wear OS.

Boks Kryteria Działanie
1 playWhenReady ma wartość Fałsz lub bieżący stan odtwarzania to STATE_ENDED. Odtwarzaj
playWhenReady ma wartość true (prawda), a obecny stan odtwarzania to STATE_BUFFERING. Wskaźnik postępu ładowania
playWhenReady ma wartość true (prawda), a obecny stan odtwarzania to STATE_READY. Wstrzymaj
2 Dostępne jest polecenie odtwarzacza COMMAND_SEEK_TO_PREVIOUS lub COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM. Wstecz
Nie jest dostępne ani polecenie odtwarzacza COMMAND_SEEK_TO_PREVIOUS, ani COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM. W boksie dostępne jest też niestandardowe polecenie z układu niestandardowego, które nie zostało jeszcze umieszczone. Możliwość
(funkcja nie jest jeszcze obsługiwana w przypadku Media3) rozwiązania PlaybackState zawierają wartość logiczną true dla klucza EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV. Puste
3 Dostępne jest polecenie odtwarzacza COMMAND_SEEK_TO_NEXT lub COMMAND_SEEK_TO_NEXT_MEDIA_ITEM. Dalej
Nie jest dostępne ani polecenie odtwarzacza COMMAND_SEEK_TO_NEXT, ani COMMAND_SEEK_TO_NEXT_MEDIA_ITEM. W boksie dostępne jest też niestandardowe polecenie z układu niestandardowego, które nie zostało jeszcze umieszczone. Możliwość
(funkcja nie jest jeszcze obsługiwana w przypadku Media3) rozwiązania PlaybackState zawierają wartość logiczną true dla klucza EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT. Puste
4 W boksie jest dostępne polecenie niestandardowe z układu niestandardowego, które nie zostało jeszcze umieszczone. Możliwość
5 W boksie jest dostępne polecenie niestandardowe z układu niestandardowego, które nie zostało jeszcze umieszczone. Możliwość

Polecenia niestandardowe są rozmieszczane w takiej kolejności, w jakiej zostały dodane do układu niestandardowego.

Dostosuj przyciski poleceń

Aby dostosować systemowe elementy sterujące multimediami za pomocą Jetpack Media3, podczas implementowania MediaSessionService możesz odpowiednio ustawić niestandardowy układ sesji i dostępne polecenia kontrolerów:

  1. W onCreate() utwórz MediaSession i zdefiniuj układ niestandardowy przycisków poleceń.

  2. W MediaSession.Callback.onConnect() autoryzuj kontrolery, definiując ich dostępne polecenia, w tym polecenia niestandardowe, w ConnectionResult.

  3. W narzędziu MediaSession.Callback.onCustomCommand() zareaguj na polecenie niestandardowe wybrane przez użytkownika.

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

Więcej informacji o konfigurowaniu MediaSession w taki sposób, aby klienty takie jak system mógł łączyć się z Twoją aplikacją do multimediów, znajdziesz w artykule Przyznawanie kontroli innym klientom.

W Jetpack Media3 po zaimplementowaniu MediaSession urządzenie PlaybackState automatycznie aktualizuje odtwarzacz. Podobnie, gdy wdrożysz MediaSessionService, biblioteka automatycznie publikuje powiadomienie MediaStyle i aktualizuje je.

Reagowanie na przyciski poleceń

Gdy użytkownik kliknie przycisk polecenia w systemowych elementach sterujących multimediami, MediaController systemu wysyła polecenie odtwarzania do urządzenia MediaSession. MediaSession przekazuje te polecenia do odtwarzacza. Polecenia zdefiniowane w interfejsie Player w Media3 są automatycznie obsługiwane przez sesję multimediów.

Wskazówki odpowiadania na takie polecenia znajdziesz w sekcji Dodawanie poleceń niestandardowych.

Działanie sprzed Androida 13

W celu zapewnienia zgodności wstecznej interfejs systemu nadal zawiera alternatywny układ, w którym można używać działań związanych z powiadomieniami z aplikacji, które nie są przeznaczone na Androida 13 lub nie zawierają informacji z kategorii PlaybackState. Przyciski poleceń pochodzą z listy Notification.Action dołączonej do powiadomienia MediaStyle. System wyświetla maksymalnie 5 działań w kolejności, w jakiej zostały dodane. W trybie kompaktowym wyświetlane są maksymalnie 3 przyciski określone na podstawie wartości przekazywanych do setShowActionsInCompactView().

Działania niestandardowe są umieszczane w kolejności, w jakiej zostały dodane do elementu PlaybackState.

Poniższy przykładowy kod ilustruje, jak dodawać działania do powiadomienia MediaStyle :

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

Wspieranie wznowienia multimediów

Wznowienie multimediów umożliwia użytkownikom ponowne uruchamianie poprzednich sesji z karuzeli bez konieczności uruchamiania aplikacji. Po rozpoczęciu odtwarzania użytkownik korzysta z elementów sterujących multimediami w zwykły sposób.

Funkcję wznowienia odtwarzania możesz włączyć lub wyłączyć w aplikacji Ustawienia w sekcji Dźwięk > Multimedia. Użytkownik może też otworzyć Ustawienia, klikając ikonę koła zębatego, która wyświetla się po przesunięciu palcem po rozwiniętej karuzeli.

Media3 udostępnia interfejsy API, które ułatwiają wznawianie odtwarzania multimediów. Wskazówki dotyczące wdrażania tej funkcji znajdziesz w dokumentacji Wznowienia odtwarzania za pomocą Media3.

Korzystanie ze starszych interfejsów API multimediów

W tej sekcji wyjaśniamy, jak przeprowadzić integrację z systemowymi elementami sterującymi multimediami za pomocą starszych interfejsów MediaCompat API.

System pobiera z elementu MediaMetadata MediaSession te informacje i wyświetla je, gdy są dostępne:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (jeśli czas trwania nie jest ustawiony, pasek przewijania nie pokazuje postępu)

Aby mieć pewność, że masz prawidłowe i dokładne powiadomienie dotyczące sterowania multimediami, ustaw wartość metadanych METADATA_KEY_TITLE lub METADATA_KEY_DISPLAY_TITLE na tytuł odtwarzanego materiału.

Odtwarzacz pokazuje czas, jaki upłynął od aktualnie odtwarzanych multimediów, wraz z paskiem przewijania zmapowanym na MediaSession PlaybackState.

Odtwarzacz pokazuje postęp odtwarzania aktualnie odtwarzanych multimediów oraz pasek przewijania, który jest zmapowany na MediaSession PlaybackState. Pasek przewijania umożliwia zmianę pozycji i wyświetla czas, jaki upłynął od wyświetlenia elementu multimedialnego. Aby włączyć pasek przewijania, musisz zaimplementować parametr PlaybackState.Builder#setActions i uwzględnić ACTION_SEEK_TO.

Boks Działanie Kryteria
1 Odtwarzaj Bieżący stan elementu PlaybackState to:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Wskaźnik postępu ładowania Bieżący stan elementu PlaybackState to:
  • STATE_CONNECTING
  • STATE_BUFFERING
Wstrzymaj Bieżący stan elementu PlaybackState nie stanowi żadnej z powyższych opcji.
2 Wstecz PlaybackState działań, w tym ACTION_SKIP_TO_PREVIOUS.
Możliwość Działania PlaybackState nie obejmują działań niestandardowych ACTION_SKIP_TO_PREVIOUS i PlaybackState, które obejmują działania niestandardowe, które nie zostały jeszcze wykonane.
Puste Rozszerzenia PlaybackState zawierają wartość logiczną true dla klucza SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 Dalej PlaybackState działań, w tym ACTION_SKIP_TO_NEXT.
Możliwość Działania PlaybackState nie obejmują działań niestandardowych ACTION_SKIP_TO_NEXT i PlaybackState, które obejmują działania niestandardowe, które nie zostały jeszcze wykonane.
Puste Rozszerzenia PlaybackState zawierają wartość logiczną true dla klucza SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Możliwość PlaybackState działania niestandardowe obejmują takie, które nie zostały jeszcze zastosowane.
5 Możliwość PlaybackState działania niestandardowe obejmują takie, które nie zostały jeszcze zastosowane.

Dodaj działania standardowe

W przykładach poniżej pokazujemy, jak dodawać standardowe i niestandardowe działania związane z PlaybackState.

W przypadku odtwarzania, wstrzymywania, poprzedniego i następnego ustawiasz te działania w PlaybackState przeznaczonej do tej sesji multimediów.

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

Jeśli nie chcesz dodawać przycisków w poprzednich lub następnych przedziałach, nie dodawaj ACTION_SKIP_TO_PREVIOUS ani ACTION_SKIP_TO_NEXT, a zamiast tego dodaj do sesji dodatki:

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

Dodaj działania niestandardowe

Jeśli chcesz, aby inne działania były widoczne przy użyciu elementów sterujących multimediami, możesz utworzyć element PlaybackStateCompat.CustomAction i dodać go do PlaybackState. Czynności te są wyświetlane w kolejności, w jakiej zostały dodane.

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

Reagowanie na działania OdtwarzanieState

Gdy użytkownik kliknie przycisk, SystemUI używa MediaController.TransportControls, aby wysłać polecenie z powrotem do MediaSession. Musisz zarejestrować wywołanie zwrotne, które potrafi prawidłowo reagować na te zdarzenia.

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

Wznowienie multimediów

Aby aplikacja odtwarzacza pojawiła się w obszarze ustawień szybkich ustawień, musisz utworzyć powiadomienie MediaStyle z prawidłowym tokenem MediaSession.

Aby wyświetlić tytuł powiadomienia MediaStyle, użyj NotificationBuilder.setContentTitle().

Aby wyświetlić ikonę marki w odtwarzaczu, użyj NotificationBuilder.setSmallIcon().

Aby umożliwić wznowienie odtwarzania, aplikacje muszą zaimplementować MediaBrowserService i MediaSession. Element MediaSession musi implementować wywołanie zwrotne onPlay().

Implementacja usługi MediaBrowserService

Po uruchomieniu urządzenia system wyszukuje 5 ostatnio używanych aplikacji multimedialnych i udostępnia elementy sterujące, które umożliwiają ponowne uruchomienie odtwarzania z każdej z nich.

System próbuje skontaktować się z urządzeniem MediaBrowserService przez połączenie z SystemUI. Aplikacja musi zezwolić na takie połączenia. W przeciwnym razie nie będzie w stanie umożliwić wznowienia odtwarzania.

Połączenia z interfejsu SystemUI można identyfikować i weryfikować za pomocą nazwy pakietu com.android.systemui i podpisu. Interfejs SystemUI jest podpisany podpisem platformy. Przykład sposobu sprawdzania zgodności z podpisem platformy znajdziesz w aplikacji UAMP.

Aby umożliwić wznowienie odtwarzania, MediaBrowserService musi zaimplementować te zachowania:

  • Funkcja onGetRoot() musi szybko zwrócić pierwiastek inny niż zero. Inne złożone logiki należy obsługiwać w polu onLoadChildren()

  • Po wywołaniu parametru onLoadChildren() na potrzeby głównego identyfikatora multimediów wynik musi zawierać element podrzędny FLAG_PLAYABLE.

  • Po otrzymaniu zapytania EXTRA_LAST funkcja MediaBrowserService powinna zwrócić ostatnio odtwarzany element multimedialny. Zwrócona wartość powinna być rzeczywistym elementem multimedialnym, a nie ogólną funkcją.

  • MediaBrowserService musi zawierać odpowiedni atrybut MediaDescription z niepustymi wartościami title i podpisami. Powinien też ustawić identyfikator URI ikony lub mapę bitową ikony.

Poniższe przykłady kodu pokazują, jak zaimplementować 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);
}