Add Builder.setMaxCommandsForMediaItems for browser and controller

The max number of commands for media items of a browser or controller
can be configured with `setMaxCommandsForMediaItems(int)` on
`MediaController.Builder` and `MediaBrowser.Builder`. An app that has
only limited space for displaying commands can hint this limit to the
session.

A session can receive the value of a connected browser or controller
through `getMaxCommandsForMediaItems()` of a `ControllerInfo` that
is passed into every callback method. The session can then pick the most
sensible commands instead of making the browser app truncating the commands
rather randomly.

When a `MediaBrowser` is connected against a legacy `MediaBrowserServiceCompat`,
the max number of commands is automatically added to the root hints. Conversely,
the value passed in with the root hints to `MediaLibraryService` by a legacy
`MediaBrowserCompat`, is read into `ControllerInfo` like for a Media3 browser.

Issue: androidx/media#1474

#cherrypick

PiperOrigin-RevId: 679076506
This commit is contained in:
bachinger 2024-09-26 03:29:50 -07:00 committed by Copybara-Service
parent 020ce7765c
commit 65962dcb37
27 changed files with 380 additions and 102 deletions

View File

@ -89,8 +89,9 @@
manufacturers when setting the broadcast receiver for media button
intents ([#1730](https://github.com/androidx/media/issues/1730)).
* Add command buttons for media items. This adds the Media3 API for what
was known as `Custom browse actions` in the legacy world. No
interoperability with the legacy API is provided with this change. See
was known as `Custom browse actions` with the legacy library with
`MediaBrowserCompat`. Note that with Media3 command buttons for media
items are available for both, `MediaBrowser` and `MediaController`. See
<a href="https://developer.android.com/training/cars/media#custom_browse_actions">Custom
Browse actions of AAOS</a>.
* UI:

View File

@ -39,13 +39,17 @@ import androidx.media3.common.util.Util;
public final Bundle connectionHints;
public ConnectionRequest(String packageName, int pid, Bundle connectionHints) {
public final int maxCommandsForMediaItems;
public ConnectionRequest(
String packageName, int pid, Bundle connectionHints, int maxCommandsForMediaItems) {
this(
MediaLibraryInfo.VERSION_INT,
MediaControllerStub.VERSION_INT,
packageName,
pid,
new Bundle(connectionHints));
new Bundle(connectionHints),
maxCommandsForMediaItems);
}
private ConnectionRequest(
@ -53,12 +57,14 @@ import androidx.media3.common.util.Util;
int controllerInterfaceVersion,
String packageName,
int pid,
Bundle connectionHints) {
Bundle connectionHints,
int maxCommandsForMediaItems) {
this.libraryVersion = libraryVersion;
this.controllerInterfaceVersion = controllerInterfaceVersion;
this.packageName = packageName;
this.pid = pid;
this.connectionHints = connectionHints;
this.maxCommandsForMediaItems = maxCommandsForMediaItems;
}
private static final String FIELD_LIBRARY_VERSION = Util.intToStringMaxRadix(0);
@ -66,8 +72,9 @@ import androidx.media3.common.util.Util;
private static final String FIELD_PID = Util.intToStringMaxRadix(2);
private static final String FIELD_CONNECTION_HINTS = Util.intToStringMaxRadix(3);
private static final String FIELD_CONTROLLER_INTERFACE_VERSION = Util.intToStringMaxRadix(4);
private static final String FIELD_MAX_COMMANDS_FOR_MEDIA_ITEM = Util.intToStringMaxRadix(5);
// Next id: 5
// Next id: 6
public Bundle toBundle() {
Bundle bundle = new Bundle();
@ -76,6 +83,7 @@ import androidx.media3.common.util.Util;
bundle.putInt(FIELD_PID, pid);
bundle.putBundle(FIELD_CONNECTION_HINTS, connectionHints);
bundle.putInt(FIELD_CONTROLLER_INTERFACE_VERSION, controllerInterfaceVersion);
bundle.putInt(FIELD_MAX_COMMANDS_FOR_MEDIA_ITEM, maxCommandsForMediaItems);
return bundle;
}
@ -88,12 +96,14 @@ import androidx.media3.common.util.Util;
checkArgument(bundle.containsKey(FIELD_PID));
int pid = bundle.getInt(FIELD_PID);
@Nullable Bundle connectionHints = bundle.getBundle(FIELD_CONNECTION_HINTS);
int maxCommandsForMediaItems =
bundle.getInt(FIELD_MAX_COMMANDS_FOR_MEDIA_ITEM, /* defaultValue= */ 0);
return new ConnectionRequest(
libraryVersion,
controllerInterfaceVersion,
packageName,
pid,
connectionHints == null ? Bundle.EMPTY : connectionHints);
connectionHints == null ? Bundle.EMPTY : connectionHints,
maxCommandsForMediaItems);
}
;
}

View File

@ -1728,6 +1728,23 @@ import java.util.concurrent.TimeoutException;
return buttonBundle;
}
/**
* Gets the max number of commands for media items from the {@linkplain
* androidx.media3.session.legacy.MediaBrowserServiceCompat#onGetRoot(String, int, Bundle) root
* hints} of a legacy {@link MediaBrowserCompat} that connects.
*
* @param rootHints The root hints passed by the legacy browser when connecting.
* @return The specified max number of commands per media items, or 0 if not specified.
*/
public static int extractMaxCommandsForMediaItemFromRootHints(Bundle rootHints) {
return max(
0,
rootHints.getInt(
androidx.media3.session.legacy.MediaConstants
.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT,
/* defaultValue= */ 0));
}
private static byte[] convertToByteArray(Bitmap bitmap) throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
bitmap.compress(Bitmap.CompressFormat.PNG, /* ignored */ 0, stream);

