Sterowanie multimediami

Elementy sterujące multimediami na Androidzie znajdują się w pobliżu Szybkich ustawień. Sesje z wielu aplikacji są wyświetlane na przesuwanej karuzeli. Karuzela wyświetla sesje w takiej kolejności:

  • Strumieniowo odtwarzane lokalnie na telefonie
  • Strumienie zdalne (np. wykryte na urządzeniach zewnętrznych) lub sesje przesyłania
  • Poprzednie sesje wznawiane (kolejność, w jakiej były ostatnio odtwarzane)

Aby zapewnić użytkownikom dostęp do bogatego zestawu elementów sterujących multimediami w aplikacjach, które odtwarzają multimedia, od Androida 13 (poziom interfejsu API 33) przyciski poleceń są pobierane ze stanu Player.

Dzięki temu możesz zapewnić spójny zestaw opcji sterowania multimediami i zapewnić bardziej dopracowane sterowanie multimediami na różnych urządzeniach.

Rysunek 1 pokazuje przykładową prezentację na telefonie i tablecie.

elementy sterujące multimediami w zakresie ich wyglądu na telefonach i tabletach (przykładowa ścieżka pokazująca, jak mogą wyglądać przyciski).
Rysunek 1. Sterowanie multimediami na telefonach i tabletach

System wyświetla maksymalnie 5 przycisków poleceń na podstawie stanu Player, zgodnie z opisem w tabeli poniżej. W trybie kompaktowym wyświetlają się tylko 3 pierwsze przedziały działań. Ta zasada jest podobna do tego, jak elementy sterujące multimediami są renderowane na innych platformach z Androidem, takich jak Auto, Asystent czy Wear OS.

Boks Kryteria Działanie
1 playWhenReady ma wartość Fałsz lub obecny stan odtwarzania to STATE_ENDED. Odtwórz
playWhenReady to prawda, a bieżący stan odtwarzania to STATE_BUFFERING. Wskaźnik wczytywania
playWhenReady to prawda, a bieżący 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 polecenie odtwarzacza COMMAND_SEEK_TO_PREVIOUS ani COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, a do wypełnienia boksu dostępne jest polecenie niestandardowe z układu niestandardowego, które nie zostało jeszcze umieszczone. Możliwość
(nieobsługiwane jeszcze w przypadku Media3) PlaybackState extras zawiera wartość logiczną true dla klucza EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV. Brak
3 Dostępne jest polecenie odtwarzacza COMMAND_SEEK_TO_NEXT lub COMMAND_SEEK_TO_NEXT_MEDIA_ITEM. Dalej
Nie jest dostępne polecenie odtwarzacza COMMAND_SEEK_TO_NEXT ani COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, a do wypełnienia boksu dostępne jest polecenie niestandardowe z układu niestandardowego, które nie zostało jeszcze umieszczone. Możliwość
(nieobsługiwane jeszcze w przypadku Media3) PlaybackState extras zawiera wartość logiczną true dla klucza EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT. Brak
4 Aby wypełnić boks, dostępne jest polecenie niestandardowe z układu niestandardowego, który nie został jeszcze umieszczony. Możliwość
5 Aby wypełnić boks, dostępne jest polecenie niestandardowe z układu niestandardowego, który nie został jeszcze umieszczony. Możliwość

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

Dostosuj przyciski poleceń

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

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

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

  3. W MediaSession.Callback.onCustomCommand() odpowiedz 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 urządzenia MediaSession tak, aby klienty takie jak system mogły łączyć się z Twoją aplikacją do multimediów, znajdziesz w sekcji Przyznawanie kontroli innym klientom.

Dzięki Jetpack Media3, gdy wdrożysz MediaSession, PlaybackState automatycznie aktualizuje się za pomocą odtwarzacza multimediów. Podobnie gdy zaimplementujesz element MediaSessionService, biblioteka automatycznie opublikuje powiadomienie MediaStyle i będzie je aktualizować.

Reakcja na przyciski poleceń

Gdy użytkownik kliknie przycisk polecenia w systemowych elementach sterujących multimediami, MediaController systemu wyśle polecenie odtwarzania na urządzenie MediaSession. Następnie MediaSession przekazuje te polecenia graczowi. Polecenia zdefiniowane w interfejsie Player Media3 są obsługiwane automatycznie przez sesję multimediów.

Wskazówki dotyczące odpowiadania na polecenia niestandardowe znajdziesz w sekcji Dodawanie poleceń niestandardowych.

Działanie sprzed Androida 13

Aby zapewnić zgodność wsteczną, interfejs systemu nadal udostępnia alternatywny układ, który wykorzystuje działania związane z powiadomieniami w przypadku aplikacji nieaktualizowanych na Androida 13 lub niezawierających informacji 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 zakresu PlaybackState.

W poniższym przykładzie kodu widać, jak dodać 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();

Obsługa wznowienia multimediów

Wznawianie multimediów umożliwia użytkownikom ponowne uruchamianie poprzednich sesji z karuzeli bez konieczności uruchamiania aplikacji. Po rozpoczęciu odtwarzania użytkownik w zwykły sposób wchodzi w interakcję z elementami sterującymi multimediami.

