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(); }