View File

@ -60,6 +60,7 @@ public final class MediaBrowser extends MediaController {
private Listener listener;
private Looper applicationLooper;
private @MonotonicNonNull BitmapLoader bitmapLoader;
private int maxCommandsForMediaItems;
/**
* Creates a builder for {@link MediaBrowser}.
@ -139,6 +140,22 @@ public final class MediaBrowser extends MediaController {
return this;
}
/**
* Sets the max number of commands the controller supports per media item.
*
* <p>Must be greater or equal to 0. The default is 0.
*
* @param maxCommandsForMediaItems The max number of commands per media item.
* @return The builder to allow chaining.
*/
@UnstableApi
@CanIgnoreReturnValue
public Builder setMaxCommandsForMediaItems(int maxCommandsForMediaItems) {
checkArgument(maxCommandsForMediaItems >= 0);
this.maxCommandsForMediaItems = maxCommandsForMediaItems;
return this;
}
// LINT.IfChange(build_async)
/**
* Builds a {@link MediaBrowser} asynchronously.
@ -173,7 +190,14 @@ public final class MediaBrowser extends MediaController {
}
MediaBrowser browser =
new MediaBrowser(
context, token, connectionHints, listener, applicationLooper, holder, bitmapLoader);
context,
token,
connectionHints,
listener,
applicationLooper,
holder,
bitmapLoader,
maxCommandsForMediaItems);
postOrRun(new Handler(applicationLooper), () -> holder.setController(browser));
return holder;
}
@ -242,7 +266,8 @@ public final class MediaBrowser extends MediaController {
Listener listener,
Looper applicationLooper,
ConnectionCallback connectionCallback,
@Nullable BitmapLoader bitmapLoader) {
@Nullable BitmapLoader bitmapLoader,
int maxCommandsForMediaItems) {
super(
context,
token,
@ -250,7 +275,8 @@ public final class MediaBrowser extends MediaController {
listener,
applicationLooper,
connectionCallback,
bitmapLoader);
bitmapLoader,
maxCommandsForMediaItems);
}
@Override

View File

@ -110,6 +110,10 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
result.set(LibraryResult.ofItem(createRootMediaItem(browserCompat), null));
} else {
Bundle rootHints = LegacyConversions.convertToRootHints(params);
rootHints.putInt(
androidx.media3.session.legacy.MediaConstants
.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT,
getInstance().getMaxCommandsForMediaItems());
MediaBrowserCompat newBrowser =
new MediaBrowserCompat(
getContext(),

View File

@ -211,6 +211,7 @@ public class MediaController implements Player {
private Listener listener;
private Looper applicationLooper;
private @MonotonicNonNull BitmapLoader bitmapLoader;
private int maxCommandsForMediaItems;
/**
* Creates a builder for {@link MediaController}.
@ -304,6 +305,22 @@ public class MediaController implements Player {
return this;
}
/**
* Sets the max number of commands the controller supports per media item.
*
* <p>Must be greater or equal to 0. The default is 0.
*
* @param maxCommandsForMediaItems The max number of commands per media item.
* @return The builder to allow chaining.
*/
@UnstableApi
@CanIgnoreReturnValue
public Builder setMaxCommandsForMediaItems(int maxCommandsForMediaItems) {
checkArgument(maxCommandsForMediaItems >= 0);
this.maxCommandsForMediaItems = maxCommandsForMediaItems;
return this;
}
/**
* Builds a {@link MediaController} asynchronously.
*
@ -338,7 +355,14 @@ public class MediaController implements Player {
}
MediaController controller =
new MediaController(
context, token, connectionHints, listener, applicationLooper, holder, bitmapLoader);
context,
token,
connectionHints,
listener,
applicationLooper,
holder,
bitmapLoader,
maxCommandsForMediaItems);
postOrRun(new Handler(applicationLooper), () -> holder.setController(controller));
return holder;
}
@ -493,6 +517,8 @@ public class MediaController implements Player {
private boolean connectionNotified;
private final int maxCommandsForMediaItems;
/* package */ final ConnectionCallback connectionCallback;
/** Creates a {@link MediaController} from the {@link SessionToken}. */
@ -505,7 +531,8 @@ public class MediaController implements Player {
Listener listener,
Looper applicationLooper,
ConnectionCallback connectionCallback,
@Nullable BitmapLoader bitmapLoader) {
@Nullable BitmapLoader bitmapLoader,
int maxCommandsForMediaItems) {
checkNotNull(context, "context must not be null");
checkNotNull(token, "token must not be null");
Log.i(
@ -526,6 +553,7 @@ public class MediaController implements Player {
this.listener = listener;
applicationHandler = new Handler(applicationLooper);
this.connectionCallback = connectionCallback;
this.maxCommandsForMediaItems = maxCommandsForMediaItems;
impl = createImpl(context, token, connectionHints, applicationLooper, bitmapLoader);
impl.connect();
@ -1947,6 +1975,10 @@ public class MediaController implements Player {
return applicationHandler.getLooper();
}
/* package */ int getMaxCommandsForMediaItems() {
return maxCommandsForMediaItems;
}
/**
* Gets the optional time diff (in milliseconds) used for calculating the current position, or
* {@link C#TIME_UNSET} if no diff should be applied.

View File

@ -2534,7 +2534,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
IMediaSession.Stub.asInterface((IBinder) checkStateNotNull(token.getBinder()));
int seq = sequencedFutureManager.obtainNextSequenceNumber();
ConnectionRequest request =
new ConnectionRequest(context.getPackageName(), Process.myPid(), connectionHints);
new ConnectionRequest(
context.getPackageName(),
Process.myPid(),
connectionHints,
instance.getMaxCommandsForMediaItems());
try {
iSession.connect(controllerStub, seq, request.toBundle());
} catch (RemoteException e) {
@ -3285,7 +3289,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
return;
}
ConnectionRequest request =
new ConnectionRequest(getContext().getPackageName(), Process.myPid(), connectionHints);
new ConnectionRequest(
getContext().getPackageName(),
Process.myPid(),
connectionHints,
instance.getMaxCommandsForMediaItems());
iService.connect(controllerStub, request.toBundle());
connectionRequested = true;
} catch (RemoteException e) {

View File

@ -19,6 +19,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.castNonNull;
import static androidx.media3.common.util.Util.postOrRun;
import static androidx.media3.session.LegacyConversions.extractMaxCommandsForMediaItemFromRootHints;
import static androidx.media3.session.LibraryResult.RESULT_SUCCESS;
import static androidx.media3.session.MediaUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES;
import static androidx.media3.session.legacy.MediaBrowserCompat.EXTRA_PAGE;
@ -372,7 +373,8 @@ import java.util.concurrent.atomic.AtomicReference;
ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
getMediaSessionManager().isTrustedForMediaControl(remoteUserInfo),
new BrowserLegacyCb(remoteUserInfo),
/* connectionHints= */ rootHints);
/* connectionHints= */ rootHints,
extractMaxCommandsForMediaItemFromRootHints(rootHints));
}
public ControllerCb getBrowserLegacyCbForBroadcast() {

View File

@ -487,6 +487,7 @@ public class MediaSession {
private final boolean isTrusted;
@Nullable private final ControllerCb controllerCb;
private final Bundle connectionHints;
private final int maxCommandsForMediaItems;
/**
* Creates an instance.
@ -499,6 +500,7 @@ public class MediaSession {
* ControllerInfo)}.
* @param connectionHints A session-specific argument sent from the controller for the
* connection. The contents of this bundle may affect the connection result.
* @param maxCommandsForMediaItems The max commands the controller supports for media items.
*/
/* package */ ControllerInfo(
RemoteUserInfo remoteUserInfo,
@ -506,13 +508,15 @@ public class MediaSession {
int interfaceVersion,
boolean trusted,
@Nullable ControllerCb cb,
Bundle connectionHints) {
Bundle connectionHints,
int maxCommandsForMediaItems) {
this.remoteUserInfo = remoteUserInfo;
this.libraryVersion = libraryVersion;
this.interfaceVersion = interfaceVersion;
isTrusted = trusted;
controllerCb = cb;
this.connectionHints = connectionHints;
this.maxCommandsForMediaItems = maxCommandsForMediaItems;
}
/* package */ RemoteUserInfo getRemoteUserInfo() {
@ -555,6 +559,15 @@ public class MediaSession {
return new Bundle(connectionHints);
}
/**
* Returns the max number of commands for a media item. A positive number or 0 (zero) to
* indicate that the feature is not supported by the controller.
*/
@UnstableApi
public int getMaxCommandsForMediaItems() {
return maxCommandsForMediaItems;
}
/**
* Returns if the controller has been granted {@code android.permission.MEDIA_CONTENT_CONTROL}
* or has an enabled notification listener so it can be trusted to accept connection and
@ -611,7 +624,8 @@ public class MediaSession {
ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
/* trusted= */ false,
/* cb= */ null,
/* connectionHints= */ Bundle.EMPTY);
/* connectionHints= */ Bundle.EMPTY,
/* maxCommandsForMediaItems= */ 0);
}
/**
@ -653,7 +667,8 @@ public class MediaSession {
interfaceVersion,
trusted,
/* cb= */ null,
connectionHints);
connectionHints,
/* maxCommandsForMediaItems= */ 0);
}
}

