diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaLibrarySessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaLibrarySessionImpl.java index 17730bd72e..069fd35421 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaLibrarySessionImpl.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaLibrarySessionImpl.java @@ -431,7 +431,7 @@ private void postOrRunOnApplicationHandler(Runnable runnable) { ? checkNotNull(getMediaNotificationControllerInfo()) : controller; ListenableFuture future = - callback.onPlaybackResumption(instance, controller); + callback.onPlaybackResumption(instance, controller, false); Futures.addCallback( future, new FutureCallback() { diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java index d8d4f5f40c..e2b641dd49 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java @@ -1744,6 +1744,17 @@ default ListenableFuture onSetMediaItems( new MediaItemsWithStartPosition(mediaItemList, startIndex, startPositionMs))); } + /** + * @deprecated Override {@link MediaSession.Callback#onPlaybackResumption(MediaSession, + * ControllerInfo, boolean)} instead. + */ + @Deprecated + @UnstableApi + default ListenableFuture onPlaybackResumption( + MediaSession mediaSession, ControllerInfo controller) { + return Futures.immediateFailedFuture(new UnsupportedOperationException()); + } + /** * Returns the playlist with which the player should be prepared when a controller requests to * play without a current {@link MediaItem}. @@ -1765,16 +1776,24 @@ default ListenableFuture onSetMediaItems( * Player#COMMAND_GET_CURRENT_MEDIA_ITEM} and either {@link Player#COMMAND_SET_MEDIA_ITEM} or * {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} available. * + *

If {@code isForPlayback} is true, {@link ManuallyHandlePlaybackResumption} may be set on + * the future to manually handle playback resumption. Refer to the {@link + * ManuallyHandlePlaybackResumption} javadoc for more details. + * * @param mediaSession The media session for which playback resumption is requested. * @param controller The {@linkplain ControllerInfo controller} that requests the playback * resumption. This may be a short living controller created only for issuing a play command * for resuming playback. + * @param isForPlayback Whether playback is going to be started as a result of the future being + * completed. If false, SystemUI is querying for media item data in order to build and + * display the resumption notification at boot time. * @return The {@linkplain MediaItemsWithStartPosition playlist} to resume playback with. */ @UnstableApi + @SuppressWarnings("deprecation") // calling deprecated API for backwards compatibility default ListenableFuture onPlaybackResumption( - MediaSession mediaSession, ControllerInfo controller) { - return Futures.immediateFailedFuture(new UnsupportedOperationException()); + MediaSession mediaSession, ControllerInfo controller, boolean isForPlayback) { + return onPlaybackResumption(mediaSession, controller); } /** @@ -1890,6 +1909,25 @@ public int hashCode() { } } + /** + * Exception that may be set to the future in {@link + * MediaSession.Callback#onPlaybackResumption(MediaSession, ControllerInfo, boolean)} if the + * parameter {@code isForPlayback} is true, in order to signal that the app wishes to handle this + * resumption itself. This means the app is responsible for restoring a playlist, setting it to + * the player, and starting playback. + * + *

Do note this has various pitfalls and needs to be used carefully:
+ * - {@link MediaSession.Callback#onPlayerInteractionFinished(MediaSession, ControllerInfo, + * Player.Commands)} will NOT be called for the resumption command.
+ * - If playback is not actually resumed in this method, the resumption notification will end up + * non-functional, but will keep being displayed. This is not a suitable way to disable playback + * resumption, do not attempt to disable it this way.
+ * - If the app takes too long to go into foreground, the grant to go into foreground may have + * expired. + */ + @UnstableApi + public static class ManuallyHandlePlaybackResumption extends Exception {} + /** * A result for {@link Callback#onConnect(MediaSession, ControllerInfo)} to denote the set of * available commands and the media button preferences for a {@link ControllerInfo controller}. diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java index 5147b6478d..106fdcc62e 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java @@ -1122,7 +1122,7 @@ protected MediaSessionServiceLegacyStub getLegacyBrowserService() { @Nullable ListenableFuture future = checkNotNull( - callback.onPlaybackResumption(instance, controllerForRequest), + callback.onPlaybackResumption(instance, controllerForRequest, true), "Callback.onPlaybackResumption must return a non-null future"); Futures.addCallback( future, @@ -1152,6 +1152,9 @@ public void onFailure(Throwable t) { + " media button receiver to your manifest or if you implement the recent" + " media item contract with your MediaLibraryService.", t); + } else if (t instanceof MediaSession.ManuallyHandlePlaybackResumption) { + // See ManuallyHandlePlaybackResumption javadoc for details. + return; } else { Log.e( TAG,