Controlli multimediali

I controlli multimediali in Android si trovano accanto alle Impostazioni rapide. Sessioni da più app sono disposte in un carosello a scorrimento. Il carosello elenca le sessioni in questo ordine:

  • Stream riprodotti localmente sullo smartphone
  • Stream remoti, ad esempio quelli rilevati su dispositivi esterni o sessioni di trasmissione
  • Sessioni ripristinabili precedenti, nell'ordine in cui sono state giocate l'ultima volta

A partire da Android 13 (livello API 33), per garantire che gli utenti possano accedere a un insieme di controlli multimediali per app che riproducono contenuti multimediali, pulsanti di azione sui controlli multimediali derivano dallo stato Player.

In questo modo, puoi presentare un insieme coerente di controlli multimediali e un un'esperienza di controllo multimediale su più dispositivi.

La figura 1 mostra un esempio di come appare su un telefono e un tablet, rispettivamente.

Controlli multimediali in termini di come vengono visualizzati su smartphone e tablet
            utilizzando un esempio di traccia di esempio che mostra l'aspetto dei pulsanti
Figura 1: controlli multimediali su smartphone e tablet

Il sistema mostra fino a cinque pulsanti di azione in base allo stato Player come descritti nella tabella seguente. In modalità compatta, solo le prime tre azioni gli slot machine. Si allinea al modo in cui i controlli multimediali vengono visualizzati Piattaforme Android come Auto, Assistente e Wear OS.

Slot Criteri Azione
1 playWhenReady è falsa o la riproduzione attuale è STATE_ENDED. Riproduci
playWhenReady è true e lo stato di riproduzione corrente è STATE_BUFFERING. Rotella di caricamento
playWhenReady è true e lo stato di riproduzione corrente è STATE_READY. Metti in pausa
2 È disponibile il comando del player COMMAND_SEEK_TO_PREVIOUS o COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM. Indietro
Non sono disponibili né i comandi COMMAND_SEEK_TO_PREVIOUSCOMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, ma è disponibile un comando personalizzato dal layout personalizzato che non è stato ancora inserito per riempire l'area. Personalizzata
(non ancora supportato con Media3) Gli extra PlaybackState includono un valore booleano true per la chiave EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV. Vuoto
3 È disponibile il comando del player COMMAND_SEEK_TO_NEXT o COMMAND_SEEK_TO_NEXT_MEDIA_ITEM. Avanti
Non sono disponibili né i comandi COMMAND_SEEK_TO_NEXTCOMMAND_SEEK_TO_NEXT_MEDIA_ITEM, ma è disponibile un comando personalizzato dal layout personalizzato che non è stato ancora inserito per riempire l'area. Personalizzata
(non ancora supportato con Media3) Gli extra PlaybackState includono un valore booleano true per la chiave EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT. Vuoto
4 Un comando personalizzato del layout personalizzato che non è stato ancora inserito è disponibile per riempire l'area. Personalizzata
5 Un comando personalizzato del layout personalizzato che non è stato ancora inserito è disponibile per riempire l'area. Personalizzata

I comandi personalizzati vengono posizionati nell'ordine in cui sono stati aggiunti layout personalizzato.

Personalizza i pulsanti di comando

Per personalizzare i controlli multimediali di sistema con Jetpack Media3: puoi impostare il layout personalizzato della sessione e i comandi disponibili controller di conseguenza, quando implementi un MediaSessionService:

  1. In onCreate(), crea una MediaSession e definisci il layout personalizzato dei pulsanti di comando.

  2. In MediaSession.Callback.onConnect(), autorizzare i controller definendo i loro comandi disponibili, tra cui comandi personalizzati, in ConnectionResult.

  3. In MediaSession.Callback.onCustomCommand(), al comando personalizzato selezionato dall'utente.

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

Per scoprire di più sulla configurazione di MediaSession in modo che i client apprezzino sistema può connettersi alla tua app multimediale, vedere Concedi il controllo ad altri clienti.