View File

@ -320,7 +320,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
/* trusted= */ false,
/* cb= */ null,
/* connectionHints= */ Bundle.EMPTY),
/* connectionHints= */ Bundle.EMPTY,
/* maxCommandsForMediaItems= */ 0),
intent);
}
@ -782,7 +783,8 @@ import org.checkerframework.checker.initialization.qual.Initialized;
ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
sessionManager.isTrustedForMediaControl(remoteUserInfo),
controllerCb,
/* connectionHints= */ Bundle.EMPTY);
/* connectionHints= */ Bundle.EMPTY,
/* maxCommandsForMediaItems= */ 0);
MediaSession.ConnectionResult connectionResult = sessionImpl.onConnectOnHandler(controller);
if (!connectionResult.isAccepted) {
try {

View File

@ -466,7 +466,8 @@ public abstract class MediaSessionService extends Service {
MediaControllerStub.VERSION_INT,
/* trusted= */ false,
/* cb= */ null,
/* connectionHints= */ Bundle.EMPTY);
/* connectionHints= */ Bundle.EMPTY,
/* maxCommandsForMediaItems= */ 0);
}
/**
@ -761,7 +762,8 @@ public abstract class MediaSessionService extends Service {
request.controllerInterfaceVersion,
isTrusted,
new MediaSessionStub.Controller2Cb(caller),
request.connectionHints);
request.connectionHints,
request.maxCommandsForMediaItems);
@Nullable MediaSession session;
try {

View File

@ -16,6 +16,7 @@
package androidx.media3.session;
import static androidx.media3.common.util.Util.postOrRun;
import static androidx.media3.session.LegacyConversions.extractMaxCommandsForMediaItemFromRootHints;
import android.os.Bundle;
import androidx.annotation.Nullable;
@ -100,7 +101,8 @@ import java.util.concurrent.atomic.AtomicReference;
ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
manager.isTrustedForMediaControl(info),
/* cb= */ null,
/* connectionHints= */ rootHints);
/* connectionHints= */ rootHints,
extractMaxCommandsForMediaItemFromRootHints(rootHints));
}
public final MediaSessionManager getMediaSessionManager() {

View File

@ -637,7 +637,8 @@ import java.util.concurrent.ExecutionException;
request.controllerInterfaceVersion,
sessionManager.isTrustedForMediaControl(remoteUserInfo),
new MediaSessionStub.Controller2Cb(caller),
request.connectionHints);
request.connectionHints,
request.maxCommandsForMediaItems);
connect(caller, controllerInfo);
} finally {
Binder.restoreCallingIdentity(token);

View File

@ -108,7 +108,10 @@ public class MediaSessionStubTest {
/* caller= */ null,
/* seq= */ 0,
/* connectionRequest= */ new ConnectionRequest(
"pkg", /* pid= */ 0, /* connectionHints= */ new Bundle())
"pkg",
/* pid= */ 0,
/* connectionHints= */ new Bundle(),
/* maxCommandsForMediaItems= */ 0)
.toBundle());
binder.onCustomCommand(
/* caller= */ null,
@ -497,7 +500,10 @@ public class MediaSessionStubTest {
caller,
/* seq= */ 0,
/* connectionRequest= */ new ConnectionRequest(
/* packageName= */ "invalid", /* pid= */ 9999, /* connectionHints= */ new Bundle())
/* packageName= */ "invalid",
/* pid= */ 9999,
/* connectionHints= */ new Bundle(),
/* maxCommandsForMediaItems= */ 0)
.toBundle());
}
}

