Return full media item when SystemUI calls service on device boot time
#minor-release PiperOrigin-RevId: 533465595
This commit is contained in:
parent
d5f9cf4f19
commit
3fa666c687
@ -24,6 +24,8 @@ import static androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_AUTHENT
|
||||
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
|
||||
import static androidx.media3.session.MediaConstants.ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT;
|
||||
import static androidx.media3.session.MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
@ -43,9 +45,11 @@ import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||
import androidx.media3.session.MediaSession.ControllerCb;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -169,9 +173,14 @@ import java.util.concurrent.Future;
|
||||
int pageSize,
|
||||
@Nullable LibraryParams params) {
|
||||
if (Objects.equals(parentId, RECENT_LIBRARY_ROOT_MEDIA_ID)) {
|
||||
// Advertise support for playback resumption, if enabled.
|
||||
return !canResumePlaybackOnStart()
|
||||
? Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED))
|
||||
if (!canResumePlaybackOnStart()) {
|
||||
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED));
|
||||
}
|
||||
// Advertise support for playback resumption. If STATE_IDLE, the request arrives at boot time
|
||||
// to get the full item data to build a notification. If not STATE_IDLE we don't need to
|
||||
// deliver the full media item, so we do the minimal viable effort.
|
||||
return getPlayerWrapper().getPlaybackState() == Player.STATE_IDLE
|
||||
? getRecentMediaItemAtDeviceBootTime(browser, params)
|
||||
: Futures.immediateFuture(
|
||||
LibraryResult.ofItemList(
|
||||
ImmutableList.of(
|
||||
@ -386,4 +395,38 @@ import java.util.concurrent.Future;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ListenableFuture<LibraryResult<ImmutableList<MediaItem>>>
|
||||
getRecentMediaItemAtDeviceBootTime(
|
||||
ControllerInfo controller, @Nullable LibraryParams params) {
|
||||
SettableFuture<LibraryResult<ImmutableList<MediaItem>>> settableFuture =
|
||||
SettableFuture.create();
|
||||
ListenableFuture<MediaSession.MediaItemsWithStartPosition> future =
|
||||
callback.onPlaybackResumption(instance, controller);
|
||||
Futures.addCallback(
|
||||
future,
|
||||
new FutureCallback<MediaSession.MediaItemsWithStartPosition>() {
|
||||
@Override
|
||||
public void onSuccess(MediaSession.MediaItemsWithStartPosition playlist) {
|
||||
if (playlist.mediaItems.isEmpty()) {
|
||||
settableFuture.set(
|
||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_INVALID_STATE, params));
|
||||
return;
|
||||
}
|
||||
int sanitizedStartIndex =
|
||||
max(0, min(playlist.startIndex, playlist.mediaItems.size() - 1));
|
||||
settableFuture.set(
|
||||
LibraryResult.ofItemList(
|
||||
ImmutableList.of(playlist.mediaItems.get(sanitizedStartIndex)), params));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
settableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_UNKNOWN, params));
|
||||
Log.e(TAG, "Failed fetching recent media item at boot time: " + t.getMessage(), t);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
return settableFuture;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.MediaLibraryService.MediaLibrarySession;
|
||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||
@ -176,13 +177,23 @@ public class MediaLibrarySessionCallbackTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onGetChildren_systemUiCallForRecentItems_returnsRecentItems() throws Exception {
|
||||
public void onGetChildren_systemUiCallForRecentItemsWhenIdle_callsOnPlaybackResumption()
|
||||
throws Exception {
|
||||
ArrayList<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
MediaLibrarySession.Callback callback =
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
latch.countDown();
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 1000L));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildren(
|
||||
MediaLibrarySession session,
|
||||
@ -203,6 +214,166 @@ public class MediaLibrarySessionCallbackTest {
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
"androidx.media3.session.recent.root",
|
||||
/* page= */ 0,
|
||||
/* pageSize= */ 100,
|
||||
/* params= */ null);
|
||||
// Load children of a non recent root that must not be intercepted.
|
||||
LibraryResult<ImmutableList<MediaItem>> children =
|
||||
browser.getChildren("children", /* page= */ 0, /* pageSize= */ 100, /* params= */ null);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(recentItem.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
|
||||
assertThat(Lists.transform(recentItem.value, (item) -> item.mediaId))
|
||||
.containsExactly("mediaItem_2");
|
||||
assertThat(children.value).isEqualTo(mediaItems);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
onGetChildren_systemUiCallForRecentItemsWhenIdleWithEmptyResumptionPlaylist_resultInvalidState()
|
||||
throws Exception {
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MediaLibrarySession.Callback callback =
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
latch.countDown();
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
ImmutableList.of(), /* startIndex= */ 11, /* startPositionMs= */ 1000L));
|
||||
}
|
||||
};
|
||||
MediaLibrarySession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
"androidx.media3.session.recent.root",
|
||||
/* page= */ 0,
|
||||
/* pageSize= */ 100,
|
||||
/* params= */ null);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(recentItem.resultCode).isEqualTo(LibraryResult.RESULT_ERROR_INVALID_STATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
onGetChildren_systemUiCallForRecentItemsWhenIdleStartIndexTooHigh_setToLastItemItemInList()
|
||||
throws Exception {
|
||||
ArrayList<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MediaLibrarySession.Callback callback =
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
latch.countDown();
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
mediaItems, /* startIndex= */ 11, /* startPositionMs= */ 1000L));
|
||||
}
|
||||
};
|
||||
MediaLibrarySession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
"androidx.media3.session.recent.root",
|
||||
/* page= */ 0,
|
||||
/* pageSize= */ 100,
|
||||
/* params= */ null);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(recentItem.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
|
||||
assertThat(Lists.transform(recentItem.value, (item) -> item.mediaId))
|
||||
.containsExactly("mediaItem_3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onGetChildren_systemUiCallForRecentItemsWhenIdleStartIndexNegative_setToZero()
|
||||
throws Exception {
|
||||
ArrayList<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MediaLibrarySession.Callback callback =
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
|
||||
MediaSession mediaSession, ControllerInfo controller) {
|
||||
latch.countDown();
|
||||
return Futures.immediateFuture(
|
||||
new MediaSession.MediaItemsWithStartPosition(
|
||||
mediaItems, /* startIndex= */ -11, /* startPositionMs= */ 1000L));
|
||||
}
|
||||
};
|
||||
MediaLibrarySession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
"androidx.media3.session.recent.root",
|
||||
/* page= */ 0,
|
||||
/* pageSize= */ 100,
|
||||
/* params= */ null);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(recentItem.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
|
||||
assertThat(Lists.transform(recentItem.value, (item) -> item.mediaId))
|
||||
.containsExactly("mediaItem_1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onGetChildren_systemUiCallForRecentItemsWhenNotIdle_returnsRecentItems()
|
||||
throws Exception {
|
||||
ArrayList<MediaItem> mediaItems = MediaTestUtils.createMediaItems(/* size= */ 3);
|
||||
MockMediaLibraryService service = new MockMediaLibraryService();
|
||||
service.attachBaseContext(context);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MediaLibrarySession.Callback callback =
|
||||
new MediaLibrarySession.Callback() {
|
||||
@Override
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> onGetChildren(
|
||||
MediaLibrarySession session,
|
||||
ControllerInfo browser,
|
||||
String parentId,
|
||||
int page,
|
||||
int pageSize,
|
||||
@Nullable LibraryParams params) {
|
||||
latch.countDown();
|
||||
return Futures.immediateFuture(
|
||||
LibraryResult.ofItemList(mediaItems, /* params= */ null));
|
||||
}
|
||||
};
|
||||
player.playbackState = Player.STATE_READY;
|
||||
MediaLibrarySession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaLibrarySession.Builder(service, player, callback)
|
||||
.setId("onGetChildren_systemUiCallForRecentItems_returnsRecentItems")
|
||||
.build());
|
||||
RemoteMediaBrowser browser = controllerTestRule.createRemoteBrowser(session.getToken());
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> recentItem =
|
||||
browser.getChildren(
|
||||
"androidx.media3.session.recent.root",
|
||||
|
Loading…
x
Reference in New Issue
Block a user