From 65962dcb373685c8e5da9cd861fb66ca7e3e7390 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 26 Sep 2024 03:29:50 -0700 Subject: [PATCH] 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 --- RELEASENOTES.md | 5 +- .../media3/session/ConnectionRequest.java | 22 ++++++--- .../media3/session/LegacyConversions.java | 17 +++++++ .../androidx/media3/session/MediaBrowser.java | 32 ++++++++++-- .../session/MediaBrowserImplLegacy.java | 4 ++ .../media3/session/MediaController.java | 36 +++++++++++++- .../session/MediaControllerImplBase.java | 12 ++++- .../MediaLibraryServiceLegacyStub.java | 4 +- .../androidx/media3/session/MediaSession.java | 21 ++++++-- .../session/MediaSessionLegacyStub.java | 6 ++- .../media3/session/MediaSessionService.java | 6 ++- .../MediaSessionServiceLegacyStub.java | 4 +- .../media3/session/MediaSessionStub.java | 3 +- .../media3/session/MediaSessionStubTest.java | 10 +++- .../media3/session/MediaSessionUnitTest.java | 21 +++++--- ...wserCompatWithMediaLibraryServiceTest.java | 35 ++++++++++++- ...enerWithMediaBrowserServiceCompatTest.java | 32 +++++++----- ...iceCompatCallbackWithMediaBrowserTest.java | 28 +++++------ .../media3/session/MediaControllerTest.java | 3 +- .../session/MediaControllerTestRule.java | 49 +++++++++++++------ .../MediaLibrarySessionCallbackTest.java | 33 +++++++++++++ .../session/MediaSessionCallbackTest.java | 31 ++++++++++++ .../session/MediaSessionServiceTest.java | 20 ++++---- .../session/RemoteControllerTestRule.java | 2 +- .../MediaControllerProviderService.java | 17 +++++++ .../MockMediaBrowserServiceCompat.java | 3 +- .../session/MockMediaLibraryService.java | 26 +++++----- 27 files changed, 380 insertions(+), 102 deletions(-) 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 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(); }