View File

@ -85,7 +85,8 @@ public class MediaSessionUnitTest { // Avoid naming collision with session_curre
MediaControllerStub.VERSION_INT,
/* trusted= */ false,
/* cb= */ null,
/* connectionHints= */ Bundle.EMPTY);
/* connectionHints= */ Bundle.EMPTY,
/* maxCommandsForMediaItems= */ 0);
assertThat(session.isAutomotiveController(controllerInfo)).isFalse();
}
@ -140,7 +141,8 @@ public class MediaSessionUnitTest { // Avoid naming collision with session_curre
MediaControllerStub.VERSION_INT,
/* trusted= */ false,
/* cb= */ null,
/* connectionHints= */ Bundle.EMPTY);
/* connectionHints= */ Bundle.EMPTY,
/* maxCommandsForMediaItems= */ 0);
assertThat(session.isAutoCompanionController(controllerInfo)).isFalse();
}
@ -161,7 +163,8 @@ public class MediaSessionUnitTest { // Avoid naming collision with session_curre
MediaControllerStub.VERSION_INT,
/* trusted= */ false,
/* cb= */ null,
connectionHints);
connectionHints,
/* maxCommandsForMediaItems= */ 0);
assertThat(session.isMediaNotificationController(controllerInfo)).isTrue();
}
@ -182,7 +185,8 @@ public class MediaSessionUnitTest { // Avoid naming collision with session_curre
MediaControllerStub.VERSION_INT,
/* trusted= */ false,
/* cb= */ null,
connectionHints);
connectionHints,
/* maxCommandsForMediaItems= */ 0);
assertThat(session.isMediaNotificationController(controllerInfo)).isFalse();
}
@ -201,7 +205,8 @@ public class MediaSessionUnitTest { // Avoid naming collision with session_curre
MediaControllerStub.VERSION_INT,
/* trusted= */ false,
/* cb= */ null,
/* connectionHints= */ Bundle.EMPTY);
/* connectionHints= */ Bundle.EMPTY,
/* maxCommandsForMediaItems= */ 0);
assertThat(session.isMediaNotificationController(controllerInfo)).isFalse();
}
@ -222,7 +227,8 @@ public class MediaSessionUnitTest { // Avoid naming collision with session_curre
MediaSession.ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
/* trusted= */ false,
/* cb= */ null,
connectionHints);
connectionHints,
/* maxCommandsForMediaItems= */ 0);
assertThat(session.isMediaNotificationController(controllerInfo)).isFalse();
}
@ -235,6 +241,7 @@ public class MediaSessionUnitTest { // Avoid naming collision with session_curre
MediaSession.ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION,
/* trusted= */ false,
/* cb= */ null,
/* connectionHints= */ Bundle.EMPTY);
/* connectionHints= */ Bundle.EMPTY,
/* maxCommandsForMediaItems= */ 0);
}
}

View File

