Android의 미디어 컨트롤은 빠른 설정 근처에 있습니다. 여러 앱의 세션이 스와이프할 수 있는 캐러셀에 정렬됩니다. 캐러셀에 세션 목록이 표시됩니다. 다음 순서로 나열:
- 휴대전화에서 로컬로 재생되는 스트림
- 외부 기기나 전송 세션에서 감지된 것과 같은 원격 스트림
- 마지막으로 재생된 순서로 재개 가능한 이전 세션
Android 13 (API 수준 33)부터 사용자가 풍부한
미디어를 재생하는 앱의 미디어 컨트롤 세트, 미디어 컨트롤의 작업 버튼
Player
상태에서 파생됩니다.
이렇게 하면 일관된 일련의 미디어 컨트롤을 표시하고 더 세련된 느낌을 미디어 제어 환경을 제공합니다
그림 1은 스마트폰 및 태블릿 기기에서 어떻게 표시되는지 보여주는 예를 보여줍니다. 로 나뉩니다.
<ph type="x-smartling-placeholder">시스템은 Player
상태에 따라 최대 5개의 작업 버튼을 다음과 같이 표시합니다.
다음 표에 설명되어 있습니다. 소형 모드에서는 처음 세 작업만
표시됩니다. 이는 미디어 컨트롤이
Auto, 어시스턴트, Wear OS와 같은 Android 플랫폼
슬롯 | 기준 | 작업 |
---|---|---|
1 |
playWhenReady 드림
false이거나 현재 재생입니다.
상태는 STATE_ENDED 입니다.
|
재생 |
playWhenReady 가 true이고 현재 재생 상태가 STATE_BUFFERING 입니다.
|
로딩 스피너 | |
playWhenReady 가 true이고 현재 재생 상태가 STATE_READY 입니다. |
일시중지 | |
2 | 플레이어 명령어 COMMAND_SEEK_TO_PREVIOUS 또는 COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM 을 사용할 수 있습니다. |
이전 |
플레이어 명령어 COMMAND_SEEK_TO_PREVIOUS 및 COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM 을 모두 사용할 수 없으며, 아직 배치되지 않은 맞춤 레이아웃의 맞춤 명령어를 사용하여 슬롯을 채울 수 있습니다. |
맞춤식 | |
(Media3에서는 아직 지원되지 않음) PlaybackState extras에는 EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV 키의 true 불리언 값이 포함됩니다. |
비어 있음 | |
3 | 플레이어 명령어 COMMAND_SEEK_TO_NEXT 또는 COMMAND_SEEK_TO_NEXT_MEDIA_ITEM 을 사용할 수 있습니다. |
다음 |
플레이어 명령어 COMMAND_SEEK_TO_NEXT 및 COMMAND_SEEK_TO_NEXT_MEDIA_ITEM 을 모두 사용할 수 없으며, 아직 배치되지 않은 맞춤 레이아웃의 맞춤 명령어를 사용하여 슬롯을 채울 수 있습니다. |
맞춤식 | |
(Media3에서는 아직 지원되지 않음) PlaybackState extras에는 EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT 키의 true 불리언 값이 포함됩니다. |
비어 있음 | |
4 | 아직 배치되지 않은 맞춤 레이아웃의 맞춤 명령을 사용하여 슬롯을 채울 수 있습니다. | 맞춤식 |
5 | 아직 배치되지 않은 맞춤 레이아웃의 맞춤 명령을 사용하여 슬롯을 채울 수 있습니다. | 맞춤식 |
맞춤 명령은 맞춤 레이아웃을 제공합니다.
명령어 버튼 맞춤설정
Jetpack Media3으로 시스템 미디어 컨트롤을 맞춤설정하려면 다음 단계를 따르세요.
세션의 맞춤 레이아웃과 사용 가능한 명령을 설정할 수 있습니다.
MediaSessionService
를 구현할 때 적절하게 컨트롤러를 적용합니다.
onCreate()
에서MediaSession
를 빌드합니다. 및 맞춤 레이아웃 정의 실행할 수 있습니다MediaSession.Callback.onConnect()
에서 다음을 포함하여 사용 가능한 명령을 정의하여 컨트롤러를 승인합니다. 맞춤 명령어와 (ConnectionResult
)MediaSession.Callback.onCustomCommand()
에서 자동으로 응답할 수 있습니다.
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) } } }
자바
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); } } }
다음과 같은 클라이언트가 되도록 MediaSession
를 구성하는 방법에 관해 자세히 알아보세요.
미디어 앱에 연결할 수 있습니다.
다른 클라이언트에 제어 권한 부여
Jetpack Media3을 사용하여 MediaSession
를 구현하면 PlaybackState
미디어 플레이어와 함께 자동으로 최신 상태로 유지됩니다. 마찬가지로
MediaSessionService
를 구현하면 라이브러리는 자동으로
MediaStyle
알림
지속적으로 업데이트할 수 있습니다
작업 버튼에 응답
사용자가 시스템 미디어 컨트롤에서 작업 버튼을 탭하면 시스템의
MediaController
는 MediaSession
에 재생 명령어를 전송합니다. 이
그러면 MediaSession
는 이러한 명령어를 플레이어에게 위임합니다. 명령어
Media3의 Player
에 정의됨
인터페이스는 자동으로 미디어에 의해 처리됩니다.
세션입니다.
자세한 내용은 맞춤 명령어 추가를 참조하세요. 참조하세요.
Android 13 이전 동작
이전 버전과의 호환성을 위해 시스템 UI는 대체 레이아웃을 계속 제공합니다.
Android 13을 타겟팅하도록 업데이트되지 않는 앱의 알림 작업을 사용합니다.
또는 PlaybackState
정보를 포함하지 않는 광고 소재입니다. 작업 버튼은
MediaStyle
에 연결된 Notification.Action
목록에서 파생됨
있습니다. 시스템은 작업을 수행한 순서대로 최대 5개의 작업을 표시합니다.
이(가) 추가되었습니다. 소형 모드에서는 최대 3개의 버튼이 표시되며, 이는
setShowActionsInCompactView()
에 전달된 값입니다.
맞춤 작업은 PlaybackState
에 추가된 순서대로 배치됩니다.
다음 코드 예는 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()
자바
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();
미디어 재개 지원
미디어를 재개하면 사용자가 캐러셀에서 이전 세션을 다시 시작할 수 있음 바로 시작할 수 있습니다. 재생이 시작되면 사용자는 제어할 수 있습니다.
재생 재개 기능은 설정 앱을 사용하여 켜고 끌 수 있습니다. 소리 > Media 옵션이 있습니다. 사용자는 다음을 통해 설정에 액세스할 수도 있습니다. 확장된 캐러셀을 스와이프하면 표시되는 설정 아이콘을 탭합니다.
Media3은 미디어 재개를 더 쉽게 지원하는 API를 제공합니다. 자세한 내용은 Media3로 재생 재개 이 기능 구현에 대한 안내를 참조하세요.
기존 미디어 API 사용
이 섹션에서는 다음을 사용하여 시스템 미디어 컨트롤과 통합하는 방법을 설명합니다. 기존 MediaCompat API입니다.
시스템은 MediaSession
의 MediaMetadata
에서 다음 정보를 검색하여 사용 가능한 경우 표시합니다.
METADATA_KEY_ALBUM_ART_URI
METADATA_KEY_TITLE
METADATA_KEY_DISPLAY_TITLE
METADATA_KEY_ARTIST
METADATA_KEY_DURATION
(지속 시간이 설정되지 않으면 탐색 막대가 작동하지 않음) 진행률 표시)
유효하고 정확한 미디어 제어 알림을 받으려면
METADATA_KEY_TITLE
또는 METADATA_KEY_DISPLAY_TITLE
의 값 설정
메타데이터를 추가합니다.
미디어 플레이어는 현재 재생 중인 미디어의 경과 시간과 MediaSession
PlaybackState
에 매핑된 탐색 막대를 표시합니다.
미디어 플레이어는 현재 재생 중인 미디어의 진행 상황을
MediaSession
PlaybackState
에 매핑된 탐색 막대 탐색바
사용자가 위치를 변경할 수 있도록 하고 미디어의 경과 시간을 표시합니다.
있습니다. 탐색바를 활성화하려면
PlaybackState.Builder#setActions
및 ACTION_SEEK_TO
를 포함합니다.
슬롯 | 작업 | 기준 |
---|---|---|
1 | 재생 |
PlaybackState 의 현재 상태는 다음 중 하나입니다.
<ph type="x-smartling-placeholder">
|
로딩 스피너 |
PlaybackState 의 현재 상태는 다음 중 하나입니다.
|
|
일시중지 | PlaybackState 의 현재 상태는 위에 없습니다. |
|
2 | 이전 | PlaybackState 작업에는 ACTION_SKIP_TO_PREVIOUS 가 포함됩니다. |
맞춤식 | PlaybackState 작업에는 ACTION_SKIP_TO_PREVIOUS 가 포함되지 않고 PlaybackState 맞춤 작업에는 아직 배치되지 않은 맞춤 작업이 포함됩니다. |
|
비어 있음 | PlaybackState extras에는 SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV 키의 true 불리언 값이 포함됩니다. |
|
3 | 다음 | PlaybackState 작업에는 ACTION_SKIP_TO_NEXT 가 포함됩니다. |
맞춤식 | PlaybackState 작업에는 ACTION_SKIP_TO_NEXT 가 포함되지 않고 PlaybackState 맞춤 작업에는 아직 배치되지 않은 맞춤 작업이 포함됩니다. |
|
비어 있음 | PlaybackState extras에는 SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT 키의 true 불리언 값이 포함됩니다. |
|
4 | 맞춤식 | PlaybackState 맞춤 작업에는 아직 배치되지 않은 맞춤 작업이 포함됩니다. |
5 | 맞춤식 | PlaybackState 맞춤 작업에는 아직 배치되지 않은 맞춤 작업이 포함됩니다. |
표준 작업 추가
다음 코드 예는 PlaybackState
표준 및
맞춤 액션을 만들 수 있습니다.
재생, 일시중지, 이전, 다음 작업의 경우
미디어 세션의 PlaybackState
입니다.
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)
자바
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);
이전 또는 다음 슬롯에 버튼을 표시하지 않으려면
ACTION_SKIP_TO_PREVIOUS
또는 ACTION_SKIP_TO_NEXT
를 선택하고 대신 extras를
있습니다.
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) })
자바
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);
맞춤 작업 추가
미디어 컨트롤에 표시하려는 다른 작업의 경우
PlaybackStateCompat.CustomAction
드림
대신 PlaybackState
에 추가합니다. 이러한 작업은
확인할 수 있습니다
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)
자바
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);
PlaybackState 작업에 응답
사용자가 버튼을 탭하면 SystemUI는
MediaController.TransportControls
드림
명령어를 MediaSession
로 다시 보냅니다. 콜백을 등록해야 합니다.
잘 대응할 수 있게 해 줍니다.
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)
자바
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); } } };
미디어 재개
빠른 설정 영역에 플레이어 앱을 표시하려면 유효한 MediaSession
토큰으로 MediaStyle
알림을 만들어야 합니다.
MediaStyle 알림의 제목을 표시하려면 다음을 사용합니다.
NotificationBuilder.setContentTitle()
미디어 플레이어의 브랜드 아이콘을 표시하려면 NotificationBuilder.setSmallIcon()
을 사용하세요.
재생 재개를 지원하려면 앱에서 MediaBrowserService
및 MediaSession
을 구현해야 합니다. MediaSession
는 onPlay()
콜백을 구현해야 합니다.
MediaBrowserService
구현
기기가 부팅되면 시스템에서는 가장 최근에 사용한 미디어 앱 5개를 찾고 각 앱에서 재생을 다시 시작하는 데 사용할 수 있는 컨트롤을 제공합니다.
시스템은 SystemUI의 연결을 통해 MediaBrowserService
에 연결하려고 합니다. 앱은 이러한 연결을 허용해야 합니다. 허용하지 않으면 재생 재개를 지원할 수 없습니다.
SystemUI의 연결은 패키지 이름 com.android.systemui
및 서명을 사용하여 식별 및 확인할 수 있습니다. SystemUI는 플랫폼 서명으로 서명됩니다. 플랫폼 서명을 확인하는 방법의 예는 UAMP 앱에서 확인할 수 있습니다.
재생 재개를 지원하려면 MediaBrowserService
에서 다음 동작을 구현해야 합니다.
onGetRoot()
는 null이 아닌 루트를 빠르게 반환해야 합니다. 다른 복잡한 로직은onLoadChildren()
에서 처리해야 합니다.onLoadChildren()
이 루트 미디어 ID에서 호출되면 결과에는 FLAG_PLAYABLE 하위 요소가 포함되어야 합니다.MediaBrowserService
는 EXTRA_RECENT 쿼리를 수신할 때 가장 최근에 재생된 미디어 항목을 반환해야 합니다. 반환되는 값은 일반 함수가 아닌 실제 미디어 항목이어야 합니다.MediaBrowserService
는 비어 있지 않은 제목과 자막이 있는 적절한 MediaDescription을 제공해야 합니다. 아이콘 URI나 아이콘 비트맵도 설정해야 합니다.
다음 코드 예에서는 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); }