Elementy sterujące multimediami w Androidzie znajdują się w pobliżu Szybkich ustawień. Sesje z różnych aplikacji są uporządkowane w karuzeli, którą można przewijać. Karuzela zawiera listę sesji w takim porządku:
- strumieniowe odtwarzanie lokalnie na telefonie,
- strumieniowanie zdalne, np. na urządzeniach zewnętrznych lub w sesjach przesyłania treści;
- poprzednie sesje, które można wznowić, w kolejności, w jakiej były ostatnio odtwarzane;
Począwszy od Androida 13 (poziom API 33) użytkownicy mają dostęp do bogatego zestawu elementów sterujących multimediów w aplikacjach odtwarzających multimedia. Przyciski akcji w elementach sterujących multimediami są pobierane ze stanu Player
.
Dzięki temu możesz prezentować spójny zestaw elementów sterowania multimediami i zapewnić lepsze sterowanie multimediami na różnych urządzeniach.
Na rysunku 1. widać, jak to wygląda na telefonie i tablecie.
System wyświetla maksymalnie 5 przycisków akcji w zależności od stanu Player
, jak opisano w tabeli poniżej. W trybie kompaktowym wyświetlane są tylko 3 pierwsze sloty akcji. Jest to zgodne ze sposobem wyświetlania elementów sterujących multimediami na innych platformach Androida, takich jak Auto, Asystent czy Wear OS.
Boks | Kryteria | Działanie |
---|---|---|
1 |
playWhenReady
jest fałszywy lub bieżący stan odtwarzania to STATE_ENDED .
|
Odtwórz |
playWhenReady jest równa 1, a obecny stan odtwarzania to STATE_BUFFERING .
|
Wskaźnik postępu wczytywania | |
playWhenReady jest równa 1, a obecny stan odtwarzania to STATE_READY . |
Wstrzymaj | |
2 | Dostępne jest polecenie dotyczące odtwarzacza COMMAND_SEEK_TO_PREVIOUS lub COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM . |
Wstecz |
Nie jest dostępna ani komenda odtwarzacza COMMAND_SEEK_TO_PREVIOUS , ani COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM . Do wypełnienia boksu jest dostępna komenda niestandardowa z niestandardowego układu, która nie została jeszcze umieszczona. |
Możliwość | |
Dodatkowe dane sesji obejmują wartość logiczną true dla klucza EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV . |
Puste | |
3 | Dostępne jest polecenie dotyczące odtwarzacza COMMAND_SEEK_TO_NEXT lub COMMAND_SEEK_TO_NEXT_MEDIA_ITEM . |
Dalej |
Nie jest dostępna ani komenda odtwarzacza COMMAND_SEEK_TO_NEXT , ani COMMAND_SEEK_TO_NEXT_MEDIA_ITEM . Do wypełnienia boksu jest dostępna komenda niestandardowa z niestandardowego układu, która nie została jeszcze umieszczona. |
Możliwość | |
Dodatkowe dane sesji obejmują wartość logiczną true dla klucza EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT . |
Puste | |
4 | Do wypełnienia boksu jest dostępne polecenie niestandardowe z niestandardowego układu, które nie zostało jeszcze umieszczone. | Możliwość |
5 | Do wypełnienia boksu jest dostępne polecenie niestandardowe z niestandardowego układu, które nie zostało jeszcze umieszczone. | Możliwość |
Polecenia niestandardowe są umieszczane w kolejności, w jakiej zostały dodane do układu niestandardowego.
Dostosowywanie przycisków poleceń
Aby dostosować elementy sterujące systemem multimediów za pomocą Jetpack Media3, możesz odpowiednio ustawić niestandardowy układ sesji i dostępne polecenia sterowników podczas wdrażania MediaSessionService
:
W
onCreate()
utwórzMediaSession
i zdefiniuj niestandardowy układ przycisków poleceń.W
MediaSession.Callback.onConnect()
autoryzuj kontrolery, definiując dostępne polecenia, w tym polecenia niestandardowe, wConnectionResult
.W sekcji
MediaSession.Callback.onCustomCommand()
odpowiada 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 ListenableFutureonCustomCommand( 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
, aby klienci, tacy jak system, mogli łączyć się z Twoją aplikacją do multimediów, znajdziesz w artykule Przyznawanie kontroli innym klientom.
W Jetpack Media3, gdy wdrożesz MediaSession
, Twoja PlaybackState
będzie automatycznie aktualizowana przez odtwarzacz multimediów. Podobnie, gdy wdrożysz MediaSessionService
, biblioteka automatycznie opublikuje MediaStyle
powiadomienie i będzie je aktualizować.
Odpowiadanie na przyciski poleceń
Gdy użytkownik kliknie przycisk działania w systemie sterowania multimediów, system MediaController
wyśle do MediaSession
polecenie odtwarzania. Następnie MediaSession
przekazuje te polecenia odtwarzaczowi. Polecenia zdefiniowane w interfejsie Player
Media3 są obsługiwane automatycznie przez sesję multimediów.
Więcej informacji o tym, jak odpowiadać na polecenia niestandardowe, znajdziesz w artykule Dodawanie poleceń niestandardowych.
Zachowanie w wersjach starszych niż Android 13
Ze względu na zgodność wsteczną interfejs systemowy nadal udostępnia alternatywny układ, który używa działań powiadomienia w przypadku aplikacji, które nie zostały zaktualizowane do wersji Android 13 lub nie zawierają informacji PlaybackState
. Przyciski poleceń są pobierane 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, które zależą od wartości przekazanych do setShowActionsInCompactView()
.
Działania niestandardowe są umieszczane w takim samym porządku, w jakim zostały dodane do PlaybackState
.
Ten przykładowy kod pokazuje, 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 wznawiania multimediów
Wznowienie multimediów umożliwia użytkownikom wznowienie poprzednich sesji z karuzeli bez konieczności uruchamiania aplikacji. Po rozpoczęciu odtwarzania użytkownik może normalnie korzystać z elementów sterujących multimediów.
Funkcję wznawiania odtwarzania można włączać i wyłączać 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 pojawia się po przesunięciu rozszerzonego karuzela.
Media3 oferuje interfejsy API, które ułatwiają obsługę wznawiania multimediów. Więcej informacji o wdrażaniu tej funkcji znajdziesz w dokumentacji Wznowienie odtwarzania za pomocą Media3.
Korzystanie ze starszych interfejsów API mediów
Z tej sekcji dowiesz się, jak zintegrować się z systemowymi elementami sterującymi multimediami za pomocą starszych interfejsów MediaCompat API.
System pobiera te informacje z MediaSession
MediaMetadata
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ć prawidłowe i dokładne powiadomienie o sterowaniu multimediami, ustaw wartość metadanych METADATA_KEY_TITLE
lub METADATA_KEY_DISPLAY_TITLE
na tytuł odtwarzanego obecnie multimediów.
Odtwarzacz multimediów wyświetla upłynięty czas odtwarzania multimediów oraz suwak przesuwania, który jest mapowany na MediaSession
PlaybackState
.
Odtwarzacz multimediów pokazuje postęp odtwarzania bieżących multimediów oraz pasek przewijania mapowany na MediaSession
PlaybackState
. Pasek przesuwania umożliwia użytkownikom zmianę pozycji i wyświetla upływający czas odtwarzania treści multimedialnej. Aby włączyć suwak, musisz zaimplementować PlaybackState.Builder#setActions
i uwzględnić ACTION_SEEK_TO
.
Boks | Działanie | Kryteria |
---|---|---|
1 | Odtwórz |
Obecny stan PlaybackState to jeden z tych:
|
Wskaźnik postępu wczytywania |
Obecny stan usługi PlaybackState to:
|
|
Wstrzymaj | Obecny stan PlaybackState nie jest żaden z wymienionych powyżej. |
|
2 | Wstecz | PlaybackState Działania obejmują ACTION_SKIP_TO_PREVIOUS . |
Możliwość | PlaybackState Działania nie obejmują ACTION_SKIP_TO_PREVIOUS i PlaybackState , a działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze umieszczone. |
|
Puste | PlaybackState extras obejmują 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 i PlaybackState , a działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze umieszczone. |
|
Puste | PlaybackState extras obejmują 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 umieszczone. |
5 | Możliwość | PlaybackState Działania niestandardowe obejmują działanie niestandardowe, które nie zostało jeszcze umieszczone. |
Dodawanie działań standardowych
Poniższe przykłady kodu pokazują, jak dodawać standardowe i niestandardowe działania PlaybackState
.
Aby ustawić odtwarzanie, wstrzymanie, odtwarzanie poprzedniego i następnego elementu, użyj PlaybackState
w 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 umieszczać żadnych przycisków w poprzednich lub kolejnych slotach, nie dodawaj przycisków ACTION_SKIP_TO_PREVIOUS
ani ACTION_SKIP_TO_NEXT
, a zamiast tego dodaj dodatkowe elementy do 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);
Dodawanie działań niestandardowych
Jeśli chcesz wyświetlać inne działania w przyciskach sterowania multimediami, możesz utworzyć PlaybackStateCompat.CustomAction
i dodać go do PlaybackState
. Te działania są wyświetlane w kolejności dodania.
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);
Odpowiadanie na działania PlaybackState
Gdy użytkownik kliknie przycisk, SystemUI użyje interfejsu MediaController.TransportControls
, aby wysłać polecenie do MediaSession
. Musisz zarejestrować wywołanie zwrotne, które będzie odpowiednio 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 była widoczna w obszarze szybkich ustawień, musisz utworzyć powiadomienie MediaStyle
z ważnym tokenem MediaSession
.
Aby wyświetlić tytuł powiadomienia MediaStyle, użyj elementu NotificationBuilder.setContentTitle()
.
Aby wyświetlić ikonę marki odtwarzacza, użyj atrybutu NotificationBuilder.setSmallIcon()
.
Aby obsługiwać 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 multimedialnych i zapewnia elementy sterujące, za pomocą których można ponownie uruchomić odtwarzanie w każdej z nich.
System próbuje połączyć się z usługą MediaBrowserService
za pomocą połączenia z interfejsem SystemUI. Aplikacja musi zezwalać na takie połączenia, w przeciwnym razie nie będzie można wznowić odtwarzania.
Połączenia z SystemUI można zidentyfikować i zweryfikować za pomocą nazwy pakietucom.android.systemui
i podpisu. SystemUI jest podpisany podpisem platformy. Przykład sprawdzenia podpisu platformy znajdziesz w aplikacji UAMP.
Aby obsługiwać wznawianie odtwarzania, MediaBrowserService
musi:
Funkcja
onGetRoot()
musi szybko zwracać niezerową wartość. Inna skomplikowana logika powinna być obsługiwana wonLoadChildren()
Gdy wywołujemy funkcję
onLoadChildren()
w przypadku głównego identyfikatora multimediów, wynik musi zawierać podelement FLAG_PLAYABLE.MediaBrowserService
powinien zwracać ostatnio odtwarzany element multimediów, gdy otrzyma zapytanie EXTRA_RECENT. Zwracana wartość powinna być rzeczywistym elementem multimedialnym, a nie ogólną funkcją.MediaBrowserService
musi zawierać odpowiednią MediaDescription z niepustym title i subtitle. Należy też ustawić identyfikator URI ikony lub pikselową ikonę bitmapową.
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); }