@ -22,6 +22,7 @@ import static androidx.media3.session.MediaLibraryService.MediaLibrarySession.LI
import static androidx.media3.session.MediaLibraryService.MediaLibrarySession.LIBRARY_ERROR_REPLICATION_MODE_NON_FATAL;
import static androidx.media3.session.MockMediaLibraryService.CONNECTION_HINTS_CUSTOM_LIBRARY_ROOT;
import static androidx.media3.session.MockMediaLibraryService.createNotifyChildrenChangedBundle;
import static androidx.media3.session.legacy.MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT;
import static androidx.media3.session.legacy.MediaConstants.DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST;
import static androidx.media3.test.session.common.CommonConstants.METADATA_ALBUM_TITLE;
import static androidx.media3.test.session.common.CommonConstants.METADATA_ARTIST;
@ -237,7 +238,9 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
@Test
public void getItem_playableWithBrowseActions_browseActionCorrectlyConverted() throws Exception {
String mediaId = MEDIA_ID_GET_ITEM_WITH_BROWSE_ACTIONS;
connectAndWait(/* rootHints= */ Bundle.EMPTY);
Bundle rootHints = new Bundle();
rootHints.putInt(BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 10);
connectAndWait(rootHints);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<MediaItem> itemRef = new AtomicReference<>();
@ -263,6 +266,36 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest
.inOrder();
}
@Test
public void getItem_maxCommandsForMediaItemSetToBelowMaxAvailableCommands_maxCommandsHonoured()
throws Exception {
String mediaId = MEDIA_ID_GET_ITEM_WITH_BROWSE_ACTIONS;
Bundle rootHints = new Bundle();
rootHints.putInt(BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 1);
connectAndWait(rootHints);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<MediaItem> itemRef = new AtomicReference<>();
browserCompat.getItem(
mediaId,
new ItemCallback() {
@Override
public void onItemLoaded(MediaItem item) {
itemRef.set(item);
latch.countDown();
}
});
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(
itemRef
.get()
.getDescription()
.getExtras()
.getStringArrayList(DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST))
.containsExactly(MediaBrowserConstants.COMMAND_PLAYLIST_ADD);
}
@Test
public void getItem_metadata() throws Exception {
String mediaId = MEDIA_ID_GET_ITEM_WITH_METADATA;

View File

@ -90,9 +90,23 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
private RemoteMediaBrowserServiceCompat remoteService;
private MediaBrowser createBrowser(@Nullable MediaBrowser.Listener listener) throws Exception {
return createBrowser(
/* connectionHints= */ Bundle.EMPTY, /* maxCommandsForMediaItems= */ 0, listener);
}
private MediaBrowser createBrowser(
Bundle connectionHints,
int maxCommandsForMediaItems,
@Nullable MediaBrowser.Listener listener)
throws Exception {
SessionToken token = new SessionToken(context, MOCK_MEDIA_BROWSER_SERVICE_COMPAT);
return (MediaBrowser)
controllerTestRule.createController(token, /* connectionHints= */ null, listener);
controllerTestRule.createController(
token,
connectionHints,
listener,
/* controllerCreationListener= */ null,
maxCommandsForMediaItems);
}
@Before
@ -151,7 +165,8 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
"invalid"))
.build())
.build();
MediaBrowser mediaBrowser = createBrowser(/* listener= */ null);
MediaBrowser mediaBrowser =
createBrowser(Bundle.EMPTY, /* maxCommandsForMediaItems= */ 2, /* listener= */ null);
// When connected to a legacy browser service, the library root needs to be requested
// before media item commands are available.
LibraryResult<MediaItem> libraryResult =
@ -172,7 +187,8 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
@Test
public void getItem_supportedCommandActions_convertedCorrectly() throws Exception {
remoteService.setProxyForTest(TEST_MEDIA_ITEMS_WITH_BROWSE_ACTIONS);
MediaBrowser mediaBrowser = createBrowser(/* listener= */ null);
MediaBrowser mediaBrowser =
createBrowser(Bundle.EMPTY, /* maxCommandsForMediaItems= */ 1, /* listener= */ null);
CommandButton playlistAddButton =
new CommandButton.Builder()
.setDisplayName("Add to playlist")
@ -180,13 +196,6 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
.setSessionCommand(
new SessionCommand(MediaBrowserConstants.COMMAND_PLAYLIST_ADD, Bundle.EMPTY))
.build();
CommandButton radioButton =
new CommandButton.Builder()
.setDisplayName("Radio station")
.setIconUri(Uri.parse("https://www.example.com/icon/radio"))
.setSessionCommand(
new SessionCommand(MediaBrowserConstants.COMMAND_RADIO, Bundle.EMPTY))
.build();
// When connected to a legacy browser service, the library root needs to be requested
// before media item commands are available.
LibraryResult<MediaItem> libraryResult =
@ -204,9 +213,8 @@ public class MediaBrowserListenerWithMediaBrowserServiceCompatTest {
.postAndSync(
() -> mediaBrowser.getCommandButtonsForMediaItem(requireNonNull(mediaItem)));
assertThat(commandButtons).containsExactly(playlistAddButton, radioButton).inOrder();
assertThat(commandButtons).containsExactly(playlistAddButton);
assertThat(commandButtons.get(0).extras.getString("key-1")).isEqualTo("playlist_add");
assertThat(commandButtons.get(1).extras.getString("key-1")).isEqualTo("radio");
}
@Test

View File

@ -97,7 +97,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<MediaItem> result = browser.getLibraryRoot(testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -136,7 +136,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
RemoteMediaBrowser browser =
new RemoteMediaBrowser(
context, token, /* waitForConnection= */ true, /* connectionHints= */ null);
context, token, /* waitForConnection= */ true, /* connectionHints= */ Bundle.EMPTY);
LibraryResult<MediaItem> result = browser.getItem(testMediaId);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -164,7 +164,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
RemoteMediaBrowser browser =
new RemoteMediaBrowser(
context, token, /* waitForConnection= */ true, /* connectionHints= */ null);
context, token, /* waitForConnection= */ true, /* connectionHints= */ Bundle.EMPTY);
LibraryResult<MediaItem> result = browser.getItem(testMediaId);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -187,7 +187,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<MediaItem> result = browser.getItem(testMediaId);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
@ -215,7 +215,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<ImmutableList<MediaItem>> result =
browser.getChildren(testParentId, testPage, testPageSize, null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -249,7 +249,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<ImmutableList<MediaItem>> result =
browser.getChildren(testParentId, testPage, testPageSize, null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -277,7 +277,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<ImmutableList<MediaItem>> result =
browser.getChildren(testParentId, testPage, testPageSize, null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -307,7 +307,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
latch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<ImmutableList<MediaItem>> result =
browser.getChildren(testParentId, testPage, testPageSize, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -332,7 +332,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
subscribeLatch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<Void> result = browser.subscribe(testParentId, testParams);
assertThat(subscribeLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
@ -354,7 +354,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
subscribeLatch.countDown();
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<Void> result = browser.subscribe(testParentId, null);
assertThat(subscribeLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isNotEqualTo(LibraryResult.RESULT_SUCCESS);
@ -382,7 +382,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<Void> result = browser.search(testQuery, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
@ -403,7 +403,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<Void> result = browser.search(testQuery, null);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(result.resultCode).isEqualTo(LibraryResult.RESULT_SUCCESS);
@ -431,7 +431,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<ImmutableList<MediaItem>> result =
browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -456,7 +456,7 @@ public class MediaBrowserServiceCompatCallbackWithMediaBrowserTest {
}
});
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, null);
RemoteMediaBrowser browser = new RemoteMediaBrowser(context, token, true, Bundle.EMPTY);
LibraryResult<ImmutableList<MediaItem>> result =
browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();

View File

@ -763,7 +763,8 @@ public class MediaControllerTest {
latch.countDown();
}
},
/* controllerCreationListener= */ MediaController::release);
/* controllerCreationListener= */ MediaController::release,
/* maxCommandsForMediaItems= */ 0);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(controller.isConnected()).isFalse();
}