Con Jetpack Media3, quando implementi un'MediaSession, PlaybackState viene automaticamente aggiornato con il media player. Analogamente, implementare un MediaSessionService, la libreria pubblica automaticamente un MediaStyle notifica e lo tiene aggiornato.

Rispondere ai pulsanti di azione

Quando un utente tocca un pulsante di azione nei controlli multimediali di sistema, il sistema MediaController invia un comando di riproduzione al tuo MediaSession. La MediaSession delega quindi questi comandi al player. Comandi definita in Player di Media3 vengono gestiti automaticamente dai media durante la sessione.

Vedi Aggiungere comandi personalizzati. per indicazioni su come rispondere a un comando personalizzato.

Comportamento prima di Android 13

Per garantire la compatibilità con le versioni precedenti, l'UI di sistema continua a fornire un layout alternativo. che utilizza le azioni di notifica per le app che non si aggiornano in modo da avere come target Android 13 o che non includono informazioni su PlaybackState. I pulsanti di azione derivato dall'elenco Notification.Action allegato a MediaStyle notifica. Il sistema visualizza fino a cinque azioni nell'ordine in cui sono sono stati aggiunti. Nella modalità compatta, vengono visualizzati fino a tre pulsanti, a seconda del valori trasferiti a setShowActionsInCompactView().

Le azioni personalizzate vengono inserite nell'ordine in cui sono state aggiunte alla PlaybackState.

Il seguente esempio di codice illustra come aggiungere azioni a 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();

Supporta la ripresa dei contenuti multimediali

La ripresa dei contenuti multimediali consente agli utenti di riavviare le sessioni precedenti dal carosello senza dover avviare l'app. Quando inizia la riproduzione, l'utente interagisce con i controlli multimediali come di consueto.

La funzione di ripresa della riproduzione può essere attivata e disattivata tramite l'app Impostazioni. in Audio > Opzioni multimediali. L'utente può accedere alle Impostazioni anche Toccando l'icona a forma di ingranaggio visualizzata dopo aver fatto scorrere il dito sul carosello espanso.

Media3 offre API per semplificare il supporto della ripresa dei contenuti multimediali. Consulta le Ripresa della riproduzione con Media3 documentazione per istruzioni sull'implementazione di questa funzionalità.

Utilizzo delle API multimediali legacy

Questa sezione spiega come eseguire l'integrazione con i controlli multimediali di sistema utilizzando le API MediaCompat legacy.

Il sistema recupera le seguenti informazioni MediaMetadata di MediaSession e lo visualizza quando è disponibile:

  • METADATA_KEY_ALBUM_ART_URI
  • METADATA_KEY_TITLE
  • METADATA_KEY_DISPLAY_TITLE
  • METADATA_KEY_ARTIST
  • METADATA_KEY_DURATION (se la durata non è impostata, la barra di scorrimento non mostra i progressi)

Per assicurarti di ricevere una notifica valida e accurata sul controllo dei contenuti multimediali, imposta il valore di METADATA_KEY_TITLE o METADATA_KEY_DISPLAY_TITLE i metadati al titolo dei contenuti multimediali attualmente in riproduzione.

Il media player mostra il tempo trascorso per l'audio in riproduzione i contenuti multimediali, insieme a una barra di scorrimento mappata all'elemento MediaSession PlaybackState.

Il media player mostra l'avanzamento dei contenuti multimediali attualmente in riproduzione, insieme una barra di scorrimento mappata a MediaSession PlaybackState. Barra di scorrimento consente agli utenti di cambiare la posizione e mostra il tempo trascorso per i contenuti multimediali molto utile. Per abilitare la barra di scorrimento, è necessario implementare PlaybackState.Builder#setActions e includi ACTION_SEEK_TO.

Slot Azione Criteri
1 Riproduci Lo stato corrente di PlaybackState è uno dei seguenti:
  • STATE_NONE
  • STATE_STOPPED
  • STATE_PAUSED
  • STATE_ERROR
Rotella di caricamento Lo stato corrente di PlaybackState è uno dei seguenti:
  • STATE_CONNECTING
  • STATE_BUFFERING
