Skip to content

Commit 627b7a3

Browse files
toniheicopybara-github
authored andcommitted
Add media button preferences
This adds the API surface for media button preferences in MediaSession and MediaController. It closely mimics the existing custom layout infrastructure (which it will replace eventually). Compat logic: - Session: - When converting to platform custom actions, prefer to use media button preferences if both are set. - When connecting to an older Media3 controller, send the media button preferences as custom layout instead. - Controller: - Maintain a single resolved media button preferences field. - For Media3 controller receiving both values, prefer media button preferences over custom layouts. Missing functionality: - The conversion from/to custom layout and platform custom actions does not take the slot preferences into account yet. PiperOrigin-RevId: 686950100
1 parent e851a14 commit 627b7a3

34 files changed

+1769
-181
lines changed

libraries/session/src/main/aidl/androidx/media3/session/IMediaController.aidl

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ oneway interface IMediaController {
4949
void onExtrasChanged(int seq, in Bundle extras) = 3011;
5050
void onSessionActivityChanged(int seq, in PendingIntent pendingIntent) = 3013;
5151
void onError(int seq, in Bundle sessionError) = 3014;
52-
// Next Id for MediaController: 3015
52+
void onSetMediaButtonPreferences(int seq, in List<Bundle> commandButtonList) = 3015;
53+
// Next Id for MediaController: 3016
5354

5455
void onChildrenChanged(
5556
int seq, String parentId, int itemCount, in @nullable Bundle libraryParams) = 4000;

libraries/session/src/main/java/androidx/media3/session/ConnectionState.java

+34-4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858

5959
public final ImmutableList<CommandButton> customLayout;
6060

61+
public final ImmutableList<CommandButton> mediaButtonPreferences;
62+
6163
@Nullable public final Token platformToken;
6264

6365
public final ImmutableList<CommandButton> commandButtonsForMediaItems;
@@ -68,6 +70,7 @@ public ConnectionState(
6870
IMediaSession sessionBinder,
6971
@Nullable PendingIntent sessionActivity,
7072
ImmutableList<CommandButton> customLayout,
73+
ImmutableList<CommandButton> mediaButtonPreferences,
7174
ImmutableList<CommandButton> commandButtonsForMediaItems,
7275
SessionCommands sessionCommands,
7376
Player.Commands playerCommandsFromSession,
@@ -81,6 +84,7 @@ public ConnectionState(
8184
this.sessionBinder = sessionBinder;
8285
this.sessionActivity = sessionActivity;
8386
this.customLayout = customLayout;
87+
this.mediaButtonPreferences = mediaButtonPreferences;
8488
this.commandButtonsForMediaItems = commandButtonsForMediaItems;
8589
this.sessionCommands = sessionCommands;
8690
this.playerCommandsFromSession = playerCommandsFromSession;
@@ -95,6 +99,7 @@ public ConnectionState(
9599
private static final String FIELD_SESSION_BINDER = Util.intToStringMaxRadix(1);
96100
private static final String FIELD_SESSION_ACTIVITY = Util.intToStringMaxRadix(2);
97101
private static final String FIELD_CUSTOM_LAYOUT = Util.intToStringMaxRadix(9);
102+
private static final String FIELD_MEDIA_BUTTON_PREFERENCES = Util.intToStringMaxRadix(14);
98103
private static final String FIELD_COMMAND_BUTTONS_FOR_MEDIA_ITEMS = Util.intToStringMaxRadix(13);
99104
private static final String FIELD_SESSION_COMMANDS = Util.intToStringMaxRadix(3);
100105
private static final String FIELD_PLAYER_COMMANDS_FROM_SESSION = Util.intToStringMaxRadix(4);
@@ -106,7 +111,7 @@ public ConnectionState(
106111
private static final String FIELD_IN_PROCESS_BINDER = Util.intToStringMaxRadix(10);
107112
private static final String FIELD_PLATFORM_TOKEN = Util.intToStringMaxRadix(12);
108113

109-
// Next field key = 14
114+
// Next field key = 15
110115

111116
public Bundle toBundleForRemoteProcess(int controllerInterfaceVersion) {
112117
Bundle bundle = new Bundle();
@@ -118,6 +123,21 @@ public Bundle toBundleForRemoteProcess(int controllerInterfaceVersion) {
118123
FIELD_CUSTOM_LAYOUT,
119124
BundleCollectionUtil.toBundleArrayList(customLayout, CommandButton::toBundle));
120125
}
126+
if (!mediaButtonPreferences.isEmpty()) {
127+
if (controllerInterfaceVersion >= 7) {
128+
bundle.putParcelableArrayList(
129+
FIELD_MEDIA_BUTTON_PREFERENCES,
130+
BundleCollectionUtil.toBundleArrayList(
131+
mediaButtonPreferences, CommandButton::toBundle));
132+
} else {
133+
// Controller doesn't support media button preferences, send the list as a custom layout.
134+
// TODO: b/332877990 - More accurately reflect media button preferences as custom layout.
135+
bundle.putParcelableArrayList(
136+
FIELD_CUSTOM_LAYOUT,
137+
BundleCollectionUtil.toBundleArrayList(
138+
mediaButtonPreferences, CommandButton::toBundle));
139+
}
140+
}
121141
if (!commandButtonsForMediaItems.isEmpty()) {
122142
bundle.putParcelableArrayList(
123143
FIELD_COMMAND_BUTTONS_FOR_MEDIA_ITEMS,
@@ -166,11 +186,20 @@ public static ConnectionState fromBundle(Bundle bundle) {
166186
IBinder sessionBinder = checkNotNull(BundleCompat.getBinder(bundle, FIELD_SESSION_BINDER));
167187
@Nullable PendingIntent sessionActivity = bundle.getParcelable(FIELD_SESSION_ACTIVITY);
168188
@Nullable
169-
List<Bundle> commandButtonArrayList = bundle.getParcelableArrayList(FIELD_CUSTOM_LAYOUT);
189+
List<Bundle> customLayoutArrayList = bundle.getParcelableArrayList(FIELD_CUSTOM_LAYOUT);
170190
ImmutableList<CommandButton> customLayout =
171-
commandButtonArrayList != null
191+
customLayoutArrayList != null
192+
? BundleCollectionUtil.fromBundleList(
193+
b -> CommandButton.fromBundle(b, sessionInterfaceVersion), customLayoutArrayList)
194+
: ImmutableList.of();
195+
@Nullable
196+
List<Bundle> mediaButtonPreferencesArrayList =
197+
bundle.getParcelableArrayList(FIELD_MEDIA_BUTTON_PREFERENCES);
198+
ImmutableList<CommandButton> mediaButtonPreferences =
199+
mediaButtonPreferencesArrayList != null
172200
? BundleCollectionUtil.fromBundleList(
173-
b -> CommandButton.fromBundle(b, sessionInterfaceVersion), commandButtonArrayList)
201+
b -> CommandButton.fromBundle(b, sessionInterfaceVersion),
202+
mediaButtonPreferencesArrayList)
174203
: ImmutableList.of();
175204
@Nullable
176205
List<Bundle> commandButtonsForMediaItemsArrayList =
@@ -212,6 +241,7 @@ public static ConnectionState fromBundle(Bundle bundle) {
212241
IMediaSession.Stub.asInterface(sessionBinder),
213242
sessionActivity,
214243
customLayout,
244+
mediaButtonPreferences,
215245
commandButtonsForMediaItems,
216246
sessionCommands,
217247
playerCommandsFromSession,

libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java

+17-17
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
import com.google.common.util.concurrent.ListenableFuture;
5252
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5353
import java.util.Arrays;
54-
import java.util.List;
5554
import java.util.concurrent.CancellationException;
5655
import java.util.concurrent.ExecutionException;
5756
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -299,19 +298,20 @@ private DefaultMediaNotificationProvider(Builder builder) {
299298
@Override
300299
public final MediaNotification createNotification(
301300
MediaSession mediaSession,
302-
ImmutableList<CommandButton> customLayout,
301+
ImmutableList<CommandButton> mediaButtonPreferences,
303302
MediaNotification.ActionFactory actionFactory,
304303
Callback onNotificationChangedCallback) {
305304
ensureNotificationChannel();
306305

307-
ImmutableList.Builder<CommandButton> customLayoutWithEnabledCommandButtonsOnly =
306+
// TODO: b/332877990 - More accurately reflect media button preferences in the notification.
307+
ImmutableList.Builder<CommandButton> mediaButtonPreferencesWithEnabledCommandButtonsOnly =
308308
new ImmutableList.Builder<>();
309-
for (int i = 0; i < customLayout.size(); i++) {
310-
CommandButton button = customLayout.get(i);
309+
for (int i = 0; i < mediaButtonPreferences.size(); i++) {
310+
CommandButton button = mediaButtonPreferences.get(i);
311311
if (button.sessionCommand != null
312312
&& button.sessionCommand.commandCode == SessionCommand.COMMAND_CODE_CUSTOM
313313
&& button.isEnabled) {
314-
customLayoutWithEnabledCommandButtonsOnly.add(customLayout.get(i));
314+
mediaButtonPreferencesWithEnabledCommandButtonsOnly.add(mediaButtonPreferences.get(i));
315315
}
316316
}
317317
Player player = mediaSession.getPlayer();
@@ -325,7 +325,7 @@ public final MediaNotification createNotification(
325325
getMediaButtons(
326326
mediaSession,
327327
player.getAvailableCommands(),
328-
customLayoutWithEnabledCommandButtonsOnly.build(),
328+
mediaButtonPreferencesWithEnabledCommandButtonsOnly.build(),
329329
!Util.shouldShowPlayButton(
330330
player, mediaSession.getShowPlayButtonIfPlaybackIsSuppressed())),
331331
builder,
@@ -424,26 +424,26 @@ public final void setSmallIcon(@DrawableRes int smallIconResourceId) {
424424
* be customized by defining the index of the command in compact view of up to 3 commands in their
425425
* extras with key {@link DefaultMediaNotificationProvider#COMMAND_KEY_COMPACT_VIEW_INDEX}.
426426
*
427-
* <p>To make the custom layout and commands work, you need to {@linkplain
428-
* MediaSession#setCustomLayout(List) set the custom layout of commands} and add the custom
427+
* <p>To make the media button preferences and custom commands work, you need to {@linkplain
428+
* MediaSession#setMediaButtonPreferences set the media button preferences} and add the custom
429429
* commands to the available commands when a controller {@linkplain
430430
* MediaSession.Callback#onConnect(MediaSession, MediaSession.ControllerInfo) connects to the
431-
* session}. Controllers that connect after you called {@link MediaSession#setCustomLayout(List)}
432-
* need the custom command set in {@link MediaSession.Callback#onPostConnect(MediaSession,
433-
* MediaSession.ControllerInfo)} also.
431+
* session}. Controllers that connect after you called {@link
432+
* MediaSession#setMediaButtonPreferences} need the custom command set in {@link
433+
* MediaSession.Callback#onPostConnect(MediaSession, MediaSession.ControllerInfo)} too.
434434
*
435435
* @param session The media session.
436436
* @param playerCommands The available player commands.
437-
* @param customLayout The {@linkplain MediaSession#setCustomLayout(List) custom layout of
438-
* commands}.
437+
* @param mediaButtonPreferences The {@linkplain MediaSession#setMediaButtonPreferences media
438+
* button preferences}.
439439
* @param showPauseButton Whether the notification should show a pause button (e.g., because the
440440
* player is currently playing content), otherwise show a play button to start playback.
441441
* @return The ordered list of command buttons to be placed on the notification.
442442
*/
443443
protected ImmutableList<CommandButton> getMediaButtons(
444444
MediaSession session,
445445
Player.Commands playerCommands,
446-
ImmutableList<CommandButton> customLayout,
446+
ImmutableList<CommandButton> mediaButtonPreferences,
447447
boolean showPauseButton) {
448448
// Skip to previous action.
449449
ImmutableList.Builder<CommandButton> commandButtons = new ImmutableList.Builder<>();
@@ -488,8 +488,8 @@ protected ImmutableList<CommandButton> getMediaButtons(
488488
.setDisplayName(context.getString(R.string.media3_controls_seek_to_next_description))
489489
.build());
490490
}
491-
for (int i = 0; i < customLayout.size(); i++) {
492-
CommandButton button = customLayout.get(i);
491+
for (int i = 0; i < mediaButtonPreferences.size(); i++) {
492+
CommandButton button = mediaButtonPreferences.get(i);
493493
if (button.sessionCommand != null
494494
&& button.sessionCommand.commandCode == SessionCommand.COMMAND_CODE_CUSTOM) {
495495
commandButtons.add(button);

libraries/session/src/main/java/androidx/media3/session/LegacyConversions.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -1495,13 +1495,12 @@ public static SessionCommands convertToSessionCommands(
14951495
}
14961496

14971497
/**
1498-
* Converts {@link CustomAction} in the {@link PlaybackStateCompat} to the custom layout which is
1499-
* the list of the {@link CommandButton}.
1498+
* Converts {@link CustomAction} in the {@link PlaybackStateCompat} to media button preferences.
15001499
*
1501-
* @param state playback state
1502-
* @return custom layout. Always non-null.
1500+
* @param state The {@link PlaybackStateCompat}.
1501+
* @return The media button preferences.
15031502
*/
1504-
public static ImmutableList<CommandButton> convertToCustomLayout(
1503+
public static ImmutableList<CommandButton> convertToMediaButtonPreferences(
15051504
@Nullable PlaybackStateCompat state) {
15061505
if (state == null) {
15071506
return ImmutableList.of();
@@ -1510,7 +1509,7 @@ public static ImmutableList<CommandButton> convertToCustomLayout(
15101509
if (customActions == null) {
15111510
return ImmutableList.of();
15121511
}
1513-
ImmutableList.Builder<CommandButton> layout = new ImmutableList.Builder<>();
1512+
ImmutableList.Builder<CommandButton> mediaButtonPreferences = new ImmutableList.Builder<>();
15141513
for (CustomAction customAction : customActions) {
15151514
String action = customAction.getAction();
15161515
@Nullable Bundle extras = customAction.getExtras();
@@ -1521,15 +1520,16 @@ public static ImmutableList<CommandButton> convertToCustomLayout(
15211520
MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT,
15221521
/* defaultValue= */ CommandButton.ICON_UNDEFINED)
15231522
: CommandButton.ICON_UNDEFINED;
1523+
// TODO: b/332877990 - Set appropriate slots based on available player commands.
15241524
CommandButton button =
15251525
new CommandButton.Builder(icon, customAction.getIcon())
15261526
.setSessionCommand(new SessionCommand(action, extras == null ? Bundle.EMPTY : extras))
15271527
.setDisplayName(customAction.getName())
15281528
.setEnabled(true)
15291529
.build();
1530-
layout.add(button);
1530+
mediaButtonPreferences.add(button);
15311531
}
1532-
return layout.build();
1532+
return mediaButtonPreferences.build();
15331533
}
15341534

15351535
/** Converts {@link AudioAttributesCompat} into {@link AudioAttributes}. */

libraries/session/src/main/java/androidx/media3/session/MediaController.java

+41-2
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,8 @@ default ListenableFuture<SessionResult> onSetCustomLayout(
409409
/**
410410
* Called when the {@linkplain #getCustomLayout() custom layout} changed.
411411
*
412+
* <p>This method will be deprecated, prefer to use {@link #onMediaButtonPreferencesChanged}.
413+
*
412414
* <p>The custom layout can change when either the session {@linkplain
413415
* MediaSession#setCustomLayout changes the custom layout}, or when the session {@linkplain
414416
* MediaSession#setAvailableCommands(MediaSession.ControllerInfo, SessionCommands, Commands)
@@ -424,6 +426,25 @@ default ListenableFuture<SessionResult> onSetCustomLayout(
424426
@UnstableApi
425427
default void onCustomLayoutChanged(MediaController controller, List<CommandButton> layout) {}
426428

429+
/**
430+
* Called when the {@linkplain #getMediaButtonPreferences() media button preferences} changed.
431+
*
432+
* <p>The media button preferences can change when either the session {@linkplain
433+
* MediaSession#setMediaButtonPreferences changes the media button preferences}, or when the
434+
* session {@linkplain MediaSession#setAvailableCommands(MediaSession.ControllerInfo,
435+
* SessionCommands, Commands) changes the available commands} for a controller that affect
436+
* whether buttons of the media button preferences are enabled or disabled.
437+
*
438+
* <p>Note that the {@linkplain CommandButton#isEnabled enabled} flag is set to {@code false} if
439+
* the available commands do not allow to use a button.
440+
*
441+
* @param controller The controller.
442+
* @param mediaButtonPreferences The ordered list of {@linkplain CommandButton command buttons}.
443+
*/
444+
@UnstableApi
445+
default void onMediaButtonPreferencesChanged(
446+
MediaController controller, List<CommandButton> mediaButtonPreferences) {}
447+
427448
/**
428449
* Called when the available session commands are changed by session.
429450
*
@@ -1094,6 +1115,8 @@ public final ListenableFuture<SessionResult> sendCustomCommand(
10941115
/**
10951116
* Returns the custom layout.
10961117
*
1118+
* <p>This method will be deprecated, prefer to use {@link #getMediaButtonPreferences()} instead.
1119+
*
10971120
* <p>After being connected, a change of the custom layout is reported with {@link
10981121
* Listener#onCustomLayoutChanged(MediaController, List)}.
10991122
*
@@ -1104,8 +1127,24 @@ public final ListenableFuture<SessionResult> sendCustomCommand(
11041127
*/
11051128
@UnstableApi
11061129
public final ImmutableList<CommandButton> getCustomLayout() {
1130+
return getMediaButtonPreferences();
1131+
}
1132+
1133+
/**
1134+
* Returns the media button preferences.
1135+
*
1136+
* <p>After being connected, a change of the media button preferences is reported with {@link
1137+
* Listener#onMediaButtonPreferencesChanged(MediaController, List)}.
1138+
*
1139+
* <p>Note that the {@linkplain CommandButton#isEnabled enabled} flag is set to {@code false} if
1140+
* the available commands do not allow to use a button.
1141+
*
1142+
* @return The media button preferences.
1143+
*/
1144+
@UnstableApi
1145+
public final ImmutableList<CommandButton> getMediaButtonPreferences() {
11071146
verifyApplicationThread();
1108-
return isConnected() ? impl.getCustomLayout() : ImmutableList.of();
1147+
return isConnected() ? impl.getMediaButtonPreferences() : ImmutableList.of();
11091148
}
11101149

11111150
/**
@@ -2168,7 +2207,7 @@ private void verifyApplicationThread() {
21682207

21692208
ListenableFuture<SessionResult> sendCustomCommand(SessionCommand command, Bundle args);
21702209

2171-
ImmutableList<CommandButton> getCustomLayout();
2210+
ImmutableList<CommandButton> getMediaButtonPreferences();
21722211

21732212
ImmutableList<CommandButton> getCommandButtonsForMediaItem(MediaItem mediaItem);
21742213

0 commit comments

Comments
 (0)