From 9abe9e2a9789e633b089dbc07e5d482dee9d8527 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 5 Mar 2024 10:04:45 -0800 Subject: [PATCH] Refine auto-update logic of CommandButton.isEnabled We currently update this value for controllers to match the availability of the associated command. This however makes it impossible to mark a button as unavailable if the command is available. This can be refined by only setting the 'enabled' field to false if the command is not available, not the other way round. And we should also enable the button by default as disabling is the unusual case not many apps will use. In addition, this change fixes missing update logic when the player commands changed and it adds some additional test coverage for all these cases. PiperOrigin-RevId: 612881016 --- RELEASENOTES.md | 3 + .../media3/session/CommandButton.java | 57 +++-- .../media3/session/ConnectionState.java | 3 +- .../media3/session/MediaController.java | 6 + .../session/MediaControllerImplBase.java | 61 ++++-- .../media3/session/MediaControllerStub.java | 12 +- .../androidx/media3/session/MediaSession.java | 15 +- .../media3/session/MediaSessionStub.java | 2 +- .../media3/session/PlayerWrapper.java | 34 ++- .../media3/session/CommandButtonTest.java | 61 ++++-- .../session/MediaSessionServiceTest.java | 18 +- ...tateCompatActionsWithMediaSessionTest.java | 17 +- .../media3/session/MediaControllerTest.java | 195 +++++++++++++++--- .../session/MediaSessionServiceTest.java | 4 +- .../session/MediaSessionProviderService.java | 4 +- .../media3/session/RemoteMediaController.java | 2 +- 16 files changed, 376 insertions(+), 118 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e4610387ad..024fcdc796 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -58,6 +58,9 @@ * Fix issue where the current position jumps back when the controller replaces the current item ([#951](https://github.com/androidx/media/issues/951)). + * Change default of `CommandButton.enabled` to `true` and ensure the value + can stay false for controllers even if the associated command is + available. * UI: * Fallback to include audio track language name if `Locale` cannot identify a display name diff --git a/libraries/session/src/main/java/androidx/media3/session/CommandButton.java b/libraries/session/src/main/java/androidx/media3/session/CommandButton.java index 7179d1dbc8..e173565de6 100644 --- a/libraries/session/src/main/java/androidx/media3/session/CommandButton.java +++ b/libraries/session/src/main/java/androidx/media3/session/CommandButton.java @@ -412,6 +412,7 @@ public final class CommandButton implements Bundleable { extras = Bundle.EMPTY; playerCommand = Player.COMMAND_INVALID; icon = ICON_UNDEFINED; + enabled = true; } /** @@ -507,6 +508,12 @@ public final class CommandButton implements Bundleable { /** * Sets whether the button is enabled. * + *

Note that this value will be set to {@code false} for {@link MediaController} instances if + * the corresponding command is not available to this controller (see {@link #setPlayerCommand} + * and {@link #setSessionCommand}). + * + *

The default value is {@code true}. + * * @param enabled Whether the button is enabled. * @return This builder for chaining. */ @@ -571,7 +578,13 @@ public final class CommandButton implements Bundleable { */ @UnstableApi public final Bundle extras; - /** Whether it's enabled. */ + /** + * Whether the button is enabled. + * + *

Note that this value will be set to {@code false} for {@link MediaController} instances if + * the corresponding command is not available to this controller (see {@link #playerCommand} and + * {@link #sessionCommand}). + */ public final boolean isEnabled; private CommandButton( @@ -639,31 +652,35 @@ public final class CommandButton implements Bundleable { } /** - * Returns a list of command buttons with the {@link CommandButton#isEnabled} flag set according - * to the available commands passed in. + * Returns a list of command buttons with the {@link CommandButton#isEnabled} flag set to false if + * the corresponding command is not available. */ - /* package */ static ImmutableList getEnabledCommandButtons( + /* package */ static ImmutableList copyWithUnavailableButtonsDisabled( List commandButtons, SessionCommands sessionCommands, Player.Commands playerCommands) { - ImmutableList.Builder enabledButtons = new ImmutableList.Builder<>(); + ImmutableList.Builder updatedButtons = new ImmutableList.Builder<>(); for (int i = 0; i < commandButtons.size(); i++) { CommandButton button = commandButtons.get(i); - enabledButtons.add( - button.copyWithIsEnabled(isEnabled(button, sessionCommands, playerCommands))); + if (isButtonCommandAvailable(button, sessionCommands, playerCommands)) { + updatedButtons.add(button); + } else { + updatedButtons.add(button.copyWithIsEnabled(false)); + } } - return enabledButtons.build(); + return updatedButtons.build(); } /** - * Returns whether the {@link CommandButton} is enabled given the available commands passed in. + * Returns whether the required command ({@link #playerCommand} or {@link #sessionCommand}) for + * the button is available. * * @param button The command button. * @param sessionCommands The available session commands. * @param playerCommands The available player commands. - * @return Whether the button is enabled given the available commands. + * @return Whether the command required for this button is available. */ - /* package */ static boolean isEnabled( + /* package */ static boolean isButtonCommandAvailable( CommandButton button, SessionCommands sessionCommands, Player.Commands playerCommands) { return (button.sessionCommand != null && sessionCommands.contains(button.sessionCommand)) || (button.playerCommand != Player.COMMAND_INVALID @@ -706,7 +723,7 @@ public final class CommandButton implements Bundleable { if (iconUri != null) { bundle.putParcelable(FIELD_ICON_URI, iconUri); } - if (isEnabled) { + if (!isEnabled) { bundle.putBoolean(FIELD_ENABLED, isEnabled); } return bundle; @@ -722,9 +739,18 @@ public final class CommandButton implements Bundleable { @SuppressWarnings("deprecation") // Deprecated instance of deprecated class public static final Creator CREATOR = CommandButton::fromBundle; - /** Restores a {@code CommandButton} from a {@link Bundle}. */ + /** + * @deprecated Use {@link #fromBundle(Bundle, int)} instead. + */ + @Deprecated @UnstableApi public static CommandButton fromBundle(Bundle bundle) { + return fromBundle(bundle, MediaSessionStub.VERSION_INT); + } + + /** Restores a {@code CommandButton} from a {@link Bundle}. */ + @UnstableApi + public static CommandButton fromBundle(Bundle bundle, int sessionInterfaceVersion) { @Nullable Bundle sessionCommandBundle = bundle.getBundle(FIELD_SESSION_COMMAND); @Nullable SessionCommand sessionCommand = @@ -735,7 +761,10 @@ public final class CommandButton implements Bundleable { int iconResId = bundle.getInt(FIELD_ICON_RES_ID, /* defaultValue= */ 0); CharSequence displayName = bundle.getCharSequence(FIELD_DISPLAY_NAME, /* defaultValue= */ ""); @Nullable Bundle extras = bundle.getBundle(FIELD_EXTRAS); - boolean enabled = bundle.getBoolean(FIELD_ENABLED, /* defaultValue= */ false); + // Before sessionInterfaceVersion == 3, the session expected this value to be meaningless and we + // can only assume it was meant to be true. + boolean enabled = + sessionInterfaceVersion < 3 || bundle.getBoolean(FIELD_ENABLED, /* defaultValue= */ true); @Nullable Uri iconUri = bundle.getParcelable(FIELD_ICON_URI); @Icon int icon = bundle.getInt(FIELD_ICON, /* defaultValue= */ ICON_UNDEFINED); Builder builder = new Builder(); diff --git a/libraries/session/src/main/java/androidx/media3/session/ConnectionState.java b/libraries/session/src/main/java/androidx/media3/session/ConnectionState.java index b1f0e82a5c..b138ef5319 100644 --- a/libraries/session/src/main/java/androidx/media3/session/ConnectionState.java +++ b/libraries/session/src/main/java/androidx/media3/session/ConnectionState.java @@ -164,7 +164,8 @@ import java.util.List; List commandButtonArrayList = bundle.getParcelableArrayList(FIELD_CUSTOM_LAYOUT); ImmutableList customLayout = commandButtonArrayList != null - ? BundleCollectionUtil.fromBundleList(CommandButton::fromBundle, commandButtonArrayList) + ? BundleCollectionUtil.fromBundleList( + b -> CommandButton.fromBundle(b, sessionInterfaceVersion), commandButtonArrayList) : ImmutableList.of(); @Nullable Bundle sessionCommandsBundle = bundle.getBundle(FIELD_SESSION_COMMANDS); SessionCommands sessionCommands = 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 4961d0a2c8..7b09c3d223 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaController.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaController.java @@ -388,6 +388,9 @@ public class MediaController implements Player { * changes the available commands} for a controller that affect whether buttons of the custom * layout are enabled or disabled. * + *

Note that the {@linkplain CommandButton#isEnabled enabled} flag is set to {@code false} if + * the available commands do not allow to use a button. + * * @param controller The controller. * @param layout The ordered list of {@linkplain CommandButton command buttons}. */ @@ -976,6 +979,9 @@ public class MediaController implements Player { *

After being connected, a change of the custom layout is reported with {@link * Listener#onCustomLayoutChanged(MediaController, List)}. * + *

Note that the {@linkplain CommandButton#isEnabled enabled} flag is set to {@code false} if + * the available commands do not allow to use a button. + * * @return The custom layout. */ @UnstableApi 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 9e590dbe61..a19de8d34e 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java @@ -120,7 +120,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; private boolean released; private PlayerInfo playerInfo; @Nullable private PendingIntent sessionActivity; - private ImmutableList customLayout; + private ImmutableList customLayoutOriginal; + private ImmutableList customLayoutWithUnavailableButtonsDisabled; private SessionCommands sessionCommands; private Commands playerCommandsFromSession; private Commands playerCommandsFromPlayer; @@ -146,7 +147,8 @@ import org.checkerframework.checker.nullness.qual.NonNull; playerInfo = PlayerInfo.DEFAULT; surfaceSize = Size.UNKNOWN; sessionCommands = SessionCommands.EMPTY; - customLayout = ImmutableList.of(); + customLayoutOriginal = ImmutableList.of(); + customLayoutWithUnavailableButtonsDisabled = ImmutableList.of(); playerCommandsFromSession = Commands.EMPTY; playerCommandsFromPlayer = Commands.EMPTY; intersectedPlayerCommands = @@ -726,7 +728,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; @Override public ImmutableList getCustomLayout() { - return customLayout; + return customLayoutWithUnavailableButtonsDisabled; } @Override @@ -2611,8 +2613,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; intersectedPlayerCommands = createIntersectedCommandsEnsuringCommandReleaseAvailable( playerCommandsFromSession, playerCommandsFromPlayer); - customLayout = - CommandButton.getEnabledCommandButtons( + customLayoutOriginal = result.customLayout; + customLayoutWithUnavailableButtonsDisabled = + CommandButton.copyWithUnavailableButtonsDisabled( result.customLayout, sessionCommands, intersectedPlayerCommands); playerInfo = result.playerInfo; try { @@ -2765,6 +2768,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; if (!playerCommandsChanged && !sessionCommandsChanged) { return; } + this.sessionCommands = sessionCommands; boolean intersectedPlayerCommandsChanged = false; if (playerCommandsChanged) { playerCommandsFromSession = playerCommands; @@ -2776,13 +2780,12 @@ import org.checkerframework.checker.nullness.qual.NonNull; !Util.areEqual(intersectedPlayerCommands, prevIntersectedPlayerCommands); } boolean customLayoutChanged = false; - if (sessionCommandsChanged) { - this.sessionCommands = sessionCommands; - ImmutableList oldCustomLayout = customLayout; - customLayout = - CommandButton.getEnabledCommandButtons( - customLayout, sessionCommands, intersectedPlayerCommands); - customLayoutChanged = !customLayout.equals(oldCustomLayout); + if (sessionCommandsChanged || intersectedPlayerCommandsChanged) { + ImmutableList oldCustomLayout = customLayoutWithUnavailableButtonsDisabled; + customLayoutWithUnavailableButtonsDisabled = + CommandButton.copyWithUnavailableButtonsDisabled( + customLayoutOriginal, sessionCommands, intersectedPlayerCommands); + customLayoutChanged = !customLayoutWithUnavailableButtonsDisabled.equals(oldCustomLayout); } if (intersectedPlayerCommandsChanged) { listeners.sendEvent( @@ -2798,7 +2801,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; if (customLayoutChanged) { getInstance() .notifyControllerListener( - listener -> listener.onCustomLayoutChanged(getInstance(), customLayout)); + listener -> + listener.onCustomLayoutChanged( + getInstance(), customLayoutWithUnavailableButtonsDisabled)); } } @@ -2816,11 +2821,24 @@ import org.checkerframework.checker.nullness.qual.NonNull; playerCommandsFromSession, playerCommandsFromPlayer); boolean intersectedPlayerCommandsChanged = !Util.areEqual(intersectedPlayerCommands, prevIntersectedPlayerCommands); + boolean customLayoutChanged = false; if (intersectedPlayerCommandsChanged) { + ImmutableList oldCustomLayout = customLayoutWithUnavailableButtonsDisabled; + customLayoutWithUnavailableButtonsDisabled = + CommandButton.copyWithUnavailableButtonsDisabled( + customLayoutOriginal, sessionCommands, intersectedPlayerCommands); + customLayoutChanged = !customLayoutWithUnavailableButtonsDisabled.equals(oldCustomLayout); listeners.sendEvent( /* eventFlag= */ Player.EVENT_AVAILABLE_COMMANDS_CHANGED, listener -> listener.onAvailableCommandsChanged(intersectedPlayerCommands)); } + if (customLayoutChanged) { + getInstance() + .notifyControllerListener( + listener -> + listener.onCustomLayoutChanged( + getInstance(), customLayoutWithUnavailableButtonsDisabled)); + } } // Calling deprecated listener callback method for backwards compatibility. @@ -2829,19 +2847,24 @@ import org.checkerframework.checker.nullness.qual.NonNull; if (!isConnected()) { return; } - ImmutableList oldCustomLayout = customLayout; - customLayout = - CommandButton.getEnabledCommandButtons(layout, sessionCommands, intersectedPlayerCommands); - boolean hasCustomLayoutChanged = !Objects.equals(customLayout, oldCustomLayout); + ImmutableList oldCustomLayout = customLayoutWithUnavailableButtonsDisabled; + customLayoutOriginal = ImmutableList.copyOf(layout); + customLayoutWithUnavailableButtonsDisabled = + CommandButton.copyWithUnavailableButtonsDisabled( + layout, sessionCommands, intersectedPlayerCommands); + boolean hasCustomLayoutChanged = + !Objects.equals(customLayoutWithUnavailableButtonsDisabled, oldCustomLayout); getInstance() .notifyControllerListener( listener -> { ListenableFuture future = checkNotNull( - listener.onSetCustomLayout(getInstance(), customLayout), + listener.onSetCustomLayout( + getInstance(), customLayoutWithUnavailableButtonsDisabled), "MediaController.Listener#onSetCustomLayout() must not return null"); if (hasCustomLayoutChanged) { - listener.onCustomLayoutChanged(getInstance(), customLayout); + listener.onCustomLayoutChanged( + getInstance(), customLayoutWithUnavailableButtonsDisabled); } sendControllerResultWhenReady(seq, future); }); diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerStub.java index e382cbd74c..d1245c4ffc 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerStub.java @@ -112,8 +112,18 @@ import org.checkerframework.checker.nullness.qual.NonNull; } List layout; try { + @Nullable MediaControllerImplBase controller = this.controller.get(); + @Nullable + SessionToken connectedToken = controller == null ? null : controller.getConnectedToken(); + if (connectedToken == null) { + // Stale event. + return; + } + int sessionInterfaceVersion = connectedToken.getInterfaceVersion(); layout = - BundleCollectionUtil.fromBundleList(CommandButton::fromBundle, commandButtonBundleList); + BundleCollectionUtil.fromBundleList( + bundle -> CommandButton.fromBundle(bundle, sessionInterfaceVersion), + commandButtonBundleList); } catch (RuntimeException e) { Log.w(TAG, "Ignoring malformed Bundle for CommandButton", e); return; 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 44f7878ba2..8ec0c33b54 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java @@ -388,6 +388,9 @@ public class MediaSession { *

Use {@code MediaSession.setCustomLayout(..)} to update the custom layout during the life * time of the session. * + *

On the controller side, the {@linkplain CommandButton#isEnabled enabled} flag is set to + * {@code false} if the available commands of a controller do not allow to use a button. + * * @param customLayout The ordered list of {@link CommandButton command buttons}. * @return The builder to allow chaining. */ @@ -911,7 +914,8 @@ public class MediaSession { * MediaController#getCustomLayout() controller already has available}. Note that this comparison * uses {@link CommandButton#equals} and therefore ignores {@link CommandButton#extras}. * - *

It's up to controller's decision how to represent the layout in its own UI. + *

On the controller side, the {@linkplain CommandButton#isEnabled enabled} flag is set to + * {@code false} if the available commands of the controller do not allow to use a button. * *

Interoperability: This call has no effect when called for a {@linkplain * ControllerInfo#LEGACY_CONTROLLER_VERSION legacy controller}. @@ -933,9 +937,8 @@ public class MediaSession { *

Calling this method broadcasts the custom layout to all connected Media3 controllers, * including the {@linkplain #getMediaNotificationControllerInfo() media notification controller}. * - *

On the controller side, the {@linkplain CommandButton#isEnabled enabled} flag is set - * according to the available commands of the controller which overrides a value that has been set - * by the session. + *

On the controller side, the {@linkplain CommandButton#isEnabled enabled} flag is set to + * {@code false} if the available commands of a controller do not allow to use a button. * *

{@link MediaController.Listener#onCustomLayoutChanged(MediaController, List)} is only called * if the new custom layout is different to the custom layout the {@linkplain @@ -1653,7 +1656,9 @@ public class MediaSession { * *

Make sure to have the session commands of all command buttons of the custom layout * included in the {@linkplain #setAvailableSessionCommands(SessionCommands)} available - * session commands}. + * session commands} On the controller side, the {@linkplain CommandButton#isEnabled enabled} + * flag is set to {@code false} if the available commands of the controller do not allow to + * use a button. */ @CanIgnoreReturnValue public AcceptedResultBuilder setCustomLayout(@Nullable List customLayout) { 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 48c0e2d124..5576ea61a3 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionStub.java @@ -113,7 +113,7 @@ import java.util.concurrent.ExecutionException; private static final String TAG = "MediaSessionStub"; /** The version of the IMediaSession interface. */ - public static final int VERSION_INT = 2; + public static final int VERSION_INT = 3; /** * Sequence number used when a controller method is triggered on the sesison side that wasn't diff --git a/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java b/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java index f02fe00a9a..cdf97c8265 100644 --- a/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java +++ b/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java @@ -1043,25 +1043,23 @@ import java.util.List; for (int i = 0; i < customLayout.size(); i++) { CommandButton commandButton = customLayout.get(i); - if (commandButton.sessionCommand != null) { - SessionCommand sessionCommand = commandButton.sessionCommand; - if (sessionCommand.commandCode == SessionCommand.COMMAND_CODE_CUSTOM - && CommandButton.isEnabled( - commandButton, availableSessionCommands, availablePlayerCommands)) { - Bundle actionExtras = sessionCommand.customExtras; - if (commandButton.icon != CommandButton.ICON_UNDEFINED) { - actionExtras = new Bundle(sessionCommand.customExtras); - actionExtras.putInt( - MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, commandButton.icon); - } - builder.addCustomAction( - new PlaybackStateCompat.CustomAction.Builder( - sessionCommand.customAction, - commandButton.displayName, - commandButton.iconResId) - .setExtras(actionExtras) - .build()); + SessionCommand sessionCommand = commandButton.sessionCommand; + if (sessionCommand != null + && commandButton.isEnabled + && sessionCommand.commandCode == SessionCommand.COMMAND_CODE_CUSTOM + && CommandButton.isButtonCommandAvailable( + commandButton, availableSessionCommands, availablePlayerCommands)) { + Bundle actionExtras = sessionCommand.customExtras; + if (commandButton.icon != CommandButton.ICON_UNDEFINED) { + actionExtras = new Bundle(sessionCommand.customExtras); + actionExtras.putInt( + MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, commandButton.icon); } + builder.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + sessionCommand.customAction, commandButton.displayName, commandButton.iconResId) + .setExtras(actionExtras) + .build()); } } if (playerError != null) { diff --git a/libraries/session/src/test/java/androidx/media3/session/CommandButtonTest.java b/libraries/session/src/test/java/androidx/media3/session/CommandButtonTest.java index 2a74567d40..78c265f2c9 100644 --- a/libraries/session/src/test/java/androidx/media3/session/CommandButtonTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/CommandButtonTest.java @@ -31,7 +31,8 @@ import org.junit.runner.RunWith; public class CommandButtonTest { @Test - public void isEnabled_playerCommandAvailableOrUnavailableInPlayerCommands_isEnabledCorrectly() { + public void + isButtonCommandAvailable_playerCommandAvailableOrUnavailableInPlayerCommands_isEnabledCorrectly() { CommandButton button = new CommandButton.Builder() .setDisplayName("button") @@ -41,14 +42,18 @@ public class CommandButtonTest { Player.Commands availablePlayerCommands = Player.Commands.EMPTY.buildUpon().add(Player.COMMAND_SEEK_TO_NEXT).build(); - assertThat(CommandButton.isEnabled(button, SessionCommands.EMPTY, Player.Commands.EMPTY)) + assertThat( + CommandButton.isButtonCommandAvailable( + button, SessionCommands.EMPTY, Player.Commands.EMPTY)) .isFalse(); - assertThat(CommandButton.isEnabled(button, SessionCommands.EMPTY, availablePlayerCommands)) + assertThat( + CommandButton.isButtonCommandAvailable( + button, SessionCommands.EMPTY, availablePlayerCommands)) .isTrue(); } @Test - public void isEnabled_sessionCommandAvailableOrUnavailable_isEnabledCorrectly() { + public void isButtonCommandAvailable_sessionCommandAvailableOrUnavailable_isEnabledCorrectly() { SessionCommand command1 = new SessionCommand("command1", Bundle.EMPTY); CommandButton button = new CommandButton.Builder() @@ -59,14 +64,18 @@ public class CommandButtonTest { SessionCommands availableSessionCommands = SessionCommands.EMPTY.buildUpon().add(command1).build(); - assertThat(CommandButton.isEnabled(button, SessionCommands.EMPTY, Player.Commands.EMPTY)) + assertThat( + CommandButton.isButtonCommandAvailable( + button, SessionCommands.EMPTY, Player.Commands.EMPTY)) .isFalse(); - assertThat(CommandButton.isEnabled(button, availableSessionCommands, Player.Commands.EMPTY)) + assertThat( + CommandButton.isButtonCommandAvailable( + button, availableSessionCommands, Player.Commands.EMPTY)) .isTrue(); } @Test - public void getEnabledCommandButtons() { + public void copyWithUnavailableButtonsDisabled() { CommandButton button1 = new CommandButton.Builder() .setDisplayName("button1") @@ -86,11 +95,11 @@ public class CommandButtonTest { Player.Commands.EMPTY.buildUpon().add(Player.COMMAND_SEEK_TO_PREVIOUS).build(); assertThat( - CommandButton.getEnabledCommandButtons( + CommandButton.copyWithUnavailableButtonsDisabled( ImmutableList.of(button1, button2), SessionCommands.EMPTY, Player.Commands.EMPTY)) - .containsExactly(button1, button2); + .containsExactly(button1.copyWithIsEnabled(false), button2.copyWithIsEnabled(false)); assertThat( - CommandButton.getEnabledCommandButtons( + CommandButton.copyWithUnavailableButtonsDisabled( ImmutableList.of(button1, button2), availableSessionCommands, availablePlayerCommands)) @@ -134,7 +143,8 @@ public class CommandButtonTest { .setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS) .build(); - CommandButton serialisedButton = CommandButton.fromBundle(button.toBundle()); + CommandButton serialisedButton = + CommandButton.fromBundle(button.toBundle(), MediaSessionStub.VERSION_INT); assertThat(serialisedButton.iconUri).isEqualTo(uri); } @@ -148,7 +158,8 @@ public class CommandButtonTest { .setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS) .build(); - CommandButton serialisedButton = CommandButton.fromBundle(button.toBundle()); + CommandButton serialisedButton = + CommandButton.fromBundle(button.toBundle(), MediaSessionStub.VERSION_INT); assertThat(serialisedButton.iconUri).isNull(); } @@ -180,7 +191,8 @@ public class CommandButtonTest { .setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT) .build(); - assertThat(button).isEqualTo(CommandButton.fromBundle(button.toBundle())); + assertThat(button) + .isEqualTo(CommandButton.fromBundle(button.toBundle(), MediaSessionStub.VERSION_INT)); assertThat(button) .isNotEqualTo( new CommandButton.Builder() @@ -205,7 +217,7 @@ public class CommandButtonTest { assertThat(button) .isNotEqualTo( new CommandButton.Builder() - .setEnabled(true) + .setEnabled(false) .setDisplayName("button") .setIconResId(R.drawable.media3_notification_small_icon) .setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT) @@ -380,11 +392,11 @@ public class CommandButtonTest { new CommandButton.Builder().setPlayerCommand(Player.COMMAND_PLAY_PAUSE).build(); CommandButton restoredButtonWithSessionCommand = - CommandButton.fromBundle(buttonWithSessionCommand.toBundle()); + CommandButton.fromBundle(buttonWithSessionCommand.toBundle(), MediaSessionStub.VERSION_INT); CommandButton restoredButtonWithPlayerCommand = - CommandButton.fromBundle(buttonWithPlayerCommand.toBundle()); + CommandButton.fromBundle(buttonWithPlayerCommand.toBundle(), MediaSessionStub.VERSION_INT); CommandButton restoredButtonWithDefaultValues = - CommandButton.fromBundle(buttonWithDefaultValues.toBundle()); + CommandButton.fromBundle(buttonWithDefaultValues.toBundle(), MediaSessionStub.VERSION_INT); assertThat(restoredButtonWithSessionCommand).isEqualTo(buttonWithSessionCommand); assertThat(restoredButtonWithSessionCommand.extras.get("key")).isEqualTo("value"); @@ -392,4 +404,19 @@ public class CommandButtonTest { assertThat(restoredButtonWithPlayerCommand.extras.get("key")).isEqualTo("value"); assertThat(restoredButtonWithDefaultValues).isEqualTo(buttonWithDefaultValues); } + + @Test + public void fromBundle_withSessionInterfaceVersionLessThan3_setsEnabledToTrue() { + CommandButton buttonWithEnabledFalse = + new CommandButton.Builder() + .setEnabled(false) + .setSessionCommand(new SessionCommand(SessionCommand.COMMAND_CODE_SESSION_SET_RATING)) + .build(); + + CommandButton restoredButtonAssumingOldSessionInterface = + CommandButton.fromBundle( + buttonWithEnabledFalse.toBundle(), /* sessionInterfaceVersion= */ 2); + + assertThat(restoredButtonAssumingOldSessionInterface.isEnabled).isTrue(); + } } diff --git a/libraries/session/src/test/java/androidx/media3/session/MediaSessionServiceTest.java b/libraries/session/src/test/java/androidx/media3/session/MediaSessionServiceTest.java index f6bb52666f..7b84bd1ddd 100644 --- a/libraries/session/src/test/java/androidx/media3/session/MediaSessionServiceTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/MediaSessionServiceTest.java @@ -158,6 +158,8 @@ public class MediaSessionServiceTest { throws TimeoutException { SessionCommand command1 = new SessionCommand("command1", Bundle.EMPTY); SessionCommand command2 = new SessionCommand("command2", Bundle.EMPTY); + SessionCommand command3 = new SessionCommand("command3", Bundle.EMPTY); + SessionCommand command4 = new SessionCommand("command4", Bundle.EMPTY); CommandButton button1 = new CommandButton.Builder() .setDisplayName("customAction1") @@ -170,10 +172,23 @@ public class MediaSessionServiceTest { .setIconResId(R.drawable.media3_notification_small_icon) .setSessionCommand(command2) .build(); + CommandButton button3 = + new CommandButton.Builder() + .setDisplayName("customAction3") + .setEnabled(false) + .setIconResId(R.drawable.media3_notification_small_icon) + .setSessionCommand(command3) + .build(); + CommandButton button4 = + new CommandButton.Builder() + .setDisplayName("customAction4") + .setIconResId(R.drawable.media3_notification_small_icon) + .setSessionCommand(command4) + .build(); ExoPlayer player = new TestExoPlayerBuilder(context).build(); MediaSession session = new MediaSession.Builder(context, player) - .setCustomLayout(ImmutableList.of(button1, button2)) + .setCustomLayout(ImmutableList.of(button1, button2, button3, button4)) .setCallback( new MediaSession.Callback() { @Override @@ -186,6 +201,7 @@ public class MediaSessionServiceTest { .buildUpon() .add(command1) .add(command2) + .add(command3) .build()) .build(); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest.java index 40897444ab..c17452c1f1 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest.java @@ -1446,10 +1446,9 @@ public class MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest Player player = createDefaultPlayer(); Bundle extras1 = new Bundle(); extras1.putString("key1", "value1"); - Bundle extras2 = new Bundle(); - extras1.putString("key2", "value2"); SessionCommand command1 = new SessionCommand("command1", extras1); - SessionCommand command2 = new SessionCommand("command2", extras2); + SessionCommand command2 = new SessionCommand("command2", Bundle.EMPTY); + SessionCommand command3 = new SessionCommand("command3", Bundle.EMPTY); ImmutableList customLayout = ImmutableList.of( new CommandButton.Builder() @@ -1461,6 +1460,12 @@ public class MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest .setDisplayName("button2") .setIconResId(R.drawable.media3_notification_pause) .setSessionCommand(command2) + .build(), + new CommandButton.Builder() + .setDisplayName("button3") + .setEnabled(false) + .setIconResId(R.drawable.media3_notification_pause) + .setSessionCommand(command3) .build()); MediaSession.Callback callback = new MediaSession.Callback() { @@ -1469,7 +1474,11 @@ public class MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest MediaSession session, MediaSession.ControllerInfo controller) { return new AcceptedResultBuilder(session) .setAvailableSessionCommands( - ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(command1).build()) + ConnectionResult.DEFAULT_SESSION_COMMANDS + .buildUpon() + .add(command1) + .add(command3) + .build()) .build(); } }; 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 8738707fa7..4f8b41ac9d 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 @@ -123,32 +123,31 @@ public class MediaControllerTest { @Test public void builder() throws Exception { - MediaController.Builder builder; SessionToken token = remoteSession.getToken(); try { - builder = new MediaController.Builder(null, token); + new MediaController.Builder(null, token); assertWithMessage("null context shouldn't be allowed").fail(); } catch (NullPointerException e) { // expected. pass-through } try { - builder = new MediaController.Builder(context, null); + new MediaController.Builder(context, null); assertWithMessage("null token shouldn't be allowed").fail(); } catch (NullPointerException e) { // expected. pass-through } try { - builder = new MediaController.Builder(context, token).setListener(null); + new MediaController.Builder(context, token).setListener(null); assertWithMessage("null listener shouldn't be allowed").fail(); } catch (NullPointerException e) { // expected. pass-through } try { - builder = new MediaController.Builder(context, token).setApplicationLooper(null); + new MediaController.Builder(context, token).setApplicationLooper(null); assertWithMessage("null looper shouldn't be allowed").fail(); } catch (NullPointerException e) { // expected. pass-through @@ -182,6 +181,7 @@ public class MediaControllerTest { CommandButton button2 = new CommandButton.Builder() .setDisplayName("button2") + .setEnabled(false) .setIconResId(R.drawable.media3_notification_small_icon) .setSessionCommand(new SessionCommand("command2", Bundle.EMPTY)) .build(); @@ -191,11 +191,28 @@ public class MediaControllerTest { .setIconResId(R.drawable.media3_notification_small_icon) .setSessionCommand(new SessionCommand("command3", Bundle.EMPTY)) .build(); - setupCustomLayout(session, ImmutableList.of(button1, button2, button3)); + CommandButton button4 = + new CommandButton.Builder() + .setDisplayName("button4") + .setIconResId(R.drawable.media3_notification_small_icon) + .setPlayerCommand(Player.COMMAND_PLAY_PAUSE) + .build(); + CommandButton button5 = + new CommandButton.Builder() + .setDisplayName("button5") + .setIconResId(R.drawable.media3_notification_small_icon) + .setPlayerCommand(Player.COMMAND_GET_TRACKS) + .build(); + setupCustomLayout(session, ImmutableList.of(button1, button2, button3, button4, button5)); MediaController controller = controllerTestRule.createController(session.getToken()); assertThat(threadTestRule.getHandler().postAndSync(controller::getCustomLayout)) - .containsExactly(button1.copyWithIsEnabled(true), button2.copyWithIsEnabled(true), button3) + .containsExactly( + button1.copyWithIsEnabled(true), + button2.copyWithIsEnabled(false), + button3.copyWithIsEnabled(false), + button4.copyWithIsEnabled(true), + button5.copyWithIsEnabled(false)) .inOrder(); session.cleanUp(); @@ -214,6 +231,7 @@ public class MediaControllerTest { CommandButton button2 = new CommandButton.Builder() .setDisplayName("button2") + .setEnabled(false) .setIconResId(R.drawable.media3_notification_small_icon) .setSessionCommand(new SessionCommand("command2", Bundle.EMPTY)) .build(); @@ -229,7 +247,19 @@ public class MediaControllerTest { .setIconResId(R.drawable.media3_notification_small_icon) .setSessionCommand(new SessionCommand("command4", Bundle.EMPTY)) .build(); - setupCustomLayout(session, ImmutableList.of(button1, button2)); + CommandButton button5 = + new CommandButton.Builder() + .setDisplayName("button5") + .setIconResId(R.drawable.media3_notification_small_icon) + .setPlayerCommand(Player.COMMAND_PLAY_PAUSE) + .build(); + CommandButton button6 = + new CommandButton.Builder() + .setDisplayName("button6") + .setIconResId(R.drawable.media3_notification_small_icon) + .setPlayerCommand(Player.COMMAND_GET_TRACKS) + .build(); + setupCustomLayout(session, ImmutableList.of(button1, button3)); CountDownLatch latch = new CountDownLatch(2); AtomicReference> reportedCustomLayout = new AtomicReference<>(); AtomicReference> reportedCustomLayoutChanged = new AtomicReference<>(); @@ -255,23 +285,32 @@ public class MediaControllerTest { }); ImmutableList initialCustomLayoutFromGetter = threadTestRule.getHandler().postAndSync(controller::getCustomLayout); - session.setCustomLayout(ImmutableList.of(button3, button4)); + session.setCustomLayout(ImmutableList.of(button1, button2, button4, button5, button6)); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); ImmutableList newCustomLayoutFromGetter = threadTestRule.getHandler().postAndSync(controller::getCustomLayout); assertThat(initialCustomLayoutFromGetter) - .containsExactly(button1.copyWithIsEnabled(true), button2.copyWithIsEnabled(true)) + .containsExactly(button1.copyWithIsEnabled(true), button3.copyWithIsEnabled(false)) + .inOrder(); + ImmutableList expectedNewButtons = + ImmutableList.of( + button1.copyWithIsEnabled(true), + button2.copyWithIsEnabled(false), + button4.copyWithIsEnabled(false), + button5.copyWithIsEnabled(true), + button6.copyWithIsEnabled(false)); + assertThat(newCustomLayoutFromGetter).containsExactlyElementsIn(expectedNewButtons).inOrder(); + assertThat(reportedCustomLayout.get()).containsExactlyElementsIn(expectedNewButtons).inOrder(); + assertThat(reportedCustomLayoutChanged.get()) + .containsExactlyElementsIn(expectedNewButtons) .inOrder(); - assertThat(newCustomLayoutFromGetter).containsExactly(button3, button4).inOrder(); - assertThat(reportedCustomLayout.get()).containsExactly(button3, button4).inOrder(); - assertThat(reportedCustomLayoutChanged.get()).containsExactly(button3, button4).inOrder(); session.cleanUp(); } @Test - public void getCustomLayout_setAvailableCommandsAddOrRemoveCommands_reportsCustomLayoutChanged() + public void getCustomLayout_setAvailableCommandsOnSession_reportsCustomLayoutChanged() throws Exception { RemoteMediaSession session = createRemoteMediaSession(TEST_GET_CUSTOM_LAYOUT, null); CommandButton button1 = @@ -283,10 +322,23 @@ public class MediaControllerTest { CommandButton button2 = new CommandButton.Builder() .setDisplayName("button2") + .setEnabled(false) .setIconResId(R.drawable.media3_notification_small_icon) .setSessionCommand(new SessionCommand("command2", Bundle.EMPTY)) .build(); - setupCustomLayout(session, ImmutableList.of(button1, button2)); + CommandButton button3 = + new CommandButton.Builder() + .setDisplayName("button3") + .setIconResId(R.drawable.media3_notification_small_icon) + .setPlayerCommand(Player.COMMAND_PLAY_PAUSE) + .build(); + CommandButton button4 = + new CommandButton.Builder() + .setDisplayName("button4") + .setIconResId(R.drawable.media3_notification_small_icon) + .setPlayerCommand(Player.COMMAND_GET_TRACKS) + .build(); + setupCustomLayout(session, ImmutableList.of(button1, button2, button3, button4)); CountDownLatch latch = new CountDownLatch(2); List> reportedCustomLayoutChanged = new ArrayList<>(); List> getterCustomLayoutChanged = new ArrayList<>(); @@ -308,23 +360,95 @@ public class MediaControllerTest { // Remove commands in custom layout from available commands. session.setAvailableCommands(SessionCommands.EMPTY, Player.Commands.EMPTY); - // Add one command back. + // Add one sesion and player command back. session.setAvailableCommands( - new SessionCommands.Builder().add(button2.sessionCommand).build(), Player.Commands.EMPTY); + new SessionCommands.Builder().add(button2.sessionCommand).build(), + new Player.Commands.Builder().add(Player.COMMAND_GET_TRACKS).build()); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(initialCustomLayout) - .containsExactly(button1.copyWithIsEnabled(true), button2.copyWithIsEnabled(true)); + .containsExactly( + button1.copyWithIsEnabled(true), + button2.copyWithIsEnabled(false), + button3.copyWithIsEnabled(true), + button4.copyWithIsEnabled(false)); assertThat(reportedCustomLayoutChanged).hasSize(2); - assertThat(reportedCustomLayoutChanged.get(0)).containsExactly(button1, button2).inOrder(); + assertThat(reportedCustomLayoutChanged.get(0)) + .containsExactly( + button1.copyWithIsEnabled(false), + button2.copyWithIsEnabled(false), + button3.copyWithIsEnabled(false), + button4.copyWithIsEnabled(false)) + .inOrder(); assertThat(reportedCustomLayoutChanged.get(1)) - .containsExactly(button1, button2.copyWithIsEnabled(true)) + .containsExactly( + button1.copyWithIsEnabled(false), + button2.copyWithIsEnabled(false), + button3.copyWithIsEnabled(false), + button4.copyWithIsEnabled(true)) .inOrder(); assertThat(getterCustomLayoutChanged).hasSize(2); - assertThat(getterCustomLayoutChanged.get(0)).containsExactly(button1, button2).inOrder(); - assertThat(getterCustomLayoutChanged.get(1)) - .containsExactly(button1, button2.copyWithIsEnabled(true)) + assertThat(getterCustomLayoutChanged.get(0)) + .containsExactly( + button1.copyWithIsEnabled(false), + button2.copyWithIsEnabled(false), + button3.copyWithIsEnabled(false), + button4.copyWithIsEnabled(false)) .inOrder(); + assertThat(getterCustomLayoutChanged.get(1)) + .containsExactly( + button1.copyWithIsEnabled(false), + button2.copyWithIsEnabled(false), + button3.copyWithIsEnabled(false), + button4.copyWithIsEnabled(true)) + .inOrder(); + } + + @Test + public void getCustomLayout_setAvailableCommandsOnPlayer_reportsCustomLayoutChanged() + throws Exception { + RemoteMediaSession session = createRemoteMediaSession(TEST_GET_CUSTOM_LAYOUT, null); + CommandButton button = + new CommandButton.Builder() + .setDisplayName("button") + .setIconResId(R.drawable.media3_notification_small_icon) + .setPlayerCommand(Player.COMMAND_PLAY_PAUSE) + .build(); + setupCustomLayout(session, ImmutableList.of(button)); + CountDownLatch latch = new CountDownLatch(2); + List> reportedCustomLayouts = new ArrayList<>(); + List> getterCustomLayouts = new ArrayList<>(); + MediaController.Listener listener = + new MediaController.Listener() { + @Override + public void onCustomLayoutChanged( + MediaController controller, List layout) { + reportedCustomLayouts.add(layout); + getterCustomLayouts.add(controller.getCustomLayout()); + latch.countDown(); + } + }; + MediaController controller = + controllerTestRule.createController( + session.getToken(), /* connectionHints= */ Bundle.EMPTY, listener); + ImmutableList initialCustomLayout = + threadTestRule.getHandler().postAndSync(controller::getCustomLayout); + + // Disable player command and then add it back. + session.getMockPlayer().notifyAvailableCommandsChanged(Player.Commands.EMPTY); + session + .getMockPlayer() + .notifyAvailableCommandsChanged( + new Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build()); + + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat(initialCustomLayout).containsExactly(button.copyWithIsEnabled(true)); + assertThat(reportedCustomLayouts).hasSize(2); + assertThat(reportedCustomLayouts.get(0)).containsExactly(button.copyWithIsEnabled(false)); + assertThat(reportedCustomLayouts.get(1)).containsExactly(button.copyWithIsEnabled(true)); + assertThat(getterCustomLayouts).hasSize(2); + assertThat(getterCustomLayouts.get(0)).containsExactly(button.copyWithIsEnabled(false)); + assertThat(getterCustomLayouts.get(1)).containsExactly(button.copyWithIsEnabled(true)); } @Test @@ -341,6 +465,7 @@ public class MediaControllerTest { CommandButton button2 = new CommandButton.Builder() .setDisplayName("button2") + .setEnabled(false) .setIconResId(R.drawable.media3_notification_small_icon) .setSessionCommand(new SessionCommand("command2", Bundle.EMPTY)) .build(); @@ -393,27 +518,31 @@ public class MediaControllerTest { assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); CommandButton button1Enabled = button1.copyWithIsEnabled(true); - CommandButton button2Enabled = button2.copyWithIsEnabled(true); - assertThat(initialCustomLayout).containsExactly(button1Enabled, button2Enabled).inOrder(); + CommandButton button2Disabled = button2.copyWithIsEnabled(false); + CommandButton button3Disabled = button3.copyWithIsEnabled(false); + CommandButton button4Disabled = button4.copyWithIsEnabled(false); + assertThat(initialCustomLayout).containsExactly(button1Enabled, button2Disabled).inOrder(); assertThat(reportedCustomLayout) .containsExactly( - ImmutableList.of(button1Enabled, button2Enabled), - ImmutableList.of(button3, button4), - ImmutableList.of(button1Enabled, button2Enabled)) + ImmutableList.of(button1Enabled, button2Disabled), + ImmutableList.of(button3Disabled, button4Disabled), + ImmutableList.of(button1Enabled, button2Disabled)) .inOrder(); assertThat(getterCustomLayout) .containsExactly( - ImmutableList.of(button1Enabled, button2Enabled), - ImmutableList.of(button3, button4), - ImmutableList.of(button1Enabled, button2Enabled)) + ImmutableList.of(button1Enabled, button2Disabled), + ImmutableList.of(button3Disabled, button4Disabled), + ImmutableList.of(button1Enabled, button2Disabled)) .inOrder(); assertThat(reportedCustomLayoutChanged) .containsExactly( - ImmutableList.of(button3, button4), ImmutableList.of(button1Enabled, button2Enabled)) + ImmutableList.of(button3Disabled, button4Disabled), + ImmutableList.of(button1Enabled, button2Disabled)) .inOrder(); assertThat(getterCustomLayoutChanged) .containsExactly( - ImmutableList.of(button3, button4), ImmutableList.of(button1Enabled, button2Enabled)) + ImmutableList.of(button3Disabled, button4Disabled), + ImmutableList.of(button1Enabled, button2Disabled)) .inOrder(); session.cleanUp(); } 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 4690f2b80b..ebf8f709ef 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 @@ -319,7 +319,9 @@ public class MediaSessionServiceTest { .isTrue(); assertThat(mediaControllerCompat.getPlaybackState().getActions()) .isEqualTo(PlaybackStateCompat.ACTION_SET_RATING); - assertThat(remoteController.getCustomLayout()).containsExactly(button1, button2).inOrder(); + assertThat(remoteController.getCustomLayout()) + .containsExactly(button1.copyWithIsEnabled(false), button2.copyWithIsEnabled(false)) + .inOrder(); assertThat(initialCustomLayoutInControllerCompat).isEmpty(); assertThat(LegacyConversions.convertToCustomLayout(mediaControllerCompat.getPlaybackState())) .containsExactly(button1.copyWithIsEnabled(true), button3.copyWithIsEnabled(true)) diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java index e3655c5578..172343b52d 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java @@ -207,7 +207,7 @@ public class MediaSessionProviderService extends Service { .add(new SessionCommand("command1", Bundle.EMPTY)) .add(new SessionCommand("command2", Bundle.EMPTY)) .build(), - Player.Commands.EMPTY); + new Player.Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build()); } }); break; @@ -549,7 +549,7 @@ public class MediaSessionProviderService extends Service { () -> { ImmutableList.Builder builder = new ImmutableList.Builder<>(); for (Bundle bundle : layout) { - builder.add(CommandButton.fromBundle(bundle)); + builder.add(CommandButton.fromBundle(bundle, MediaSessionStub.VERSION_INT)); } MediaSession session = sessionMap.get(sessionId); session.setCustomLayout(builder.build()); diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/RemoteMediaController.java b/libraries/test_session_current/src/main/java/androidx/media3/session/RemoteMediaController.java index a470ac7b2b..70bb2bedf3 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/RemoteMediaController.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/RemoteMediaController.java @@ -381,7 +381,7 @@ public class RemoteMediaController { ArrayList list = customLayoutBundle.getParcelableArrayList(KEY_COMMAND_BUTTON_LIST); ImmutableList.Builder customLayout = new ImmutableList.Builder<>(); for (Bundle bundle : list) { - customLayout.add(CommandButton.fromBundle(bundle)); + customLayout.add(CommandButton.fromBundle(bundle, MediaSessionStub.VERSION_INT)); } return customLayout.build(); }