View File

@ -105,7 +105,7 @@ public final class MediaControllerTestRule extends ExternalResource {
public MediaController createController(
MediaSessionCompat.Token token, @Nullable MediaController.Listener listener)
throws Exception {
return createController(token, listener, /* controllerCreateListener= */ null);
return createController(token, listener, /* controllerCreationListener= */ null);
}
/** Creates {@link MediaController} from {@link MediaSessionCompat.Token}. */
@ -121,17 +121,6 @@ public final class MediaControllerTestRule extends ExternalResource {
return controller;
}
private MediaController createControllerOnHandler(
MediaSessionCompat.Token token,
TestMediaBrowserListener listener,
@Nullable MediaControllerCreationListener controllerCreationListener)
throws Exception {
SessionToken sessionToken =
SessionToken.createSessionToken(context, token).get(TIMEOUT_MS, MILLISECONDS);
return createControllerOnHandler(
sessionToken, /* connectionHints= */ null, listener, controllerCreationListener);
}
/** Creates {@link MediaController} from {@link SessionToken} with default options. */
public MediaController createController(SessionToken token) throws Exception {
return createController(token, /* connectionHints= */ null, /* listener= */ null);
@ -144,7 +133,11 @@ public final class MediaControllerTestRule extends ExternalResource {
@Nullable MediaController.Listener listener)
throws Exception {
return createController(
token, connectionHints, listener, /* controllerCreationListener= */ null);
token,
connectionHints,
listener,
/* controllerCreationListener= */ null,
/* maxCommandsForMediaItems= */ 0);
}
/** Creates {@link MediaController} from {@link SessionToken}. */
@ -152,20 +145,42 @@ public final class MediaControllerTestRule extends ExternalResource {
SessionToken token,
@Nullable Bundle connectionHints,
@Nullable MediaController.Listener listener,
@Nullable MediaControllerCreationListener controllerCreationListener)
@Nullable MediaControllerCreationListener controllerCreationListener,
int maxCommandsForMediaItems)
throws Exception {
TestMediaBrowserListener testListener = new TestMediaBrowserListener(listener);
MediaController controller =
createControllerOnHandler(token, connectionHints, testListener, controllerCreationListener);
createControllerOnHandler(
token,
connectionHints,
testListener,
controllerCreationListener,
maxCommandsForMediaItems);
controllers.put(controller, testListener);
return controller;
}
private MediaController createControllerOnHandler(
MediaSessionCompat.Token token,
TestMediaBrowserListener listener,
@Nullable MediaControllerCreationListener controllerCreationListener)
throws Exception {
SessionToken sessionToken =
SessionToken.createSessionToken(context, token).get(TIMEOUT_MS, MILLISECONDS);
return createControllerOnHandler(
sessionToken,
/* connectionHints= */ null,
listener,
controllerCreationListener,
/* maxCommandsForMediaItems= */ 0);
}
private MediaController createControllerOnHandler(
SessionToken token,
@Nullable Bundle connectionHints,
TestMediaBrowserListener listener,
@Nullable MediaControllerCreationListener controllerCreationListener)
@Nullable MediaControllerCreationListener controllerCreationListener,
int maxCommandsForMediaItems)
throws Exception {
// Create controller on the test handler, for changing MediaBrowserCompat's Handler
// Looper. Otherwise, MediaBrowserCompat will post all the commands to the handler
@ -181,6 +196,7 @@ public final class MediaControllerTestRule extends ExternalResource {
if (connectionHints != null) {
builder.setConnectionHints(connectionHints);
}
builder.setMaxCommandsForMediaItems(maxCommandsForMediaItems);
return builder.buildAsync();
} else {
MediaController.Builder builder =
@ -188,6 +204,7 @@ public final class MediaControllerTestRule extends ExternalResource {
if (connectionHints != null) {
builder.setConnectionHints(connectionHints);
}
builder.setMaxCommandsForMediaItems(maxCommandsForMediaItems);
return builder.buildAsync();
}
});

View File

