Android 11 updates how media controls are displayed, making use of the
MediaSession
and MediaRouter2
APIs to render controls and audio output
information.
Media controls in Android 11 are located near the Quick Settings. Sessions from multiple apps are arranged in a swipeable carousel. The carousel lists sessions in this order:
- Streams playing locally on the phone
- Remote streams, such as those detected on external devices or cast sessions
- Previous resumable sessions, in the order they were last played
Users can restart previous sessions from the carousel without having to start the app. When playback begins, the user interacts with the media controls in the usual way.
Supporting playback resumption
In order to use this feature, you must enable Media resumption in the Developer Options settings.
To make your player app appear in the quick setting settings area,
you must create a MediaStyle
notification with a valid MediaSession
token.
To display the brand icon for the media player, use
NotificationBuilder.setSmallIcon()
.
To support playback resumption, apps must implement a MediaBrowserService
and MediaSession
.
The playback resumption feature can be be turned off using the Settings app, under the Sound > Media options. The user can also access Settings by tapping the gear icon that appears after swiping on the expanded carousel.
MediaBrowserService
implementation
After the device boots, the system looks for the five most recently used media apps, and provides controls that can be used to restart playing from each app.
The system attempts to contact your MediaBrowserService
with a connection from
SystemUI. Your app must allow such connections, otherwise it cannot support
playback resumption.
Connections from SystemUI can be identified and verified using the package name
com.android.systemui
and signature. The SystemUI is signed with the platform
signature. An example of how to check against the platform signature can be
found in the UAMP app.
In order to support playback resumption, your MediaBrowserService
must
implement these behaviors:
onGetRoot()
must return a non-null root quickly. Other complex logic should be handled inonLoadChildren()
When
onLoadChildren()
is called on the root media ID, the result must contain a FLAG_PLAYABLE child.MediaBrowserService
should return the most recently played media item when they receive an EXTRA_RECENT query. The value returned should be an actual media item rather than generic function.MediaBrowserService
must provide an appropriate MediaDescription with a non-empty title and subtitle. It should also set an icon URI or an icon bitmap.
The following code examples illustrate how to implement 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); }
MediaSession
implementation
The system retrieves the following information from the
MediaSession
's MediaMetadata
, and displays it when it is available:
METADATA_KEY_ALBUM_ART_URI
METADATA_KEY_TITLE
METADATA_KEY_ARTIST
METADATA_KEY_DURATION
(If the duration isn’t set the seek bar doesn't show progress)
In order to support play resumption, your MediaSession
must implement a
MediaSession
callback for onPlay()
.
The media player shows the elapsed time for the currently playing
media, along with a seek bar which is mapped to the MediaSession
PlaybackState
.
In order for the the seek bar to work correctly, you must implement
PlaybackState.Builder#setActions
and include ACTION_SEEK_TO
. Otherwise the
player only shows the elapsed time and duration.
To set the player controls, use
Notification.Builder#setActions
.
Only the actions indicated with
Notification.MediaStyle#setShowsActionsInCompactView
will be displayed in the
media player appearing in the collapsed quick settings.