בקרי המדיה ב-Android נמצאים ליד ההגדרות המהירות. הסשנים ממספר אפליקציות מסודרים בקרוסלה שאפשר להחליק. הסשנים מופיעים בקרוסלה בסדר הזה:
- שידורים חיים שמופעלים באופן מקומי בטלפון
- שידורים מרחוק, כמו אלה שזוהו במכשירים חיצוניים או בסשנים של העברה (cast)
- סשנים קודמים שניתן להמשיך מהם, לפי הסדר שבו הם הופעלו בפעם האחרונה
החל מגרסה Android 13 (רמת API 33), כדי להבטיח שלמשתמשים תהיה גישה לקבוצה עשירה של פקדי מדיה באפליקציות שמפעילות מדיה, לחצני הפעולה בפקדי המדיה נגזרים מהמצב Player
.
כך תוכלו להציג קבוצה עקבית של אמצעי בקרה על מדיה וחוויית בקרה משופרת על המדיה במכשירים השונים.
איור 1 מראה דוגמה לאופן שבו זה נראה בטלפון ובטאבלט, בהתאמה.
המערכת מציגה עד חמישה לחצני פעולה על סמך המצב של Player
, כפי שמתואר בטבלה הבאה. במצב קומפקטי מוצגים רק שלושת חריצי הפעולה הראשונים. האופן הזה תואם לאופן שבו לחצני המדיה מוצגים בפלטפורמות אחרות של Android, כמו Auto, Assistant ו-Wear OS.
משבצת | קריטריונים | פעולה |
---|---|---|
1 |
הערך של playWhenReady הוא שקר או שמצב ההפעלה הנוכחי הוא STATE_ENDED .
|
Play |
הערך של 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 לא זמינות, ופקודת התאמה אישית מהפריסה בהתאמה אישית שעדיין לא הוצבה זמינה כדי למלא את המקום. |
בהתאמה אישית | |
פרטים נוספים על הסשן כוללים ערך בוליאני true למפתח EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV . |
ריק | |
3 | הפקודה לנגן COMMAND_SEEK_TO_NEXT או COMMAND_SEEK_TO_NEXT_MEDIA_ITEM זמינה. |
הבא |
פקודת השחקן COMMAND_SEEK_TO_NEXT או COMMAND_SEEK_TO_NEXT_MEDIA_ITEM לא זמינות, ופקודת התאמה אישית מהפריסה בהתאמה אישית שעדיין לא הוצבה זמינה כדי למלא את המקום. |
בהתאמה אישית | |
פרטים נוספים על הסשן כוללים ערך בוליאני true למפתח EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT . |
ריק | |
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) } } }
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); } } }
מידע נוסף על הגדרת MediaSession
כך שלקוחות כמו המערכת יוכלו להתחבר לאפליקציית המדיה מופיע במאמר הענקת שליטה ללקוחות אחרים.
כשמשתמשים ב-Jetpack Media3, כשמטמיעים MediaSession
, ה-PlaybackState
מתעדכן באופן אוטומטי עם נגן המדיה. באופן דומה, כשמטמיעים MediaSessionService
, הספרייה מפרסמת בשבילכם הודעה MediaStyle
באופן אוטומטי ומעדכנת אותה.
תגובה ללחצני פעולה
כשמשתמש מקשיב על לחצן פעולה באמצעי הבקרה של המדיה במערכת, ה-MediaController
של המערכת שולח פקודה להפעלה ל-MediaSession
. לאחר מכן, ה-MediaSession
מעביר את הפקודות האלה לנגן. הפקודות שמוגדרות בממשק Player
של Media3 מטופלות באופן אוטומטי על ידי סשן המדיה.
במאמר הוספת פקודות בהתאמה אישית מוסבר איך להגיב לפקודה בהתאמה אישית.
התנהגות לפני Android 13
כדי לשמור על תאימות לאחור, ממשק המשתמש של המערכת ממשיך לספק פריסה חלופית שמשתמשת בפעולות התראות לאפליקציות שלא מתעדכנות כדי לטרגט את Android 13, או לאפליקציות שלא כוללות מידע על PlaybackState
. לחצני הפעולה מגיעים מהרשימה Notification.Action
שמצורפת להתראה MediaStyle
. המערכת מציגה עד חמש פעולות בסדר שבו הן נוספו. במצב קומפקטי מוצגים עד שלושה לחצנים, בהתאם לערכים שמועברים אל setShowActionsInCompactView()
.
הפעולות בהתאמה אישית ממוקמות לפי הסדר שבו הן נוספו ל-PlaybackState
.
דוגמת הקוד הבאה ממחישה איך מוסיפים פעולות להתראה בסגנון Media :
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();
תמיכה בהמשך הפעלת המדיה
האפשרות להמשיך את הצפייה במדיה מאפשרת למשתמשים להפעיל מחדש סשנים קודמים מהקרוסלה בלי להפעיל את האפליקציה. כשההפעלה מתחילה, המשתמש יוצר אינטראקציה עם פקדי המדיה בדרך הרגילה.
אפשר להפעיל או להשבית את התכונה 'המשך ההפעלה' באפליקציית ההגדרות, בקטע צליל > מדיה. המשתמש יכול לגשת להגדרות גם על ידי הקשה על סמל גלגל השיניים שמופיע אחרי החלקה על הקרוסלה המורחבת.
Media3 מציע ממשקי API שבעזרתם קל יותר לתמוך בהמשך הפעלת מדיה. במסמכים בנושא המשך ההפעלה באמצעות Media3 מוסבר איך להטמיע את התכונה הזו.
שימוש בממשקי ה-API הקודמים של מדיה
בקטע הזה מוסבר איך לשלב את הרכיב עם אמצעי הבקרה של המדיה במערכת באמצעות ממשקי ה-API הקודמים של MediaCompat.
המערכת מאחזרת את הפרטים הבאים מ-MediaMetadata
של MediaSession
ומציגה אותם כשהם זמינים:
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 | Play |
המצב הנוכחי של PlaybackState הוא אחד מהמצבים הבאים:
|
סימן גרפי של טעינת נתונים |
המצב הנוכחי של PlaybackState הוא אחד מהמצבים הבאים:
|
|
השהיה | המצב הנוכחי של PlaybackState הוא לא אף אחת מהאפשרויות האלה. |
|
2 | הקודם | PlaybackState פעולות כוללות את ACTION_SKIP_TO_PREVIOUS . |
בהתאמה אישית | PlaybackState פעולות לא כוללות את ACTION_SKIP_TO_PREVIOUS ו-PlaybackState פעולות בהתאמה אישית כוללות פעולה בהתאמה אישית שעדיין לא הוצבה. |
|
ריק | PlaybackState extras כוללים ערך בוליאני true למפתח SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV . |
|
3 | הבא | PlaybackState פעולות כוללות את ACTION_SKIP_TO_NEXT . |
בהתאמה אישית | PlaybackState פעולות לא כוללות את ACTION_SKIP_TO_NEXT ו-PlaybackState פעולות בהתאמה אישית כוללות פעולה בהתאמה אישית שעדיין לא הוצבה. |
|
ריק | PlaybackState extras כוללים ערך בוליאני true למפתח SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT . |
|
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)
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);
אם לא רוצים להוסיף לחצנים למיקומים 'הקודם' או 'הבא', לא מוסיפים את הערכים ACTION_SKIP_TO_PREVIOUS
או ACTION_SKIP_TO_NEXT
, ובמקום זאת מוסיפים פריטים נוספים לסשן:
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);
הוספת פעולות בהתאמה אישית
כדי להציג פעולות אחרות באמצעי הבקרה של המדיה, אפשר ליצור 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)
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);
תגובה לפעולות של PlaybackState
כשמשתמש מקשיב על לחצן, SystemUI משתמש ב-MediaController.TransportControls
כדי לשלוח פקודה חזרה ל-MediaSession
. צריך לרשום קריאה חוזרת (callback) שיכולה להגיב כראוי לאירועים האלה.
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); } } };
המשך הפעלה של המדיה
כדי שאפליקציית הנגן תופיע באזור ההגדרות המהירות, צריך ליצור התראה מסוג MediaStyle
עם אסימון MediaSession
תקף.
כדי להציג את השם של ההתראה בסגנון MediaStyle, משתמשים ב-NotificationBuilder.setContentTitle()
.
כדי להציג את סמל המותג של נגן המדיה, משתמשים ב-NotificationBuilder.setSmallIcon()
.
כדי לתמוך בהמשך ההפעלה, האפליקציות צריכות להטמיע MediaBrowserService
ו-MediaSession
. ב-MediaSession
צריך להטמיע את פונקציית ה-callback onPlay()
.
הטמעה של MediaBrowserService
אחרי שהמכשיר מופעל, המערכת מחפשת את חמש אפליקציות המדיה שבהן השתמשתם לאחרונה ומספקת פקדים שאפשר להשתמש בהם כדי להפעיל מחדש את ההפעלה מכל אפליקציה.
המערכת מנסה ליצור קשר עם MediaBrowserService
באמצעות חיבור מ-SystemUI. האפליקציה צריכה לאפשר חיבורים כאלה, אחרת לא תהיה לה תמיכה בהמשך ההפעלה.
אפשר לזהות ולאמת חיבורים מ-SystemUI באמצעות שם החבילה com.android.systemui
והחתימה. SystemUI חתום על ידי חתימה של הפלטפורמה. דוגמה לאופן שבו בודקים את החתימה של הפלטפורמה מופיעה באפליקציית UAMP.
כדי לתמוך בהמשך ההפעלה, MediaBrowserService
צריך להטמיע את ההתנהגויות הבאות:
onGetRoot()
חייבת להחזיר שורש שאינו null במהירות. לוגיקה מורכבת אחרת צריך לטפל ב-onLoadChildren()
כשמפעילים את
onLoadChildren()
במזהה המדיה ברמה הבסיסית, התוצאה צריכה להכיל צאצא מסוג FLAG_PLAYABLE.כשמקבלים שאילתה מסוג EXTRA_RECENT, הפונקציה
MediaBrowserService
אמורה להחזיר את פריט המדיה שהושמעו לאחרונה. הערך המוחזר צריך להיות פריט מדיה בפועל ולא פונקציה גנרית.MediaBrowserService
חייב לספק MediaDescription מתאים עם שם וכותרת משנה שאינם ריקים. צריך גם להגדיר URI של סמל או קובץ bitmap של סמל.
בדוגמאות הקוד הבאות מוסבר איך מטמיעים את 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); }