@ -44,6 +44,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.ClassRule;
@ -83,6 +84,38 @@ public class MediaLibrarySessionCallbackTest {
.build();
}
@Test
public void onConnect_withMaxCommandsForMediaItems_correctMaxLimitInControllerInfo()
throws Exception {
CountDownLatch latch = new CountDownLatch(/* count= */ 1);
AtomicInteger maxCommandsForMediaItems = new AtomicInteger();
MediaLibrarySession.Callback sessionCallback =
new MediaLibrarySession.Callback() {
@Override
public MediaSession.ConnectionResult onConnect(
MediaSession session, ControllerInfo browser) {
maxCommandsForMediaItems.set(browser.getMaxCommandsForMediaItems());
latch.countDown();
return MediaLibrarySession.Callback.super.onConnect(session, browser);
}
};
MockMediaLibraryService service = new MockMediaLibraryService();
service.attachBaseContext(context);
MediaLibrarySession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaLibrarySession.Builder(service, player, sessionCallback)
.setId("onConnect_withMaxCommandForMediaItems_correctMaxLimitInControllerInfo")
.build());
Bundle connectionHints = new Bundle();
connectionHints.putInt(
MediaControllerProviderService.CONNECTION_HINT_KEY_MAX_COMMANDS_FOR_MEDIA_ITEMS, 14);
controllerTestRule.createRemoteBrowser(session.getToken(), connectionHints);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(maxCommandsForMediaItems.get()).isEqualTo(14);
}
@Test
public void onSubscribeUnsubscribe() throws Exception {
String testParentId = "testSubscribeUnsubscribeId";

View File

@ -353,6 +353,37 @@ public class MediaSessionCallbackTest {
assertThat(remoteController.getSessionExtras().size()).isEqualTo(0);
}
@Test
public void onConnect_withMaxCommandsForMediaItems_correctMaxLimitInControllerInfo()
throws Exception {
CountDownLatch latch = new CountDownLatch(/* count= */ 1);
AtomicInteger maxCommandsForMediaItems = new AtomicInteger();
MediaSession session =
sessionTestRule.ensureReleaseAfterTest(
new MediaSession.Builder(context, player)
.setCallback(
new MediaSession.Callback() {
@Override
public MediaSession.ConnectionResult onConnect(
MediaSession session, ControllerInfo controller) {
maxCommandsForMediaItems.set(controller.getMaxCommandsForMediaItems());
latch.countDown();
return MediaSession.Callback.super.onConnect(session, controller);
}
})
.setId("onConnect_withMaxCommandForMediaItems_correctMaxLimitInControllerInfo")
.build());
Bundle connectionHints = new Bundle();
connectionHints.putInt(
MediaControllerProviderService.CONNECTION_HINT_KEY_MAX_COMMANDS_FOR_MEDIA_ITEMS, 2);
remoteControllerTestRule.createRemoteController(
session.getToken(), /* waitForConnection= */ true, connectionHints);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(maxCommandsForMediaItems.get()).isEqualTo(2);
}
@Test
public void onPostConnect_afterConnected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);

View File

