diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 64ea6aabae..6324295640 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -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
Custom
Browse actions of AAOS.
* UI:
diff --git a/libraries/session/src/main/java/androidx/media3/session/ConnectionRequest.java b/libraries/session/src/main/java/androidx/media3/session/ConnectionRequest.java
index 3b0699f767..85626d8fbb 100644
--- a/libraries/session/src/main/java/androidx/media3/session/ConnectionRequest.java
+++ b/libraries/session/src/main/java/androidx/media3/session/ConnectionRequest.java
@@ -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);
}
- ;
}
diff --git a/libraries/session/src/main/java/androidx/media3/session/LegacyConversions.java b/libraries/session/src/main/java/androidx/media3/session/LegacyConversions.java
index d5cf2d4db5..6107180e24 100644
--- a/libraries/session/src/main/java/androidx/media3/session/LegacyConversions.java
+++ b/libraries/session/src/main/java/androidx/media3/session/LegacyConversions.java
@@ -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);
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaBrowser.java b/libraries/session/src/main/java/androidx/media3/session/MediaBrowser.java
index b2dc0501f7..a5f968a387 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaBrowser.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaBrowser.java
@@ -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.
+ *
+ *
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
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java
index 46fcab9ab2..a8ef55e73c 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaBrowserImplLegacy.java
@@ -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(),
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaController.java b/libraries/session/src/main/java/androidx/media3/session/MediaController.java
index 59dafa8dfc..15abc49131 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaController.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaController.java
@@ -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.
+ *
+ *
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.
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java
index 1fd3ac59a5..c1e1c95699 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java
@@ -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) {
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java
index 9351e67703..4f05563aaa 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java
@@ -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() {
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 c7d1325739..8e6b41a388 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
@@ -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);
}
}
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
index 771e5550a5..fccc3a49b4 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
@@ -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 {
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionService.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionService.java
index c7c247bef6..5bdaebabe5 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionService.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionService.java
@@ -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 {
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionServiceLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionServiceLegacyStub.java
index 03c1fb25a1..890fa298ce 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionServiceLegacyStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionServiceLegacyStub.java
@@ -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() {
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java
index 8529282436..aa710084c1 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java
@@ -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);
diff --git a/libraries/session/src/test/java/androidx/media3/session/MediaSessionStubTest.java b/libraries/session/src/test/java/androidx/media3/session/MediaSessionStubTest.java
index 941384cf3e..755f3d5651 100644
--- a/libraries/session/src/test/java/androidx/media3/session/MediaSessionStubTest.java
+++ b/libraries/session/src/test/java/androidx/media3/session/MediaSessionStubTest.java
@@ -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());
}
}
diff --git a/libraries/session/src/test/java/androidx/media3/session/MediaSessionUnitTest.java b/libraries/session/src/test/java/androidx/media3/session/MediaSessionUnitTest.java
index 7534a61cb5..1c619d237d 100644
--- a/libraries/session/src/test/java/androidx/media3/session/MediaSessionUnitTest.java
+++ b/libraries/session/src/test/java/androidx/media3/session/MediaSessionUnitTest.java
@@ -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);
}
}
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java
index 1d25aa8ad5..41b1b3f47a 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java
@@ -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 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 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;
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java
index 5cb7633b97..fe1297c83c 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserListenerWithMediaBrowserServiceCompatTest.java
@@ -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 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 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
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
index 873d2a4441..4a2230dcec 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserServiceCompatCallbackWithMediaBrowserTest.java
@@ -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 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 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 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 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> 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> 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> 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> 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 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 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 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 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> 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> result =
browser.getSearchResult(testQuery, testPage, testPageSize, testParams);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTest.java
index 327292de0a..037ba929e0 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTest.java
@@ -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();
}
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTestRule.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTestRule.java
index e2864b8a49..9a37439c03 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTestRule.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTestRule.java
@@ -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();
}
});
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaLibrarySessionCallbackTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaLibrarySessionCallbackTest.java
index 3ef2e246b9..599c15461a 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaLibrarySessionCallbackTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaLibrarySessionCallbackTest.java
@@ -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";
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java
index e8c361f83b..fcd4f9418f 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java
@@ -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);
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionServiceTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionServiceTest.java
index 62348604cf..586ec6fb89 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionServiceTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionServiceTest.java
@@ -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);
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/RemoteControllerTestRule.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/RemoteControllerTestRule.java
index 55cf4c92ee..d54ed5a3be 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/RemoteControllerTestRule.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/RemoteControllerTestRule.java
@@ -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}. */
diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaControllerProviderService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaControllerProviderService.java
index a912560c56..fbd0239562 100644
--- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaControllerProviderService.java
+++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaControllerProviderService.java
@@ -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 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();
}
});
diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java
index 05362358bf..092d5be93c 100644
--- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java
+++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaBrowserServiceCompat.java
@@ -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 browseActionList = new ArrayList<>();
for (int i = 0; i < min(actionLimit, browseActions.size()); i++) {
diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java
index 8282e43ffd..4c60d9a36b 100644
--- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java
+++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java
@@ -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 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 allCommands =
+ ImmutableList.of(
+ MediaBrowserConstants.COMMAND_PLAYLIST_ADD, MediaBrowserConstants.COMMAND_RADIO);
+ ImmutableList.Builder 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();
}