Funkcję wznawiania odtwarzania możesz włączyć lub wyłączyć w sekcji Dźwięk > Multimedia w aplikacji Ustawienia. Użytkownik może też otworzyć Ustawienia, klikając ikonę koła zębatego, która pojawi 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 wznawiania 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 API MediaCompat.

System pobiera te informacje z komponentu MediaMetadata MediaSession 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 otrzymać prawidłowe i dokładne powiadomienie dotyczące sterowania multimediami, jako wartość metadanych METADATA_KEY_TITLE lub METADATA_KEY_DISPLAY_TITLE podaj tytuł aktualnie odtwarzanych multimediów.

Odtwarzacz pokazuje czas odtwarzania aktualnie odtwarzanych multimediów i pasek przewijania, który jest zmapowany na element MediaSession PlaybackState.

Odtwarzacz pokazuje postęp obecnie odtwarzanych multimediów oraz pasek przewijania przypisany do elementu MediaSession PlaybackState. Pasek przewijania umożliwia zmianę pozycji elementu multimedialnego i pokazuje czas, który upłynął od danego elementu multimedialnego. Aby włączyć pasek przewijania, musisz zaimplementować funkcję PlaybackState.Builder#setActions i dodać ACTION_SEEK_TO.

Boks Działanie Kryteria
1 Odtwórz Obecny stan elementu PlaybackState to:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Wskaźnik wczytywania Obecny stan elementu PlaybackState to:
  • STATE_CONNECTING
  • STATE_BUFFERING
Wstrzymaj Obecny stan (PlaybackState) nie jest jednym z powyższych.
2 Wstecz PlaybackState działania obejmują ACTION_SKIP_TO_PREVIOUS.
Możliwość PlaybackState działania nie obejmują ACTION_SKIP_TO_PREVIOUS ani PlaybackState działań niestandardowych, które nie obejmują działania niestandardowego, które nie zostało jeszcze wykonane.
Brak PlaybackState extras zawiera wartość logiczną true dla klucza SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 Dalej PlaybackState działania obejmują ACTION_SKIP_TO_NEXT.
Możliwość PlaybackState działania nie obejmują ACTION_SKIP_TO_NEXT ani PlaybackState działań niestandardowych, które nie obejmują działania niestandardowego, które nie zostało jeszcze wykonane.
Brak PlaybackState extras zawiera wartość logiczną true dla klucza SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Możliwość PlaybackState działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze wykonane.
5 Możliwość PlaybackState działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze wykonane.

Dodaj działania standardowe

Poniższe przykłady kodu pokazują, jak dodać działania standardowe i niestandardowe PlaybackState.

Odtwarzanie, wstrzymywanie, przejście do poprzedniej i następnej trasy ustaw w polu PlaybackState sesji multimedialnej.

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 poprzednim lub następnym przedziale, nie dodawaj elementów ACTION_SKIP_TO_PREVIOUS ani ACTION_SKIP_TO_NEXT, tylko dodatkowe elementy w sesji:

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

Aby wyświetlić inne działania w panelu sterowania multimediami, możesz utworzyć obiekt PlaybackStateCompat.CustomAction i dodać go do elementu PlaybackState. Te działania 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 PlaybackState

Gdy użytkownik kliknie przycisk, SystemUI użyje MediaController.TransportControls, aby przesłać polecenie z powrotem do MediaSession. Musisz zarejestrować wywołanie zwrotne, które może 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 Twoja aplikacja gracza wyświetlała się w obszarze szybkich ustawień, musisz utworzyć powiadomienie MediaStyle z ważnym 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ć wznawianie odtwarzania, aplikacje muszą implementować MediaBrowserService i MediaSession. MediaSession musi implementować wywołanie zwrotne onPlay().

Implementacja usługi MediaBrowserService

Po uruchomieniu urządzenia system wyszukuje 5 ostatnio używanych aplikacji do multimediów i udostępnia elementy sterujące, które można wykorzystać do ponownego uruchomienia odtwarzania z każdej aplikacji.

System próbuje połączyć się z urządzeniem MediaBrowserService przez połączenie z interfejsu SystemUI. Twoja aplikacja musi zezwalać na takie połączenia. W przeciwnym razie nie będzie obsługiwać wznawiania odtwarzania.

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

Aby umożliwić wznawianie odtwarzania, MediaBrowserService musi stosować te zachowania:

  • onGetRoot() musi szybko zwracać pierwiastek o wartości niezerowej. Inne złożone logiki należy obsługiwać w elemencie onLoadChildren()

  • Wywołanie funkcji onLoadChildren() z głównym identyfikatorem mediów w wyniku musi zawierać element podrzędny FLAG_PLAYABLE.

  • Funkcja MediaBrowserService powinna zwracać ostatnio odtwarzany element multimedialny po otrzymaniu zapytania EXTRA_RECENT. Zwrócona wartość powinna być rzeczywistym elementem multimedialnym, a nie funkcją ogólną.

  • MediaBrowserService musi podać odpowiedni MediaDescription, a tytuł i podtytuł nie mogą być puste. Powinien też ustawiać identyfikator URI ikony lub mapę bitową.

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