@ -145,7 +145,7 @@ public class MediaSessionServiceTest {
// Create the remote controller to start the service.
RemoteMediaController controller =
controllerTestRule.createRemoteController(
token, /* waitForConnection= */ true, /* connectionHints= */ null);
token, /* waitForConnection= */ true, /* connectionHints= */ Bundle.EMPTY);
// Get the started service instance after creation.
MockMediaSessionService service =
(MockMediaSessionService) testServiceRegistry.getServiceInstance();
@ -221,7 +221,7 @@ public class MediaSessionServiceTest {
});
RemoteMediaController controller =
controllerTestRule.createRemoteController(
token, /* waitForConnection= */ true, /* connectionHints= */ null);
token, /* waitForConnection= */ true, /* connectionHints= */ Bundle.EMPTY);
MockMediaSessionService service =
(MockMediaSessionService) testServiceRegistry.getServiceInstance();
@ -459,9 +459,9 @@ public class MediaSessionServiceTest {
});
RemoteMediaController controller1 =
controllerTestRule.createRemoteController(token, true, null);
controllerTestRule.createRemoteController(token, true, /* connectionHints= */ Bundle.EMPTY);
RemoteMediaController controller2 =
controllerTestRule.createRemoteController(token, true, null);
controllerTestRule.createRemoteController(token, true, /* connectionHints= */ Bundle.EMPTY);
assertThat(controller2.getConnectedSessionToken())
.isNotEqualTo(controller1.getConnectedSessionToken());
@ -504,9 +504,9 @@ public class MediaSessionServiceTest {
});
RemoteMediaController controller1 =
controllerTestRule.createRemoteController(token, true, null);
controllerTestRule.createRemoteController(token, true, /* connectionHints= */ Bundle.EMPTY);
RemoteMediaController controller2 =
controllerTestRule.createRemoteController(token, true, null);
controllerTestRule.createRemoteController(token, true, /* connectionHints= */ Bundle.EMPTY);
controller1.release();
controller2.release();
@ -540,9 +540,9 @@ public class MediaSessionServiceTest {
});
RemoteMediaController controller1 =
controllerTestRule.createRemoteController(token, true, null);
controllerTestRule.createRemoteController(token, true, /* connectionHints= */ Bundle.EMPTY);
RemoteMediaController controller2 =
controllerTestRule.createRemoteController(token, true, null);
controllerTestRule.createRemoteController(token, true, /* connectionHints= */ Bundle.EMPTY);
controller1.release();
assertThat(latch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse();
@ -555,7 +555,7 @@ public class MediaSessionServiceTest {
@Test
public void getSessions() throws Exception {
controllerTestRule.createRemoteController(
token, /* waitForConnection= */ true, /* connectionHints= */ null);
token, /* waitForConnection= */ true, /* connectionHints= */ Bundle.EMPTY);
MediaSessionService service = TestServiceRegistry.getInstance().getServiceInstance();
MediaSession session = createMediaSession("testGetSessions");
service.addSession(session);
@ -572,7 +572,7 @@ public class MediaSessionServiceTest {
@Test
public void addSessions_removedWhenReleased() throws Exception {
controllerTestRule.createRemoteController(
token, /* waitForConnection= */ true, /* connectionHints= */ null);
token, /* waitForConnection= */ true, /* connectionHints= */ Bundle.EMPTY);
MediaSessionService service = TestServiceRegistry.getInstance().getServiceInstance();
MediaSession session = createMediaSession("testAddSessions_removedWhenReleased");
service.addSession(session);

View File

@ -76,7 +76,7 @@ public final class RemoteControllerTestRule extends ExternalResource {
*/
public RemoteMediaController createRemoteController(SessionToken token) throws RemoteException {
return createRemoteController(
token, /* waitForConnection= */ true, /* connectionHints= */ null);
token, /* waitForConnection= */ true, /* connectionHints= */ Bundle.EMPTY);
}
/** Creates {@link RemoteMediaController} from {@link SessionToken}. */

View File

@ -59,6 +59,10 @@ import java.util.concurrent.TimeoutException;
* app's requests.
*/
public class MediaControllerProviderService extends Service {
public static final String CONNECTION_HINT_KEY_MAX_COMMANDS_FOR_MEDIA_ITEMS =
"CONNECTION_HINT_KEY_MAX_COMMANDS_FOR_MEDIA_ITEMS";
private static final String TAG = "MCProviderService";
Map<String, MediaController> mediaControllerMap = new HashMap<>();
@ -123,6 +127,13 @@ public class MediaControllerProviderService extends Service {
boolean waitForConnection)
throws RemoteException {
SessionToken token = SessionToken.fromBundle(tokenBundle);
// Allow a test to define with what max number of commands per item to connect.
int maxCommandsForMediaItems =
connectionHints.containsKey(CONNECTION_HINT_KEY_MAX_COMMANDS_FOR_MEDIA_ITEMS)
? connectionHints.getInt(
CONNECTION_HINT_KEY_MAX_COMMANDS_FOR_MEDIA_ITEMS, /* defaultValue= */ -1)
: 0;
connectionHints.remove(CONNECTION_HINT_KEY_MAX_COMMANDS_FOR_MEDIA_ITEMS);
ListenableFuture<? extends MediaController> controllerFuture =
runOnHandler(
() -> {
@ -132,12 +143,18 @@ public class MediaControllerProviderService extends Service {
if (connectionHints != null) {
builder.setConnectionHints(connectionHints);
}
if (maxCommandsForMediaItems >= 0) {
builder.setMaxCommandsForMediaItems(maxCommandsForMediaItems);
}
return builder.buildAsync();
} else {
MediaController.Builder builder = new MediaController.Builder(context, token);
if (connectionHints != null) {
builder.setConnectionHints(connectionHints);
}
if (maxCommandsForMediaItems >= 0) {
builder.setMaxCommandsForMediaItems(maxCommandsForMediaItems);
}
return builder.buildAsync();
}
});

View File

@ -393,8 +393,7 @@ public class MockMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
String clientPackageName, int clientUid, Bundle rootHints) {
int actionLimit =
rootHints.getInt(
BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT,
/* defaultValue= */ browseActions.size());
BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, /* defaultValue= */ 0);
Bundle extras = new Bundle(rootHints);
ArrayList<Bundle> browseActionList = new ArrayList<>();
for (int i = 0; i < min(actionLimit, browseActions.size()); i++) {

View File

@ -62,6 +62,7 @@ import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_R
import static androidx.media3.test.session.common.MediaBrowserConstants.SEARCH_TIME_IN_MS;
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_1;
import static androidx.media3.test.session.common.MediaBrowserConstants.SUBSCRIBE_PARENT_ID_2;
import static java.lang.Math.min;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.PendingIntent;
@ -360,7 +361,9 @@ public class MockMediaLibraryService extends MediaLibraryService {
case MEDIA_ID_GET_ITEM_WITH_BROWSE_ACTIONS:
return Futures.immediateFuture(
LibraryResult.ofItem(
createPlayableMediaItemWithBrowseActions(mediaId), /* params= */ null));
createPlayableMediaItemWithCommands(
mediaId, browser.getMaxCommandsForMediaItems()),
/* params= */ null));
case MEDIA_ID_GET_ITEM_WITH_METADATA:
return Futures.immediateFuture(
LibraryResult.ofItem(createMediaItemWithMetadata(mediaId), /* params= */ null));
@ -575,7 +578,7 @@ public class MockMediaLibraryService extends MediaLibraryService {
int totalItemCount = items.size();
int fromIndex = page * pageSize;
int toIndex = Math.min((page + 1) * pageSize, totalItemCount);
int toIndex = min((page + 1) * pageSize, totalItemCount);
List<String> paginatedMediaIdList = new ArrayList<>();
try {
@ -626,17 +629,18 @@ public class MockMediaLibraryService extends MediaLibraryService {
return mediaItem.buildUpon().setMediaMetadata(mediaMetadataWithArtwork).build();
}
private MediaItem createPlayableMediaItemWithBrowseActions(String mediaId) {
private MediaItem createPlayableMediaItemWithCommands(
String mediaId, int maxCommandsForMediaItems) {
MediaItem mediaItem = createPlayableMediaItem(mediaId);
ImmutableList<String> allCommands =
ImmutableList.of(
MediaBrowserConstants.COMMAND_PLAYLIST_ADD, MediaBrowserConstants.COMMAND_RADIO);
ImmutableList.Builder<String> supportedCommands = new ImmutableList.Builder<>();
for (int i = 0; i < min(maxCommandsForMediaItems, allCommands.size()); i++) {
supportedCommands.add(allCommands.get(i));
}
MediaMetadata mediaMetadataWithBrowseActions =
mediaItem
.mediaMetadata
.buildUpon()
.setSupportedCommands(
ImmutableList.of(
MediaBrowserConstants.COMMAND_PLAYLIST_ADD,
MediaBrowserConstants.COMMAND_RADIO))
.build();
mediaItem.mediaMetadata.buildUpon().setSupportedCommands(supportedCommands.build()).build();
return mediaItem.buildUpon().setMediaMetadata(mediaMetadataWithBrowseActions).build();
}