Metti in pausa Lo stato corrente di PlaybackState non è uno dei precedenti.
2 Indietro Le azioni di PlaybackState includono ACTION_SKIP_TO_PREVIOUS.
Personalizzata PlaybackState azioni non includono ACTION_SKIP_TO_PREVIOUS e PlaybackState azioni personalizzate includono un'azione personalizzata che non è stata ancora eseguita.
Vuoto PlaybackState Gli extra includono un valore booleano true per la chiave SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV.
3 Avanti Le azioni di PlaybackState includono ACTION_SKIP_TO_NEXT.
Personalizzata PlaybackState azioni non includono ACTION_SKIP_TO_NEXT e PlaybackState azioni personalizzate includono un'azione personalizzata che non è stata ancora eseguita.
Vuoto PlaybackState Gli extra includono un valore booleano true per la chiave SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT.
4 Personalizzata PlaybackState azioni personalizzate includono un'azione personalizzata che non è stata ancora eseguita.
5 Personalizzata PlaybackState azioni personalizzate includono un'azione personalizzata che non è stata ancora eseguita.

Aggiungi azioni standard

I seguenti esempi di codice illustrano come aggiungere PlaybackState standard e azioni personalizzate.

Per i giochi di riproduzione, pausa, precedente e successivo, imposta queste azioni in PlaybackState per la sessione multimediale.

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

Se non vuoi nessun pulsante negli spazi precedenti o successivi, non aggiungere ACTION_SKIP_TO_PREVIOUS o ACTION_SKIP_TO_NEXT e aggiungi extra a la sessione:

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

Aggiungi azioni personalizzate

Per altre azioni che vuoi visualizzare nei controlli multimediali, puoi creare un PlaybackStateCompat.CustomAction e aggiungilo a PlaybackState. Queste azioni vengono mostrate nella nell'ordine di aggiunta.

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

Rispondere alle azioni PlaybackState

Quando un utente tocca un pulsante, SystemUI utilizza MediaController.TransportControls per inviare un comando a MediaSession. Devi registrare una richiamata in grado di rispondere correttamente a questi eventi.

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

Ripresa contenuti multimediali

Per visualizzare l'app del player nell'area delle impostazioni rapide: devi creare una notifica MediaStyle con un token MediaSession valido.

Per visualizzare il titolo della notifica MediaStyle, utilizza NotificationBuilder.setContentTitle().

Per visualizzare l'icona del brand per il media player, utilizza NotificationBuilder.setSmallIcon().

Per supportare la ripresa della riproduzione, le app devono implementare un'MediaBrowserService e MediaSession. MediaSession deve implementare il callback onPlay().

Implementazione di MediaBrowserService

Dopo l'avvio del dispositivo, il sistema cerca i cinque contenuti multimediali utilizzati più di recente app e fornisce controlli che puoi usare per riavviare la riproduzione da ogni app.

Il sistema tenta di contattare il tuo MediaBrowserService con una connessione da UI di sistema. L'app deve consentire queste connessioni, altrimenti non può supportare la ripresa della riproduzione.

Le connessioni da SystemUI possono essere identificate e verificate utilizzando il nome del pacchetto com.android.systemui e firma. SystemUI è firmata con la piattaforma firma. Un esempio di come eseguire un controllo con la firma della piattaforma può essere: disponibile nell'app UAMP.

Per supportare la ripresa della riproduzione, il tuo MediaBrowserService deve implementare questi comportamenti:

  • onGetRoot() deve restituire rapidamente una radice non nullo. Un'altra logica complessa dovrebbe da gestire in onLoadChildren()

  • Quando onLoadChildren() viene chiamato sull'ID media principale, il risultato deve contenere un FLAG_PLAYABLE figlio/a.

  • MediaBrowserService deve restituire l'elemento multimediale riprodotto più di recente quando ricevono un EXTRA_RECENT query. Il valore restituito deve essere un effettivo elemento multimediale e non generico personalizzata.

  • MediaBrowserService deve fornire un documento MediaDescription con un campo non vuoto title e sottotitolo. Deve inoltre impostare URI icona o un bitmap dell'icona.

I seguenti esempi di codice illustrano come implementare 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);
}