mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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
This commit is contained in:
parent
e851a1419d
commit
627b7a3e56
@ -49,7 +49,8 @@ oneway interface IMediaController {
|
||||
void onExtrasChanged(int seq, in Bundle extras) = 3011;
|
||||
void onSessionActivityChanged(int seq, in PendingIntent pendingIntent) = 3013;
|
||||
void onError(int seq, in Bundle sessionError) = 3014;
|
||||
// Next Id for MediaController: 3015
|
||||
void onSetMediaButtonPreferences(int seq, in List<Bundle> commandButtonList) = 3015;
|
||||
// Next Id for MediaController: 3016
|
||||
|
||||
void onChildrenChanged(
|
||||
int seq, String parentId, int itemCount, in @nullable Bundle libraryParams) = 4000;
|
||||
|
@ -58,6 +58,8 @@ import java.util.List;
|
||||
|
||||
public final ImmutableList<CommandButton> customLayout;
|
||||
|
||||
public final ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
|
||||
@Nullable public final Token platformToken;
|
||||
|
||||
public final ImmutableList<CommandButton> commandButtonsForMediaItems;
|
||||
@ -68,6 +70,7 @@ import java.util.List;
|
||||
IMediaSession sessionBinder,
|
||||
@Nullable PendingIntent sessionActivity,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
ImmutableList<CommandButton> commandButtonsForMediaItems,
|
||||
SessionCommands sessionCommands,
|
||||
Player.Commands playerCommandsFromSession,
|
||||
@ -81,6 +84,7 @@ import java.util.List;
|
||||
this.sessionBinder = sessionBinder;
|
||||
this.sessionActivity = sessionActivity;
|
||||
this.customLayout = customLayout;
|
||||
this.mediaButtonPreferences = mediaButtonPreferences;
|
||||
this.commandButtonsForMediaItems = commandButtonsForMediaItems;
|
||||
this.sessionCommands = sessionCommands;
|
||||
this.playerCommandsFromSession = playerCommandsFromSession;
|
||||
@ -95,6 +99,7 @@ import java.util.List;
|
||||
private static final String FIELD_SESSION_BINDER = Util.intToStringMaxRadix(1);
|
||||
private static final String FIELD_SESSION_ACTIVITY = Util.intToStringMaxRadix(2);
|
||||
private static final String FIELD_CUSTOM_LAYOUT = Util.intToStringMaxRadix(9);
|
||||
private static final String FIELD_MEDIA_BUTTON_PREFERENCES = Util.intToStringMaxRadix(14);
|
||||
private static final String FIELD_COMMAND_BUTTONS_FOR_MEDIA_ITEMS = Util.intToStringMaxRadix(13);
|
||||
private static final String FIELD_SESSION_COMMANDS = Util.intToStringMaxRadix(3);
|
||||
private static final String FIELD_PLAYER_COMMANDS_FROM_SESSION = Util.intToStringMaxRadix(4);
|
||||
@ -106,7 +111,7 @@ import java.util.List;
|
||||
private static final String FIELD_IN_PROCESS_BINDER = Util.intToStringMaxRadix(10);
|
||||
private static final String FIELD_PLATFORM_TOKEN = Util.intToStringMaxRadix(12);
|
||||
|
||||
// Next field key = 14
|
||||
// Next field key = 15
|
||||
|
||||
public Bundle toBundleForRemoteProcess(int controllerInterfaceVersion) {
|
||||
Bundle bundle = new Bundle();
|
||||
@ -118,6 +123,21 @@ import java.util.List;
|
||||
FIELD_CUSTOM_LAYOUT,
|
||||
BundleCollectionUtil.toBundleArrayList(customLayout, CommandButton::toBundle));
|
||||
}
|
||||
if (!mediaButtonPreferences.isEmpty()) {
|
||||
if (controllerInterfaceVersion >= 7) {
|
||||
bundle.putParcelableArrayList(
|
||||
FIELD_MEDIA_BUTTON_PREFERENCES,
|
||||
BundleCollectionUtil.toBundleArrayList(
|
||||
mediaButtonPreferences, CommandButton::toBundle));
|
||||
} else {
|
||||
// Controller doesn't support media button preferences, send the list as a custom layout.
|
||||
// TODO: b/332877990 - More accurately reflect media button preferences as custom layout.
|
||||
bundle.putParcelableArrayList(
|
||||
FIELD_CUSTOM_LAYOUT,
|
||||
BundleCollectionUtil.toBundleArrayList(
|
||||
mediaButtonPreferences, CommandButton::toBundle));
|
||||
}
|
||||
}
|
||||
if (!commandButtonsForMediaItems.isEmpty()) {
|
||||
bundle.putParcelableArrayList(
|
||||
FIELD_COMMAND_BUTTONS_FOR_MEDIA_ITEMS,
|
||||
@ -166,11 +186,20 @@ import java.util.List;
|
||||
IBinder sessionBinder = checkNotNull(BundleCompat.getBinder(bundle, FIELD_SESSION_BINDER));
|
||||
@Nullable PendingIntent sessionActivity = bundle.getParcelable(FIELD_SESSION_ACTIVITY);
|
||||
@Nullable
|
||||
List<Bundle> commandButtonArrayList = bundle.getParcelableArrayList(FIELD_CUSTOM_LAYOUT);
|
||||
List<Bundle> customLayoutArrayList = bundle.getParcelableArrayList(FIELD_CUSTOM_LAYOUT);
|
||||
ImmutableList<CommandButton> customLayout =
|
||||
commandButtonArrayList != null
|
||||
customLayoutArrayList != null
|
||||
? BundleCollectionUtil.fromBundleList(
|
||||
b -> CommandButton.fromBundle(b, sessionInterfaceVersion), commandButtonArrayList)
|
||||
b -> CommandButton.fromBundle(b, sessionInterfaceVersion), customLayoutArrayList)
|
||||
: ImmutableList.of();
|
||||
@Nullable
|
||||
List<Bundle> mediaButtonPreferencesArrayList =
|
||||
bundle.getParcelableArrayList(FIELD_MEDIA_BUTTON_PREFERENCES);
|
||||
ImmutableList<CommandButton> mediaButtonPreferences =
|
||||
mediaButtonPreferencesArrayList != null
|
||||
? BundleCollectionUtil.fromBundleList(
|
||||
b -> CommandButton.fromBundle(b, sessionInterfaceVersion),
|
||||
mediaButtonPreferencesArrayList)
|
||||
: ImmutableList.of();
|
||||
@Nullable
|
||||
List<Bundle> commandButtonsForMediaItemsArrayList =
|
||||
@ -212,6 +241,7 @@ import java.util.List;
|
||||
IMediaSession.Stub.asInterface(sessionBinder),
|
||||
sessionActivity,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
commandButtonsForMediaItems,
|
||||
sessionCommands,
|
||||
playerCommandsFromSession,
|
||||
|
@ -51,7 +51,6 @@ import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@ -299,19 +298,20 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
@Override
|
||||
public final MediaNotification createNotification(
|
||||
MediaSession mediaSession,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
MediaNotification.ActionFactory actionFactory,
|
||||
Callback onNotificationChangedCallback) {
|
||||
ensureNotificationChannel();
|
||||
|
||||
ImmutableList.Builder<CommandButton> customLayoutWithEnabledCommandButtonsOnly =
|
||||
// TODO: b/332877990 - More accurately reflect media button preferences in the notification.
|
||||
ImmutableList.Builder<CommandButton> mediaButtonPreferencesWithEnabledCommandButtonsOnly =
|
||||
new ImmutableList.Builder<>();
|
||||
for (int i = 0; i < customLayout.size(); i++) {
|
||||
CommandButton button = customLayout.get(i);
|
||||
for (int i = 0; i < mediaButtonPreferences.size(); i++) {
|
||||
CommandButton button = mediaButtonPreferences.get(i);
|
||||
if (button.sessionCommand != null
|
||||
&& button.sessionCommand.commandCode == SessionCommand.COMMAND_CODE_CUSTOM
|
||||
&& button.isEnabled) {
|
||||
customLayoutWithEnabledCommandButtonsOnly.add(customLayout.get(i));
|
||||
mediaButtonPreferencesWithEnabledCommandButtonsOnly.add(mediaButtonPreferences.get(i));
|
||||
}
|
||||
}
|
||||
Player player = mediaSession.getPlayer();
|
||||
@ -325,7 +325,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
getMediaButtons(
|
||||
mediaSession,
|
||||
player.getAvailableCommands(),
|
||||
customLayoutWithEnabledCommandButtonsOnly.build(),
|
||||
mediaButtonPreferencesWithEnabledCommandButtonsOnly.build(),
|
||||
!Util.shouldShowPlayButton(
|
||||
player, mediaSession.getShowPlayButtonIfPlaybackIsSuppressed())),
|
||||
builder,
|
||||
@ -424,18 +424,18 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
* be customized by defining the index of the command in compact view of up to 3 commands in their
|
||||
* extras with key {@link DefaultMediaNotificationProvider#COMMAND_KEY_COMPACT_VIEW_INDEX}.
|
||||
*
|
||||
* <p>To make the custom layout and commands work, you need to {@linkplain
|
||||
* MediaSession#setCustomLayout(List) set the custom layout of commands} and add the custom
|
||||
* <p>To make the media button preferences and custom commands work, you need to {@linkplain
|
||||
* MediaSession#setMediaButtonPreferences set the media button preferences} and add the custom
|
||||
* commands to the available commands when a controller {@linkplain
|
||||
* MediaSession.Callback#onConnect(MediaSession, MediaSession.ControllerInfo) connects to the
|
||||
* session}. Controllers that connect after you called {@link MediaSession#setCustomLayout(List)}
|
||||
* need the custom command set in {@link MediaSession.Callback#onPostConnect(MediaSession,
|
||||
* MediaSession.ControllerInfo)} also.
|
||||
* session}. Controllers that connect after you called {@link
|
||||
* MediaSession#setMediaButtonPreferences} need the custom command set in {@link
|
||||
* MediaSession.Callback#onPostConnect(MediaSession, MediaSession.ControllerInfo)} too.
|
||||
*
|
||||
* @param session The media session.
|
||||
* @param playerCommands The available player commands.
|
||||
* @param customLayout The {@linkplain MediaSession#setCustomLayout(List) custom layout of
|
||||
* commands}.
|
||||
* @param mediaButtonPreferences The {@linkplain MediaSession#setMediaButtonPreferences media
|
||||
* button preferences}.
|
||||
* @param showPauseButton Whether the notification should show a pause button (e.g., because the
|
||||
* player is currently playing content), otherwise show a play button to start playback.
|
||||
* @return The ordered list of command buttons to be placed on the notification.
|
||||
@ -443,7 +443,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
protected ImmutableList<CommandButton> getMediaButtons(
|
||||
MediaSession session,
|
||||
Player.Commands playerCommands,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
boolean showPauseButton) {
|
||||
// Skip to previous action.
|
||||
ImmutableList.Builder<CommandButton> commandButtons = new ImmutableList.Builder<>();
|
||||
@ -488,8 +488,8 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi
|
||||
.setDisplayName(context.getString(R.string.media3_controls_seek_to_next_description))
|
||||
.build());
|
||||
}
|
||||
for (int i = 0; i < customLayout.size(); i++) {
|
||||
CommandButton button = customLayout.get(i);
|
||||
for (int i = 0; i < mediaButtonPreferences.size(); i++) {
|
||||
CommandButton button = mediaButtonPreferences.get(i);
|
||||
if (button.sessionCommand != null
|
||||
&& button.sessionCommand.commandCode == SessionCommand.COMMAND_CODE_CUSTOM) {
|
||||
commandButtons.add(button);
|
||||
|
@ -1495,13 +1495,12 @@ import java.util.concurrent.TimeoutException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts {@link CustomAction} in the {@link PlaybackStateCompat} to the custom layout which is
|
||||
* the list of the {@link CommandButton}.
|
||||
* Converts {@link CustomAction} in the {@link PlaybackStateCompat} to media button preferences.
|
||||
*
|
||||
* @param state playback state
|
||||
* @return custom layout. Always non-null.
|
||||
* @param state The {@link PlaybackStateCompat}.
|
||||
* @return The media button preferences.
|
||||
*/
|
||||
public static ImmutableList<CommandButton> convertToCustomLayout(
|
||||
public static ImmutableList<CommandButton> convertToMediaButtonPreferences(
|
||||
@Nullable PlaybackStateCompat state) {
|
||||
if (state == null) {
|
||||
return ImmutableList.of();
|
||||
@ -1510,7 +1509,7 @@ import java.util.concurrent.TimeoutException;
|
||||
if (customActions == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
ImmutableList.Builder<CommandButton> layout = new ImmutableList.Builder<>();
|
||||
ImmutableList.Builder<CommandButton> mediaButtonPreferences = new ImmutableList.Builder<>();
|
||||
for (CustomAction customAction : customActions) {
|
||||
String action = customAction.getAction();
|
||||
@Nullable Bundle extras = customAction.getExtras();
|
||||
@ -1521,15 +1520,16 @@ import java.util.concurrent.TimeoutException;
|
||||
MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT,
|
||||
/* defaultValue= */ CommandButton.ICON_UNDEFINED)
|
||||
: CommandButton.ICON_UNDEFINED;
|
||||
// TODO: b/332877990 - Set appropriate slots based on available player commands.
|
||||
CommandButton button =
|
||||
new CommandButton.Builder(icon, customAction.getIcon())
|
||||
.setSessionCommand(new SessionCommand(action, extras == null ? Bundle.EMPTY : extras))
|
||||
.setDisplayName(customAction.getName())
|
||||
.setEnabled(true)
|
||||
.build();
|
||||
layout.add(button);
|
||||
mediaButtonPreferences.add(button);
|
||||
}
|
||||
return layout.build();
|
||||
return mediaButtonPreferences.build();
|
||||
}
|
||||
|
||||
/** Converts {@link AudioAttributesCompat} into {@link AudioAttributes}. */
|
||||
|
@ -409,6 +409,8 @@ public class MediaController implements Player {
|
||||
/**
|
||||
* Called when the {@linkplain #getCustomLayout() custom layout} changed.
|
||||
*
|
||||
* <p>This method will be deprecated, prefer to use {@link #onMediaButtonPreferencesChanged}.
|
||||
*
|
||||
* <p>The custom layout can change when either the session {@linkplain
|
||||
* MediaSession#setCustomLayout changes the custom layout}, or when the session {@linkplain
|
||||
* MediaSession#setAvailableCommands(MediaSession.ControllerInfo, SessionCommands, Commands)
|
||||
@ -424,6 +426,25 @@ public class MediaController implements Player {
|
||||
@UnstableApi
|
||||
default void onCustomLayoutChanged(MediaController controller, List<CommandButton> layout) {}
|
||||
|
||||
/**
|
||||
* Called when the {@linkplain #getMediaButtonPreferences() media button preferences} changed.
|
||||
*
|
||||
* <p>The media button preferences can change when either the session {@linkplain
|
||||
* MediaSession#setMediaButtonPreferences changes the media button preferences}, or when the
|
||||
* session {@linkplain MediaSession#setAvailableCommands(MediaSession.ControllerInfo,
|
||||
* SessionCommands, Commands) changes the available commands} for a controller that affect
|
||||
* whether buttons of the media button preferences are enabled or disabled.
|
||||
*
|
||||
* <p>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 mediaButtonPreferences The ordered list of {@linkplain CommandButton command buttons}.
|
||||
*/
|
||||
@UnstableApi
|
||||
default void onMediaButtonPreferencesChanged(
|
||||
MediaController controller, List<CommandButton> mediaButtonPreferences) {}
|
||||
|
||||
/**
|
||||
* Called when the available session commands are changed by session.
|
||||
*
|
||||
@ -1094,6 +1115,8 @@ public class MediaController implements Player {
|
||||
/**
|
||||
* Returns the custom layout.
|
||||
*
|
||||
* <p>This method will be deprecated, prefer to use {@link #getMediaButtonPreferences()} instead.
|
||||
*
|
||||
* <p>After being connected, a change of the custom layout is reported with {@link
|
||||
* Listener#onCustomLayoutChanged(MediaController, List)}.
|
||||
*
|
||||
@ -1104,8 +1127,24 @@ public class MediaController implements Player {
|
||||
*/
|
||||
@UnstableApi
|
||||
public final ImmutableList<CommandButton> getCustomLayout() {
|
||||
return getMediaButtonPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media button preferences.
|
||||
*
|
||||
* <p>After being connected, a change of the media button preferences is reported with {@link
|
||||
* Listener#onMediaButtonPreferencesChanged(MediaController, List)}.
|
||||
*
|
||||
* <p>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 media button preferences.
|
||||
*/
|
||||
@UnstableApi
|
||||
public final ImmutableList<CommandButton> getMediaButtonPreferences() {
|
||||
verifyApplicationThread();
|
||||
return isConnected() ? impl.getCustomLayout() : ImmutableList.of();
|
||||
return isConnected() ? impl.getMediaButtonPreferences() : ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2168,7 +2207,7 @@ public class MediaController implements Player {
|
||||
|
||||
ListenableFuture<SessionResult> sendCustomCommand(SessionCommand command, Bundle args);
|
||||
|
||||
ImmutableList<CommandButton> getCustomLayout();
|
||||
ImmutableList<CommandButton> getMediaButtonPreferences();
|
||||
|
||||
ImmutableList<CommandButton> getCommandButtonsForMediaItem(MediaItem mediaItem);
|
||||
|
||||
|
@ -126,7 +126,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
private PlayerInfo playerInfo;
|
||||
@Nullable private PendingIntent sessionActivity;
|
||||
private ImmutableList<CommandButton> customLayoutOriginal;
|
||||
private ImmutableList<CommandButton> customLayoutWithUnavailableButtonsDisabled;
|
||||
private ImmutableList<CommandButton> mediaButtonPreferencesOriginal;
|
||||
private ImmutableList<CommandButton> resolvedMediaButtonPreferences;
|
||||
private ImmutableMap<String, CommandButton> commandButtonsForMediaItemsMap;
|
||||
private SessionCommands sessionCommands;
|
||||
private Commands playerCommandsFromSession;
|
||||
@ -155,7 +156,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
surfaceSize = Size.UNKNOWN;
|
||||
sessionCommands = SessionCommands.EMPTY;
|
||||
customLayoutOriginal = ImmutableList.of();
|
||||
customLayoutWithUnavailableButtonsDisabled = ImmutableList.of();
|
||||
mediaButtonPreferencesOriginal = ImmutableList.of();
|
||||
resolvedMediaButtonPreferences = ImmutableList.of();
|
||||
commandButtonsForMediaItemsMap = ImmutableMap.of();
|
||||
playerCommandsFromSession = Commands.EMPTY;
|
||||
playerCommandsFromPlayer = Commands.EMPTY;
|
||||
@ -745,8 +747,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<CommandButton> getCustomLayout() {
|
||||
return customLayoutWithUnavailableButtonsDisabled;
|
||||
public ImmutableList<CommandButton> getMediaButtonPreferences() {
|
||||
return resolvedMediaButtonPreferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -2652,9 +2654,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
createIntersectedCommandsEnsuringCommandReleaseAvailable(
|
||||
playerCommandsFromSession, playerCommandsFromPlayer);
|
||||
customLayoutOriginal = result.customLayout;
|
||||
customLayoutWithUnavailableButtonsDisabled =
|
||||
CommandButton.copyWithUnavailableButtonsDisabled(
|
||||
result.customLayout, sessionCommands, intersectedPlayerCommands);
|
||||
mediaButtonPreferencesOriginal = result.mediaButtonPreferences;
|
||||
resolvedMediaButtonPreferences =
|
||||
resolveMediaButtonPreferences(
|
||||
mediaButtonPreferencesOriginal,
|
||||
customLayoutOriginal,
|
||||
sessionCommands,
|
||||
intersectedPlayerCommands);
|
||||
ImmutableMap.Builder<String, CommandButton> commandButtonsForMediaItems =
|
||||
new ImmutableMap.Builder<>();
|
||||
for (int i = 0; i < result.commandButtonsForMediaItems.size(); i++) {
|
||||
@ -2834,13 +2840,17 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
intersectedPlayerCommandsChanged =
|
||||
!Util.areEqual(intersectedPlayerCommands, prevIntersectedPlayerCommands);
|
||||
}
|
||||
boolean customLayoutChanged = false;
|
||||
boolean mediaButtonPreferencesChanged = false;
|
||||
if (sessionCommandsChanged || intersectedPlayerCommandsChanged) {
|
||||
ImmutableList<CommandButton> oldCustomLayout = customLayoutWithUnavailableButtonsDisabled;
|
||||
customLayoutWithUnavailableButtonsDisabled =
|
||||
CommandButton.copyWithUnavailableButtonsDisabled(
|
||||
customLayoutOriginal, sessionCommands, intersectedPlayerCommands);
|
||||
customLayoutChanged = !customLayoutWithUnavailableButtonsDisabled.equals(oldCustomLayout);
|
||||
ImmutableList<CommandButton> oldMediaButtonPreferences = resolvedMediaButtonPreferences;
|
||||
resolvedMediaButtonPreferences =
|
||||
resolveMediaButtonPreferences(
|
||||
mediaButtonPreferencesOriginal,
|
||||
customLayoutOriginal,
|
||||
sessionCommands,
|
||||
intersectedPlayerCommands);
|
||||
mediaButtonPreferencesChanged =
|
||||
!resolvedMediaButtonPreferences.equals(oldMediaButtonPreferences);
|
||||
}
|
||||
if (intersectedPlayerCommandsChanged) {
|
||||
listeners.sendEvent(
|
||||
@ -2853,12 +2863,14 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
listener ->
|
||||
listener.onAvailableSessionCommandsChanged(getInstance(), sessionCommands));
|
||||
}
|
||||
if (customLayoutChanged) {
|
||||
if (mediaButtonPreferencesChanged) {
|
||||
getInstance()
|
||||
.notifyControllerListener(
|
||||
listener ->
|
||||
listener.onCustomLayoutChanged(
|
||||
getInstance(), customLayoutWithUnavailableButtonsDisabled));
|
||||
listener -> {
|
||||
listener.onCustomLayoutChanged(getInstance(), resolvedMediaButtonPreferences);
|
||||
listener.onMediaButtonPreferencesChanged(
|
||||
getInstance(), resolvedMediaButtonPreferences);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -2876,50 +2888,84 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
playerCommandsFromSession, playerCommandsFromPlayer);
|
||||
boolean intersectedPlayerCommandsChanged =
|
||||
!Util.areEqual(intersectedPlayerCommands, prevIntersectedPlayerCommands);
|
||||
boolean customLayoutChanged = false;
|
||||
boolean mediaButtonPreferencesChanged = false;
|
||||
if (intersectedPlayerCommandsChanged) {
|
||||
ImmutableList<CommandButton> oldCustomLayout = customLayoutWithUnavailableButtonsDisabled;
|
||||
customLayoutWithUnavailableButtonsDisabled =
|
||||
CommandButton.copyWithUnavailableButtonsDisabled(
|
||||
customLayoutOriginal, sessionCommands, intersectedPlayerCommands);
|
||||
customLayoutChanged = !customLayoutWithUnavailableButtonsDisabled.equals(oldCustomLayout);
|
||||
ImmutableList<CommandButton> oldMediaButtonPreferences = resolvedMediaButtonPreferences;
|
||||
resolvedMediaButtonPreferences =
|
||||
resolveMediaButtonPreferences(
|
||||
mediaButtonPreferencesOriginal,
|
||||
customLayoutOriginal,
|
||||
sessionCommands,
|
||||
intersectedPlayerCommands);
|
||||
mediaButtonPreferencesChanged =
|
||||
!resolvedMediaButtonPreferences.equals(oldMediaButtonPreferences);
|
||||
listeners.sendEvent(
|
||||
/* eventFlag= */ Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
|
||||
listener -> listener.onAvailableCommandsChanged(intersectedPlayerCommands));
|
||||
}
|
||||
if (customLayoutChanged) {
|
||||
if (mediaButtonPreferencesChanged) {
|
||||
getInstance()
|
||||
.notifyControllerListener(
|
||||
listener ->
|
||||
listener.onCustomLayoutChanged(
|
||||
getInstance(), customLayoutWithUnavailableButtonsDisabled));
|
||||
listener -> {
|
||||
listener.onCustomLayoutChanged(getInstance(), resolvedMediaButtonPreferences);
|
||||
listener.onMediaButtonPreferencesChanged(
|
||||
getInstance(), resolvedMediaButtonPreferences);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Calling deprecated listener callback method for backwards compatibility.
|
||||
@SuppressWarnings("deprecation")
|
||||
void onSetCustomLayout(int seq, List<CommandButton> layout) {
|
||||
if (!isConnected()) {
|
||||
return;
|
||||
}
|
||||
ImmutableList<CommandButton> oldCustomLayout = customLayoutWithUnavailableButtonsDisabled;
|
||||
ImmutableList<CommandButton> oldMediaButtonPreferences = resolvedMediaButtonPreferences;
|
||||
customLayoutOriginal = ImmutableList.copyOf(layout);
|
||||
customLayoutWithUnavailableButtonsDisabled =
|
||||
CommandButton.copyWithUnavailableButtonsDisabled(
|
||||
layout, sessionCommands, intersectedPlayerCommands);
|
||||
boolean hasCustomLayoutChanged =
|
||||
!Objects.equals(customLayoutWithUnavailableButtonsDisabled, oldCustomLayout);
|
||||
resolvedMediaButtonPreferences =
|
||||
resolveMediaButtonPreferences(
|
||||
mediaButtonPreferencesOriginal, layout, sessionCommands, intersectedPlayerCommands);
|
||||
boolean mediaButtonPreferencesChanged =
|
||||
!Objects.equals(resolvedMediaButtonPreferences, oldMediaButtonPreferences);
|
||||
getInstance()
|
||||
.notifyControllerListener(
|
||||
listener -> {
|
||||
ListenableFuture<SessionResult> future =
|
||||
checkNotNull(
|
||||
listener.onSetCustomLayout(
|
||||
getInstance(), customLayoutWithUnavailableButtonsDisabled),
|
||||
listener.onSetCustomLayout(getInstance(), resolvedMediaButtonPreferences),
|
||||
"MediaController.Listener#onSetCustomLayout() must not return null");
|
||||
if (hasCustomLayoutChanged) {
|
||||
listener.onCustomLayoutChanged(
|
||||
getInstance(), customLayoutWithUnavailableButtonsDisabled);
|
||||
if (mediaButtonPreferencesChanged) {
|
||||
listener.onCustomLayoutChanged(getInstance(), resolvedMediaButtonPreferences);
|
||||
listener.onMediaButtonPreferencesChanged(
|
||||
getInstance(), resolvedMediaButtonPreferences);
|
||||
}
|
||||
sendControllerResultWhenReady(seq, future);
|
||||
});
|
||||
}
|
||||
|
||||
void onSetMediaButtonPreferences(int seq, List<CommandButton> mediaButtonPreferences) {
|
||||
if (!isConnected()) {
|
||||
return;
|
||||
}
|
||||
ImmutableList<CommandButton> oldMediaButtonPreferences = resolvedMediaButtonPreferences;
|
||||
mediaButtonPreferencesOriginal = ImmutableList.copyOf(mediaButtonPreferences);
|
||||
resolvedMediaButtonPreferences =
|
||||
resolveMediaButtonPreferences(
|
||||
mediaButtonPreferences,
|
||||
customLayoutOriginal,
|
||||
sessionCommands,
|
||||
intersectedPlayerCommands);
|
||||
boolean mediaButtonPreferencesChanged =
|
||||
!Objects.equals(resolvedMediaButtonPreferences, oldMediaButtonPreferences);
|
||||
getInstance()
|
||||
.notifyControllerListener(
|
||||
listener -> {
|
||||
ListenableFuture<SessionResult> future =
|
||||
checkNotNull(
|
||||
listener.onSetCustomLayout(getInstance(), resolvedMediaButtonPreferences),
|
||||
"MediaController.Listener#onSetCustomLayout() must not return null");
|
||||
if (mediaButtonPreferencesChanged) {
|
||||
listener.onCustomLayoutChanged(getInstance(), resolvedMediaButtonPreferences);
|
||||
listener.onMediaButtonPreferencesChanged(
|
||||
getInstance(), resolvedMediaButtonPreferences);
|
||||
}
|
||||
sendControllerResultWhenReady(seq, future);
|
||||
});
|
||||
@ -3277,6 +3323,18 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
return newMediaItemIndex;
|
||||
}
|
||||
|
||||
private static ImmutableList<CommandButton> resolveMediaButtonPreferences(
|
||||
List<CommandButton> mediaButtonPreferences,
|
||||
List<CommandButton> customLayout,
|
||||
SessionCommands sessionCommands,
|
||||
Player.Commands playerCommands) {
|
||||
// TODO: b/332877990 - When using custom layout, set correct slots based on available commands.
|
||||
return CommandButton.copyWithUnavailableButtonsDisabled(
|
||||
mediaButtonPreferences.isEmpty() ? customLayout : mediaButtonPreferences,
|
||||
sessionCommands,
|
||||
playerCommands);
|
||||
}
|
||||
|
||||
private static Commands createIntersectedCommandsEnsuringCommandReleaseAvailable(
|
||||
Commands commandFromSession, Commands commandsFromPlayer) {
|
||||
Commands intersectedCommands = MediaUtils.intersect(commandFromSession, commandsFromPlayer);
|
||||
|
@ -202,7 +202,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
maskedPlayerInfo,
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -268,7 +268,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
/* playerError= */ null),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -391,7 +391,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
maskedPlayerInfo,
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -432,8 +432,8 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<CommandButton> getCustomLayout() {
|
||||
return controllerInfo.customLayout;
|
||||
public ImmutableList<CommandButton> getMediaButtonPreferences() {
|
||||
return controllerInfo.mediaButtonPreferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -556,7 +556,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo.copyWithPlaybackParameters(playbackParameters),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -577,7 +577,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo.copyWithPlaybackParameters(new PlaybackParameters(speed)),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -671,7 +671,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
maskedPlayerInfo,
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -736,7 +736,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
maskedPlayerInfo,
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -790,7 +790,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
maskedPlayerInfo,
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -858,7 +858,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
maskedPlayerInfo,
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -972,7 +972,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo.copyWithRepeatMode(repeatMode),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -1000,7 +1000,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo.copyWithShuffleModeEnabled(shuffleModeEnabled),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -1137,7 +1137,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo.copyWithDeviceVolume(volume, isDeviceMuted),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -1170,7 +1170,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo.copyWithDeviceVolume(volume + 1, isDeviceMuted),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -1202,7 +1202,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo.copyWithDeviceVolume(volume - 1, isDeviceMuted),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -1237,7 +1237,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo.copyWithDeviceVolume(volume, muted),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -1276,7 +1276,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
Player.PLAYBACK_SUPPRESSION_REASON_NONE),
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
controllerInfo.sessionExtras,
|
||||
/* sessionError= */ null);
|
||||
updateStateMaskedControllerInfo(
|
||||
@ -1625,13 +1625,18 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
|
||||
if (notifyConnected) {
|
||||
getInstance().notifyAccepted();
|
||||
if (!oldControllerInfo.customLayout.equals(newControllerInfo.customLayout)) {
|
||||
if (!oldControllerInfo.mediaButtonPreferences.equals(
|
||||
newControllerInfo.mediaButtonPreferences)) {
|
||||
getInstance()
|
||||
.notifyControllerListener(
|
||||
listener -> {
|
||||
ignoreFuture(
|
||||
listener.onSetCustomLayout(getInstance(), newControllerInfo.customLayout));
|
||||
listener.onCustomLayoutChanged(getInstance(), newControllerInfo.customLayout);
|
||||
listener.onSetCustomLayout(
|
||||
getInstance(), newControllerInfo.mediaButtonPreferences));
|
||||
listener.onCustomLayoutChanged(
|
||||
getInstance(), newControllerInfo.mediaButtonPreferences);
|
||||
listener.onMediaButtonPreferencesChanged(
|
||||
getInstance(), newControllerInfo.mediaButtonPreferences);
|
||||
});
|
||||
}
|
||||
return;
|
||||
@ -1758,13 +1763,18 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
listener.onAvailableSessionCommandsChanged(
|
||||
getInstance(), newControllerInfo.availableSessionCommands));
|
||||
}
|
||||
if (!oldControllerInfo.customLayout.equals(newControllerInfo.customLayout)) {
|
||||
if (!oldControllerInfo.mediaButtonPreferences.equals(
|
||||
newControllerInfo.mediaButtonPreferences)) {
|
||||
getInstance()
|
||||
.notifyControllerListener(
|
||||
listener -> {
|
||||
ignoreFuture(
|
||||
listener.onSetCustomLayout(getInstance(), newControllerInfo.customLayout));
|
||||
listener.onCustomLayoutChanged(getInstance(), newControllerInfo.customLayout);
|
||||
listener.onSetCustomLayout(
|
||||
getInstance(), newControllerInfo.mediaButtonPreferences));
|
||||
listener.onCustomLayoutChanged(
|
||||
getInstance(), newControllerInfo.mediaButtonPreferences);
|
||||
listener.onMediaButtonPreferencesChanged(
|
||||
getInstance(), newControllerInfo.mediaButtonPreferences);
|
||||
});
|
||||
}
|
||||
if (newControllerInfo.sessionError != null) {
|
||||
@ -1913,7 +1923,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
controllerInfo.playerInfo,
|
||||
controllerInfo.availableSessionCommands,
|
||||
controllerInfo.availablePlayerCommands,
|
||||
controllerInfo.customLayout,
|
||||
controllerInfo.mediaButtonPreferences,
|
||||
extras,
|
||||
/* sessionError= */ null);
|
||||
getInstance()
|
||||
@ -1988,7 +1998,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
boolean shuffleModeEnabled;
|
||||
SessionCommands availableSessionCommands;
|
||||
Commands availablePlayerCommands;
|
||||
ImmutableList<CommandButton> customLayout;
|
||||
ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
|
||||
boolean isQueueChanged = oldLegacyPlayerInfo.queue != newLegacyPlayerInfo.queue;
|
||||
currentTimeline =
|
||||
@ -2074,11 +2084,12 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
availableSessionCommands =
|
||||
LegacyConversions.convertToSessionCommands(
|
||||
newLegacyPlayerInfo.playbackStateCompat, isSessionReady);
|
||||
customLayout =
|
||||
LegacyConversions.convertToCustomLayout(newLegacyPlayerInfo.playbackStateCompat);
|
||||
mediaButtonPreferences =
|
||||
LegacyConversions.convertToMediaButtonPreferences(
|
||||
newLegacyPlayerInfo.playbackStateCompat);
|
||||
} else {
|
||||
availableSessionCommands = oldControllerInfo.availableSessionCommands;
|
||||
customLayout = oldControllerInfo.customLayout;
|
||||
mediaButtonPreferences = oldControllerInfo.mediaButtonPreferences;
|
||||
}
|
||||
// Note: Sets the available player command here although it can be obtained before session is
|
||||
// ready. It's to follow the decision on MediaController to disallow any commands before
|
||||
@ -2164,7 +2175,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
shuffleModeEnabled,
|
||||
availableSessionCommands,
|
||||
availablePlayerCommands,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
newLegacyPlayerInfo.sessionExtras,
|
||||
playerError,
|
||||
sessionError,
|
||||
@ -2334,7 +2345,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
boolean shuffleModeEnabled,
|
||||
SessionCommands availableSessionCommands,
|
||||
Commands availablePlayerCommands,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
Bundle sessionExtras,
|
||||
@Nullable PlaybackException playerError,
|
||||
@Nullable SessionError sessionError,
|
||||
@ -2411,7 +2422,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
playerInfo,
|
||||
availableSessionCommands,
|
||||
availablePlayerCommands,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
sessionExtras,
|
||||
sessionError);
|
||||
}
|
||||
@ -2622,7 +2633,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
public final PlayerInfo playerInfo;
|
||||
public final SessionCommands availableSessionCommands;
|
||||
public final Commands availablePlayerCommands;
|
||||
public final ImmutableList<CommandButton> customLayout;
|
||||
public final ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
public final Bundle sessionExtras;
|
||||
@Nullable public final SessionError sessionError;
|
||||
|
||||
@ -2630,7 +2641,7 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
playerInfo = PlayerInfo.DEFAULT.copyWithTimeline(QueueTimeline.DEFAULT);
|
||||
availableSessionCommands = SessionCommands.EMPTY;
|
||||
availablePlayerCommands = Commands.EMPTY;
|
||||
customLayout = ImmutableList.of();
|
||||
mediaButtonPreferences = ImmutableList.of();
|
||||
sessionExtras = Bundle.EMPTY;
|
||||
sessionError = null;
|
||||
}
|
||||
@ -2639,13 +2650,13 @@ import org.checkerframework.checker.initialization.qual.UnderInitialization;
|
||||
PlayerInfo playerInfo,
|
||||
SessionCommands availableSessionCommands,
|
||||
Commands availablePlayerCommands,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
@Nullable Bundle sessionExtras,
|
||||
@Nullable SessionError sessionError) {
|
||||
this.playerInfo = playerInfo;
|
||||
this.availableSessionCommands = availableSessionCommands;
|
||||
this.availablePlayerCommands = availablePlayerCommands;
|
||||
this.customLayout = customLayout;
|
||||
this.mediaButtonPreferences = mediaButtonPreferences;
|
||||
this.sessionExtras = sessionExtras == null ? Bundle.EMPTY : sessionExtras;
|
||||
this.sessionError = sessionError;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import androidx.media3.common.util.BundleCollectionUtil;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.session.MediaLibraryService.LibraryParams;
|
||||
import androidx.media3.session.PlayerInfo.BundlingExclusions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
@ -39,7 +40,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
private static final String TAG = "MediaControllerStub";
|
||||
|
||||
/** The version of the IMediaController interface. */
|
||||
public static final int VERSION_INT = 6;
|
||||
public static final int VERSION_INT = 7;
|
||||
|
||||
private final WeakReference<MediaControllerImplBase> controller;
|
||||
|
||||
@ -129,6 +130,30 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
dispatchControllerTaskOnHandler(controller -> controller.onSetCustomLayout(seq, layout));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMediaButtonPreferences(int seq, @Nullable List<Bundle> commandButtonBundleList) {
|
||||
if (commandButtonBundleList == null) {
|
||||
return;
|
||||
}
|
||||
ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
try {
|
||||
int sessionInterfaceVersion = getSessionInterfaceVersion();
|
||||
if (sessionInterfaceVersion == C.INDEX_UNSET) {
|
||||
// Stale event.
|
||||
return;
|
||||
}
|
||||
mediaButtonPreferences =
|
||||
BundleCollectionUtil.fromBundleList(
|
||||
bundle -> CommandButton.fromBundle(bundle, sessionInterfaceVersion),
|
||||
commandButtonBundleList);
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "Ignoring malformed Bundle for CommandButton", e);
|
||||
return;
|
||||
}
|
||||
dispatchControllerTaskOnHandler(
|
||||
controller -> controller.onSetMediaButtonPreferences(seq, mediaButtonPreferences));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailableCommandsChangedFromSession(
|
||||
int seq, @Nullable Bundle sessionCommandsBundle, @Nullable Bundle playerCommandsBundle) {
|
||||
|
@ -543,6 +543,10 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
/**
|
||||
* Sets the custom layout of the session.
|
||||
*
|
||||
* <p>This method will be deprecated, prefer to use {@link #setMediaButtonPreferences}. Note
|
||||
* that the media button preferences use {@link CommandButton#slots} to define the allowed
|
||||
* button placement.
|
||||
*
|
||||
* <p>The buttons are converted to custom actions in the legacy media session playback state
|
||||
* for legacy controllers (see {@code
|
||||
* PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}). When
|
||||
@ -564,11 +568,42 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
* @return The builder to allow chaining.
|
||||
*/
|
||||
@UnstableApi
|
||||
@CanIgnoreReturnValue
|
||||
@Override
|
||||
public Builder setCustomLayout(List<CommandButton> customLayout) {
|
||||
return super.setCustomLayout(customLayout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media button preferences.
|
||||
*
|
||||
* <p>The button are converted to custom actions in the legacy media session playback state
|
||||
* for legacy controllers (see {@code
|
||||
* PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}). When
|
||||
* converting, the {@linkplain SessionCommand#customExtras custom extras of the session
|
||||
* command} is used for the extras of the legacy custom action.
|
||||
*
|
||||
* <p>Controllers that connect have the media button preferences of the session available with
|
||||
* the initial connection result by default. Media button preferences specific to a controller
|
||||
* can be set when the controller {@linkplain MediaSession.Callback#onConnect connects} by
|
||||
* using an {@link ConnectionResult.AcceptedResultBuilder}.
|
||||
*
|
||||
* <p>Use {@code MediaSession.setMediaButtonPreferences(..)} to update the media button
|
||||
* preferences during the life time of the session.
|
||||
*
|
||||
* <p>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 mediaButtonPreferences The ordered list of {@link CommandButton command buttons}.
|
||||
* @return The builder to allow chaining.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Builder setMediaButtonPreferences(List<CommandButton> mediaButtonPreferences) {
|
||||
return super.setMediaButtonPreferences(mediaButtonPreferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether a play button is shown if playback is {@linkplain
|
||||
* Player#getPlaybackSuppressionReason() suppressed}.
|
||||
@ -660,6 +695,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
player,
|
||||
sessionActivity,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
commandButtonsForMediaItems,
|
||||
callback,
|
||||
tokenExtras,
|
||||
@ -677,6 +713,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
Player player,
|
||||
@Nullable PendingIntent sessionActivity,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
ImmutableList<CommandButton> commandButtonsForMediaItems,
|
||||
MediaSession.Callback callback,
|
||||
Bundle tokenExtras,
|
||||
@ -691,6 +728,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
player,
|
||||
sessionActivity,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
commandButtonsForMediaItems,
|
||||
callback,
|
||||
tokenExtras,
|
||||
@ -708,6 +746,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
Player player,
|
||||
@Nullable PendingIntent sessionActivity,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
ImmutableList<CommandButton> commandButtonsForMediaItems,
|
||||
MediaSession.Callback callback,
|
||||
Bundle tokenExtras,
|
||||
@ -723,6 +762,7 @@ public abstract class MediaLibraryService extends MediaSessionService {
|
||||
player,
|
||||
sessionActivity,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
commandButtonsForMediaItems,
|
||||
(Callback) callback,
|
||||
tokenExtras,
|
||||
|
@ -75,6 +75,7 @@ import java.util.concurrent.Future;
|
||||
Player player,
|
||||
@Nullable PendingIntent sessionActivity,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
ImmutableList<CommandButton> commandButtonsForMediaItems,
|
||||
MediaLibrarySession.Callback callback,
|
||||
Bundle tokenExtras,
|
||||
@ -90,6 +91,7 @@ import java.util.concurrent.Future;
|
||||
player,
|
||||
sessionActivity,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
commandButtonsForMediaItems,
|
||||
callback,
|
||||
tokenExtras,
|
||||
|
@ -27,7 +27,6 @@ import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
|
||||
/** A notification for media playbacks. */
|
||||
public final class MediaNotification {
|
||||
@ -145,15 +144,15 @@ public final class MediaNotification {
|
||||
* @param mediaSession The media session.
|
||||
* @param actionFactory The {@link ActionFactory} for creating notification {@link
|
||||
* NotificationCompat.Action actions}.
|
||||
* @param customLayout The custom layout {@linkplain MediaSession#setCustomLayout(List) set by
|
||||
* the session}.
|
||||
* @param mediaButtonPreferences The media button preferences {@linkplain
|
||||
* MediaSession#setMediaButtonPreferences set by the session}.
|
||||
* @param onNotificationChangedCallback A callback that the provider needs to notify when the
|
||||
* notification has changed and needs to be posted again, for example after a bitmap has
|
||||
* been loaded asynchronously.
|
||||
*/
|
||||
MediaNotification createNotification(
|
||||
MediaSession mediaSession,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
ActionFactory actionFactory,
|
||||
Callback onNotificationChangedCallback);
|
||||
|
||||
|
@ -159,9 +159,9 @@ import java.util.concurrent.TimeoutException;
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
ImmutableList<CommandButton> customLayout =
|
||||
ImmutableList<CommandButton> mediaButtonPreferences =
|
||||
mediaNotificationController != null
|
||||
? mediaNotificationController.getCustomLayout()
|
||||
? mediaNotificationController.getMediaButtonPreferences()
|
||||
: ImmutableList.of();
|
||||
MediaNotification.Provider.Callback callback =
|
||||
notification ->
|
||||
@ -172,7 +172,7 @@ import java.util.concurrent.TimeoutException;
|
||||
() -> {
|
||||
MediaNotification mediaNotification =
|
||||
this.mediaNotificationProvider.createNotification(
|
||||
session, customLayout, actionFactory, callback);
|
||||
session, mediaButtonPreferences, actionFactory, callback);
|
||||
mainExecutor.execute(
|
||||
() ->
|
||||
updateNotificationInternal(
|
||||
@ -320,7 +320,8 @@ import java.util.concurrent.TimeoutException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCustomLayoutChanged(MediaController controller, List<CommandButton> layout) {
|
||||
public void onMediaButtonPreferencesChanged(
|
||||
MediaController controller, List<CommandButton> mediaButtonPreferences) {
|
||||
mediaSessionService.onUpdateNotificationInternal(
|
||||
session, /* startInForegroundWhenPaused= */ false);
|
||||
}
|
||||
|
@ -369,6 +369,10 @@ public class MediaSession {
|
||||
/**
|
||||
* Sets the custom layout of the session.
|
||||
*
|
||||
* <p>This method will be deprecated, prefer to use {@link #setMediaButtonPreferences}. Note
|
||||
* that the media button preferences use {@link CommandButton#slots} to define the allowed
|
||||
* button placement.
|
||||
*
|
||||
* <p>The button are converted to custom actions in the legacy media session playback state for
|
||||
* legacy controllers (see {@code
|
||||
* PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}). When
|
||||
@ -389,12 +393,43 @@ public class MediaSession {
|
||||
* @param customLayout The ordered list of {@link CommandButton command buttons}.
|
||||
* @return The builder to allow chaining.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Builder setCustomLayout(List<CommandButton> customLayout) {
|
||||
return super.setCustomLayout(customLayout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media button preferences.
|
||||
*
|
||||
* <p>The button are converted to custom actions in the legacy media session playback state for
|
||||
* legacy controllers (see {@code
|
||||
* PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}). When
|
||||
* converting, the {@linkplain SessionCommand#customExtras custom extras of the session command}
|
||||
* is used for the extras of the legacy custom action.
|
||||
*
|
||||
* <p>Controllers that connect have the media button preferences of the session available with
|
||||
* the initial connection result by default. Media button preferences specific to a controller
|
||||
* can be set when the controller {@linkplain MediaSession.Callback#onConnect connects} by using
|
||||
* an {@link ConnectionResult.AcceptedResultBuilder}.
|
||||
*
|
||||
* <p>Use {@code MediaSession.setMediaButtonPreferences(..)} to update the media button
|
||||
* preferences during the life time of the session.
|
||||
*
|
||||
* <p>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 mediaButtonPreferences The ordered list of {@link CommandButton command buttons}.
|
||||
* @return The builder to allow chaining.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
@UnstableApi
|
||||
@Override
|
||||
public Builder setMediaButtonPreferences(List<CommandButton> mediaButtonPreferences) {
|
||||
return super.setMediaButtonPreferences(mediaButtonPreferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether periodic position updates should be sent to controllers while playing. If false,
|
||||
* no periodic position updates are sent to controllers.
|
||||
@ -455,6 +490,7 @@ public class MediaSession {
|
||||
player,
|
||||
sessionActivity,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
commandButtonsForMediaItems,
|
||||
callback,
|
||||
tokenExtras,
|
||||
@ -682,6 +718,7 @@ public class MediaSession {
|
||||
Player player,
|
||||
@Nullable PendingIntent sessionActivity,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
ImmutableList<CommandButton> commandButtonsForMediaItems,
|
||||
Callback callback,
|
||||
Bundle tokenExtras,
|
||||
@ -703,6 +740,7 @@ public class MediaSession {
|
||||
player,
|
||||
sessionActivity,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
commandButtonsForMediaItems,
|
||||
callback,
|
||||
tokenExtras,
|
||||
@ -719,6 +757,7 @@ public class MediaSession {
|
||||
Player player,
|
||||
@Nullable PendingIntent sessionActivity,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
ImmutableList<CommandButton> commandButtonsForMediaItems,
|
||||
Callback callback,
|
||||
Bundle tokenExtras,
|
||||
@ -734,6 +773,7 @@ public class MediaSession {
|
||||
player,
|
||||
sessionActivity,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
commandButtonsForMediaItems,
|
||||
callback,
|
||||
tokenExtras,
|
||||
@ -912,12 +952,12 @@ public class MediaSession {
|
||||
*
|
||||
* <p>Use this controller info to set {@linkplain #setAvailableCommands(ControllerInfo,
|
||||
* SessionCommands, Player.Commands) available commands} and {@linkplain
|
||||
* #setCustomLayout(ControllerInfo, List) custom layout} that are consistently applied to the
|
||||
* media notification on all API levels.
|
||||
* #setMediaButtonPreferences(ControllerInfo, List) media button preferences} that are
|
||||
* consistently applied to the media notification on all API levels.
|
||||
*
|
||||
* <p>Available {@linkplain SessionCommands session commands} of the media notification controller
|
||||
* are used to enable or disable buttons of the custom layout before it is passed to the
|
||||
* {@linkplain MediaNotification.Provider#createNotification(MediaSession, ImmutableList,
|
||||
* are used to enable or disable buttons of the media button preferences before they are passed to
|
||||
* the {@linkplain MediaNotification.Provider#createNotification(MediaSession, ImmutableList,
|
||||
* MediaNotification.ActionFactory, MediaNotification.Provider.Callback) notification provider}.
|
||||
* Disabled command buttons are not converted to notification actions when using {@link
|
||||
* DefaultMediaNotificationProvider}. This affects the media notification displayed by System UI
|
||||
@ -925,9 +965,9 @@ public class MediaSession {
|
||||
*
|
||||
* <p>The available session commands of the media notification controller are used to maintain
|
||||
* custom actions of the platform session (see {@code PlaybackStateCompat.getCustomActions()}).
|
||||
* Command buttons of the custom layout are disabled or enabled according to the available session
|
||||
* commands. Disabled command buttons are not converted to custom actions of the platform session.
|
||||
* This affects the media notification displayed by System UI <a
|
||||
* Command buttons of the media button preferences are disabled or enabled according to the
|
||||
* available session commands. Disabled command buttons are not converted to custom actions of the
|
||||
* platform session. This affects the media notification displayed by System UI <a
|
||||
* href="https://developer.android.com/about/versions/13/behavior-changes-13#playback-controls">starting
|
||||
* with API 33</a>.
|
||||
*
|
||||
@ -969,7 +1009,11 @@ public class MediaSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom layout for the given Media3 controller.
|
||||
* Sets the custom layout for the given controller.
|
||||
*
|
||||
* <p>This method will be deprecated, prefer to use {@link
|
||||
* #setMediaButtonPreferences(ControllerInfo, List)}. Note that the media button preferences use
|
||||
* {@link CommandButton#slots} to define the allowed button placement.
|
||||
*
|
||||
* <p>Make sure to have the session commands of all command buttons of the custom layout
|
||||
* {@linkplain MediaController#getAvailableSessionCommands() available for controllers}. Include
|
||||
@ -1003,7 +1047,11 @@ public class MediaSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom layout that can initially be set when building the session.
|
||||
* Sets the custom layout for all controllers.
|
||||
*
|
||||
* <p>This method will be deprecated, prefer to use {@link #setMediaButtonPreferences(List)}. Note
|
||||
* that the media button preferences use {@link CommandButton#slots} to define the allowed button
|
||||
* placement.
|
||||
*
|
||||
* <p>Calling this method broadcasts the custom layout to all connected Media3 controllers,
|
||||
* including the {@linkplain #getMediaNotificationControllerInfo() media notification controller}.
|
||||
@ -1021,13 +1069,78 @@ public class MediaSession {
|
||||
* the controller {@linkplain MediaSession.Callback#onConnect connects} by using an {@link
|
||||
* ConnectionResult.AcceptedResultBuilder}.
|
||||
*
|
||||
* @param layout The ordered list of {@link CommandButton}.
|
||||
* @param layout The ordered list of {@linkplain CommandButton command buttons}.
|
||||
*/
|
||||
public final void setCustomLayout(List<CommandButton> layout) {
|
||||
checkNotNull(layout, "layout must not be null");
|
||||
impl.setCustomLayout(ImmutableList.copyOf(layout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media button preferences for the given controller.
|
||||
*
|
||||
* <p>Make sure to have the session commands of all command buttons of the media button
|
||||
* preferences {@linkplain MediaController#getAvailableSessionCommands() available for
|
||||
* controllers}. Include the custom session commands a controller should be able to send in the
|
||||
* available commands of the connection result {@linkplain
|
||||
* MediaSession.Callback#onConnect(MediaSession, ControllerInfo) that your app returns when the
|
||||
* controller connects}. The {@link CommandButton#isEnabled} flag is set according to the
|
||||
* available commands of the controller and overrides a value that may have been set by the app.
|
||||
*
|
||||
* <p>On the controller side, {@link
|
||||
* MediaController.Listener#onMediaButtonPreferencesChanged(MediaController, List)} is only called
|
||||
* if the new media button preferences are different to the media button preferences the {@link
|
||||
* MediaController#getMediaButtonPreferences() controller already has available}. Note that this
|
||||
* comparison uses {@link CommandButton#equals} and therefore ignores {@link
|
||||
* CommandButton#extras}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Interoperability: This call has no effect when called for a {@linkplain
|
||||
* ControllerInfo#LEGACY_CONTROLLER_VERSION legacy controller}.
|
||||
*
|
||||
* @param controller The controller for which to set the media button preferences.
|
||||
* @param mediaButtonPreferences The ordered list of {@linkplain CommandButton command buttons}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@CanIgnoreReturnValue
|
||||
public final ListenableFuture<SessionResult> setMediaButtonPreferences(
|
||||
ControllerInfo controller, List<CommandButton> mediaButtonPreferences) {
|
||||
checkNotNull(controller, "controller must not be null");
|
||||
checkNotNull(mediaButtonPreferences, "media button preferences must not be null");
|
||||
return impl.setMediaButtonPreferences(controller, ImmutableList.copyOf(mediaButtonPreferences));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media button preferences for all controllers.
|
||||
*
|
||||
* <p>Calling this method broadcasts the media button preferences to all connected Media3
|
||||
* controllers, including the {@linkplain #getMediaNotificationControllerInfo() media notification
|
||||
* controller}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>{@link MediaController.Listener#onMediaButtonPreferencesChanged(MediaController, List)} is
|
||||
* only called if the new media button preferences are different to the media button preferences
|
||||
* the {@linkplain MediaController#getMediaButtonPreferences() controller already has available}.
|
||||
* Note that {@link Bundle extras} are ignored when comparing {@linkplain CommandButton command
|
||||
* buttons}.
|
||||
*
|
||||
* <p>Controllers that connect after calling this method will have the new media button
|
||||
* preferences available with the initial connection result. Media button preferences specific to
|
||||
* a controller can be set when the controller {@linkplain MediaSession.Callback#onConnect
|
||||
* connects} by using an {@link ConnectionResult.AcceptedResultBuilder}.
|
||||
*
|
||||
* @param mediaButtonPreferences The ordered list of {@linkplain CommandButton command buttons}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public final void setMediaButtonPreferences(List<CommandButton> mediaButtonPreferences) {
|
||||
checkNotNull(mediaButtonPreferences, "media button preferences must not be null");
|
||||
impl.setMediaButtonPreferences(ImmutableList.copyOf(mediaButtonPreferences));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new available commands for the controller.
|
||||
*
|
||||
@ -1055,6 +1168,10 @@ public class MediaSession {
|
||||
/**
|
||||
* Returns the custom layout of the session.
|
||||
*
|
||||
* <p>This method will be deprecated, prefer to use {@link #getMediaButtonPreferences()} instead.
|
||||
* Note that the media button preferences use {@link CommandButton#slots} to define the allowed
|
||||
* button placement.
|
||||
*
|
||||
* <p>For informational purpose only. Mutations on the {@link Bundle} of either a {@link
|
||||
* CommandButton} or a {@link SessionCommand} do not have effect. To change the custom layout use
|
||||
* {@link #setCustomLayout(List)} or {@link #setCustomLayout(ControllerInfo, List)}.
|
||||
@ -1064,6 +1181,18 @@ public class MediaSession {
|
||||
return impl.getCustomLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media button preferences of the session.
|
||||
*
|
||||
* <p>For informational purpose only. Mutations on the {@link Bundle} of either a {@link
|
||||
* CommandButton} or a {@link SessionCommand} do not have effect. To change the media button
|
||||
* preferences use {@link #setMediaButtonPreferences}.
|
||||
*/
|
||||
@UnstableApi
|
||||
public ImmutableList<CommandButton> getMediaButtonPreferences() {
|
||||
return impl.getMediaButtonPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a custom command to all connected controllers.
|
||||
*
|
||||
@ -1313,7 +1442,8 @@ public class MediaSession {
|
||||
*
|
||||
* <p>If this callback is not overridden, it allows all controllers to connect that can access
|
||||
* the session. All session and player commands are made available and the {@linkplain
|
||||
* MediaSession#getCustomLayout() custom layout of the session} is included.
|
||||
* MediaSession#getMediaButtonPreferences() media button preferences of the session} are
|
||||
* included.
|
||||
*
|
||||
* <p>Note that the player commands in {@link ConnectionResult#availablePlayerCommands} will be
|
||||
* intersected with the {@link Player#getAvailableCommands() available commands} of the
|
||||
@ -1325,8 +1455,8 @@ public class MediaSession {
|
||||
* returned by {@link MediaController.Builder#buildAsync()}.
|
||||
*
|
||||
* <p>The controller isn't connected yet, so calls to the controller (e.g. {@link
|
||||
* #sendCustomCommand}, {@link #setCustomLayout}) will be ignored. Use {@link #onPostConnect}
|
||||
* for custom initialization of the controller instead.
|
||||
* #sendCustomCommand}, {@link #setMediaButtonPreferences}) will be ignored. Use {@link
|
||||
* #onPostConnect} for custom initialization of the controller instead.
|
||||
*
|
||||
* <p>Interoperability: If a legacy controller is connecting to the session then this callback
|
||||
* may block the main thread, even if it's called on a different application thread. If it's
|
||||
@ -1346,8 +1476,8 @@ public class MediaSession {
|
||||
* controller.
|
||||
*
|
||||
* <p>Note that calls to the controller (e.g. {@link #sendCustomCommand}, {@link
|
||||
* #setCustomLayout}) work here but don't work in {@link #onConnect} because the controller
|
||||
* isn't connected yet in {@link #onConnect}.
|
||||
* #setMediaButtonPreferences}) work here but don't work in {@link #onConnect} because the
|
||||
* controller isn't connected yet in {@link #onConnect}.
|
||||
*
|
||||
* @param session The session for this event.
|
||||
* @param controller The {@linkplain ControllerInfo controller} information.
|
||||
@ -1738,7 +1868,7 @@ public class MediaSession {
|
||||
|
||||
/**
|
||||
* A result for {@link Callback#onConnect(MediaSession, ControllerInfo)} to denote the set of
|
||||
* available commands and the custom layout for a {@link ControllerInfo controller}.
|
||||
* available commands and the media button preferences for a {@link ControllerInfo controller}.
|
||||
*/
|
||||
public static final class ConnectionResult {
|
||||
|
||||
@ -1748,6 +1878,7 @@ public class MediaSession {
|
||||
private SessionCommands availableSessionCommands;
|
||||
private Player.Commands availablePlayerCommands = DEFAULT_PLAYER_COMMANDS;
|
||||
@Nullable private ImmutableList<CommandButton> customLayout;
|
||||
@Nullable private ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
@Nullable private Bundle sessionExtras;
|
||||
@Nullable private PendingIntent sessionActivity;
|
||||
|
||||
@ -1799,13 +1930,17 @@ public class MediaSession {
|
||||
* Sets the custom layout, overriding the {@linkplain MediaSession#getCustomLayout() custom
|
||||
* layout of the session}.
|
||||
*
|
||||
* <p>This method will be deprecated, prefer to use {@link #setMediaButtonPreferences}. Note
|
||||
* that the media button preferences use {@link CommandButton#slots} to define the allowed
|
||||
* button placement.
|
||||
*
|
||||
* <p>The default is null to indicate that the custom layout of the session should be used.
|
||||
*
|
||||
* <p>Make sure to have the session commands of all command buttons of the custom layout
|
||||
* included in the {@linkplain #setAvailableSessionCommands(SessionCommands)} available
|
||||
* 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.
|
||||
* included in the {@linkplain #setAvailableSessionCommands(SessionCommands) available 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<CommandButton> customLayout) {
|
||||
@ -1813,6 +1948,27 @@ public class MediaSession {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media button preferences, overriding the {@linkplain
|
||||
* MediaSession#getMediaButtonPreferences() media button preferences of the session}.
|
||||
*
|
||||
* <p>The default is null to indicate that the media button preferences of the session should
|
||||
* be used.
|
||||
*
|
||||
* <p>Make sure to have the session commands of all command buttons of the media button
|
||||
* preferences included in the {@linkplain #setAvailableSessionCommands(SessionCommands)
|
||||
* available 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 setMediaButtonPreferences(
|
||||
@Nullable List<CommandButton> mediaButtonPreferences) {
|
||||
this.mediaButtonPreferences =
|
||||
mediaButtonPreferences == null ? null : ImmutableList.copyOf(mediaButtonPreferences);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session extras, overriding the {@linkplain MediaSession#getSessionExtras() extras
|
||||
* of the session}.
|
||||
@ -1844,6 +2000,7 @@ public class MediaSession {
|
||||
availableSessionCommands,
|
||||
availablePlayerCommands,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
sessionExtras,
|
||||
sessionActivity);
|
||||
}
|
||||
@ -1873,6 +2030,12 @@ public class MediaSession {
|
||||
/** The custom layout or null if the custom layout of the session should be used. */
|
||||
@UnstableApi @Nullable public final ImmutableList<CommandButton> customLayout;
|
||||
|
||||
/**
|
||||
* The media button preferences or null if the media button preferences of the session should be
|
||||
* used.
|
||||
*/
|
||||
@UnstableApi @Nullable public final ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
|
||||
/** The session extras. */
|
||||
@UnstableApi @Nullable public final Bundle sessionExtras;
|
||||
|
||||
@ -1885,12 +2048,14 @@ public class MediaSession {
|
||||
SessionCommands availableSessionCommands,
|
||||
Player.Commands availablePlayerCommands,
|
||||
@Nullable ImmutableList<CommandButton> customLayout,
|
||||
@Nullable ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
@Nullable Bundle sessionExtras,
|
||||
@Nullable PendingIntent sessionActivity) {
|
||||
isAccepted = accepted;
|
||||
this.availableSessionCommands = availableSessionCommands;
|
||||
this.availablePlayerCommands = availablePlayerCommands;
|
||||
this.customLayout = customLayout;
|
||||
this.mediaButtonPreferences = mediaButtonPreferences;
|
||||
this.sessionExtras = sessionExtras;
|
||||
this.sessionActivity = sessionActivity;
|
||||
}
|
||||
@ -1900,8 +2065,8 @@ public class MediaSession {
|
||||
*
|
||||
* <p>Commands are specific to the controller receiving this connection result.
|
||||
*
|
||||
* <p>The controller receives {@linkplain MediaSession#getCustomLayout() the custom layout of
|
||||
* the session}.
|
||||
* <p>The controller receives {@linkplain MediaSession#getMediaButtonPreferences() the media
|
||||
* button preferences of the session}.
|
||||
*
|
||||
* <p>See {@link AcceptedResultBuilder} for a more flexible way to accept a connection.
|
||||
*/
|
||||
@ -1912,6 +2077,7 @@ public class MediaSession {
|
||||
availableSessionCommands,
|
||||
availablePlayerCommands,
|
||||
/* customLayout= */ null,
|
||||
/* mediaButtonPreferences= */ null,
|
||||
/* sessionExtras= */ null,
|
||||
/* sessionActivity= */ null);
|
||||
}
|
||||
@ -1923,6 +2089,7 @@ public class MediaSession {
|
||||
SessionCommands.EMPTY,
|
||||
Player.Commands.EMPTY,
|
||||
/* customLayout= */ ImmutableList.of(),
|
||||
/* mediaButtonPreferences= */ ImmutableList.of(),
|
||||
/* sessionExtras= */ Bundle.EMPTY,
|
||||
/* sessionActivity= */ null);
|
||||
}
|
||||
@ -1943,8 +2110,7 @@ public class MediaSession {
|
||||
PlayerInfo playerInfo,
|
||||
Player.Commands availableCommands,
|
||||
boolean excludeTimeline,
|
||||
boolean excludeTracks,
|
||||
int controllerInterfaceVersion)
|
||||
boolean excludeTracks)
|
||||
throws RemoteException {}
|
||||
|
||||
default void onPeriodicSessionPositionInfoChanged(
|
||||
@ -1961,6 +2127,9 @@ public class MediaSession {
|
||||
|
||||
default void setCustomLayout(int seq, List<CommandButton> layout) throws RemoteException {}
|
||||
|
||||
default void setMediaButtonPreferences(int seq, List<CommandButton> mediaButtonPreferences)
|
||||
throws RemoteException {}
|
||||
|
||||
default void onSessionActivityChanged(int seq, PendingIntent sessionActivity)
|
||||
throws RemoteException {}
|
||||
|
||||
@ -2105,6 +2274,7 @@ public class MediaSession {
|
||||
/* package */ @MonotonicNonNull BitmapLoader bitmapLoader;
|
||||
/* package */ boolean playIfSuppressed;
|
||||
/* package */ ImmutableList<CommandButton> customLayout;
|
||||
/* package */ ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
/* package */ ImmutableList<CommandButton> commandButtonsForMediaItems;
|
||||
/* package */ boolean isPeriodicPositionUpdateEnabled;
|
||||
|
||||
@ -2117,6 +2287,7 @@ public class MediaSession {
|
||||
tokenExtras = Bundle.EMPTY;
|
||||
sessionExtras = Bundle.EMPTY;
|
||||
customLayout = ImmutableList.of();
|
||||
mediaButtonPreferences = ImmutableList.of();
|
||||
playIfSuppressed = true;
|
||||
isPeriodicPositionUpdateEnabled = true;
|
||||
commandButtonsForMediaItems = ImmutableList.of();
|
||||
@ -2174,6 +2345,13 @@ public class MediaSession {
|
||||
return (BuilderT) this;
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
@SuppressWarnings("unchecked")
|
||||
public BuilderT setMediaButtonPreferences(List<CommandButton> mediaButtonPreferences) {
|
||||
this.mediaButtonPreferences = ImmutableList.copyOf(mediaButtonPreferences);
|
||||
return (BuilderT) this;
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
@SuppressWarnings("unchecked")
|
||||
public BuilderT setShowPlayButtonIfPlaybackIsSuppressed(
|
||||
|
@ -152,6 +152,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private long sessionPositionUpdateDelayMs;
|
||||
private boolean isMediaNotificationControllerConnected;
|
||||
private ImmutableList<CommandButton> customLayout;
|
||||
private ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
private Bundle sessionExtras;
|
||||
|
||||
@SuppressWarnings("argument.type.incompatible") // Using this in System.identityHashCode
|
||||
@ -162,6 +163,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
Player player,
|
||||
@Nullable PendingIntent sessionActivity,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
ImmutableList<CommandButton> commandButtonsForMediaItems,
|
||||
MediaSession.Callback callback,
|
||||
Bundle tokenExtras,
|
||||
@ -183,6 +185,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
sessionId = id;
|
||||
this.sessionActivity = sessionActivity;
|
||||
this.customLayout = customLayout;
|
||||
this.mediaButtonPreferences = mediaButtonPreferences;
|
||||
this.commandButtonsForMediaItems = commandButtonsForMediaItems;
|
||||
this.callback = callback;
|
||||
this.sessionExtras = sessionExtras;
|
||||
@ -246,6 +249,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
player,
|
||||
playIfSuppressed,
|
||||
customLayout,
|
||||
mediaButtonPreferences,
|
||||
connectionResult.availableSessionCommands,
|
||||
connectionResult.availablePlayerCommands,
|
||||
sessionExtras);
|
||||
@ -272,6 +276,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
player,
|
||||
playIfSuppressed,
|
||||
playerWrapper.getCustomLayout(),
|
||||
playerWrapper.getMediaButtonPreferences(),
|
||||
playerWrapper.getAvailableSessionCommands(),
|
||||
playerWrapper.getAvailablePlayerCommands(),
|
||||
playerWrapper.getLegacyExtras()));
|
||||
@ -511,11 +516,45 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
(controller, seq) -> controller.setCustomLayout(seq, customLayout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media button preferences for the given {@link MediaController}.
|
||||
*
|
||||
* @param controller The controller.
|
||||
* @param mediaButtonPreferences The media button preferences.
|
||||
* @return The session result from the controller.
|
||||
*/
|
||||
public ListenableFuture<SessionResult> setMediaButtonPreferences(
|
||||
ControllerInfo controller, ImmutableList<CommandButton> mediaButtonPreferences) {
|
||||
if (isMediaNotificationController(controller)) {
|
||||
playerWrapper.setMediaButtonPreferences(mediaButtonPreferences);
|
||||
sessionLegacyStub.updateLegacySessionPlaybackState(playerWrapper);
|
||||
}
|
||||
return dispatchRemoteControllerTask(
|
||||
controller,
|
||||
(controller1, seq) -> controller1.setMediaButtonPreferences(seq, mediaButtonPreferences));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media button preferences of the session and sends the media button preferences to all
|
||||
* controllers.
|
||||
*/
|
||||
public void setMediaButtonPreferences(ImmutableList<CommandButton> mediaButtonPreferences) {
|
||||
this.mediaButtonPreferences = mediaButtonPreferences;
|
||||
playerWrapper.setMediaButtonPreferences(mediaButtonPreferences);
|
||||
dispatchRemoteControllerTaskWithoutReturn(
|
||||
(controller, seq) -> controller.setMediaButtonPreferences(seq, mediaButtonPreferences));
|
||||
}
|
||||
|
||||
/** Returns the custom layout. */
|
||||
public ImmutableList<CommandButton> getCustomLayout() {
|
||||
return customLayout;
|
||||
}
|
||||
|
||||
/** Returns the media button preferences. */
|
||||
public ImmutableList<CommandButton> getMediaButtonPreferences() {
|
||||
return mediaButtonPreferences;
|
||||
}
|
||||
|
||||
/** Returns the command buttons for media items. */
|
||||
public ImmutableList<CommandButton> getCommandButtonsForMediaItems() {
|
||||
return commandButtonsForMediaItems;
|
||||
@ -612,12 +651,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
getPlayerWrapper().getAvailableCommands());
|
||||
checkStateNotNull(controller.getControllerCb())
|
||||
.onPlayerInfoChanged(
|
||||
seq,
|
||||
playerInfo,
|
||||
intersectedCommands,
|
||||
excludeTimeline,
|
||||
excludeTracks,
|
||||
controller.getInterfaceVersion());
|
||||
seq, playerInfo, intersectedCommands, excludeTimeline, excludeTracks);
|
||||
} catch (DeadObjectException e) {
|
||||
onDeadObjectException(controller);
|
||||
} catch (RemoteException e) {
|
||||
@ -679,6 +713,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
.setAvailableSessionCommands(playerWrapper.getAvailableSessionCommands())
|
||||
.setAvailablePlayerCommands(playerWrapper.getAvailablePlayerCommands())
|
||||
.setCustomLayout(playerWrapper.getCustomLayout())
|
||||
.setMediaButtonPreferences(playerWrapper.getMediaButtonPreferences())
|
||||
.build();
|
||||
}
|
||||
MediaSession.ConnectionResult connectionResult =
|
||||
@ -691,6 +726,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
connectionResult.customLayout != null
|
||||
? connectionResult.customLayout
|
||||
: instance.getCustomLayout());
|
||||
playerWrapper.setMediaButtonPreferences(
|
||||
connectionResult.mediaButtonPreferences != null
|
||||
? connectionResult.mediaButtonPreferences
|
||||
: instance.getMediaButtonPreferences());
|
||||
setAvailableFrameworkControllerCommands(
|
||||
connectionResult.availableSessionCommands, connectionResult.availablePlayerCommands);
|
||||
}
|
||||
|
@ -1115,6 +1115,11 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
||||
updateLegacySessionPlaybackState(sessionImpl.getPlayerWrapper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaButtonPreferences(int seq, List<CommandButton> mediaButtonPreferences) {
|
||||
updateLegacySessionPlaybackState(sessionImpl.getPlayerWrapper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionExtrasChanged(int seq, Bundle sessionExtras) {
|
||||
sessionCompat.setExtras(sessionExtras);
|
||||
|
@ -761,7 +761,8 @@ public abstract class MediaSessionService extends Service {
|
||||
request.libraryVersion,
|
||||
request.controllerInterfaceVersion,
|
||||
isTrusted,
|
||||
new MediaSessionStub.Controller2Cb(caller),
|
||||
new MediaSessionStub.Controller2Cb(
|
||||
caller, request.controllerInterfaceVersion),
|
||||
request.connectionHints,
|
||||
request.maxCommandsForMediaItems);
|
||||
|
||||
|
@ -535,6 +535,9 @@ import java.util.concurrent.ExecutionException;
|
||||
connectionResult.customLayout != null
|
||||
? connectionResult.customLayout
|
||||
: sessionImpl.getCustomLayout(),
|
||||
connectionResult.mediaButtonPreferences != null
|
||||
? connectionResult.mediaButtonPreferences
|
||||
: sessionImpl.getMediaButtonPreferences(),
|
||||
sessionImpl.getCommandButtonsForMediaItems(),
|
||||
connectionResult.availableSessionCommands,
|
||||
connectionResult.availablePlayerCommands,
|
||||
@ -636,7 +639,7 @@ import java.util.concurrent.ExecutionException;
|
||||
request.libraryVersion,
|
||||
request.controllerInterfaceVersion,
|
||||
sessionManager.isTrustedForMediaControl(remoteUserInfo),
|
||||
new MediaSessionStub.Controller2Cb(caller),
|
||||
new MediaSessionStub.Controller2Cb(caller, request.controllerInterfaceVersion),
|
||||
request.connectionHints,
|
||||
request.maxCommandsForMediaItems);
|
||||
connect(caller, controllerInfo);
|
||||
@ -2006,9 +2009,11 @@ import java.util.concurrent.ExecutionException;
|
||||
/* package */ static final class Controller2Cb implements ControllerCb {
|
||||
|
||||
private final IMediaController iController;
|
||||
private final int controllerInterfaceVersion;
|
||||
|
||||
public Controller2Cb(IMediaController callback) {
|
||||
iController = callback;
|
||||
public Controller2Cb(IMediaController callback, int controllerInterfaceVersion) {
|
||||
this.iController = callback;
|
||||
this.controllerInterfaceVersion = controllerInterfaceVersion;
|
||||
}
|
||||
|
||||
public IBinder getCallbackBinder() {
|
||||
@ -2032,8 +2037,7 @@ import java.util.concurrent.ExecutionException;
|
||||
PlayerInfo playerInfo,
|
||||
Player.Commands availableCommands,
|
||||
boolean excludeTimeline,
|
||||
boolean excludeTracks,
|
||||
int controllerInterfaceVersion)
|
||||
boolean excludeTracks)
|
||||
throws RemoteException {
|
||||
Assertions.checkState(controllerInterfaceVersion != 0);
|
||||
// The bundling exclusions merge the performance overrides with the available commands.
|
||||
@ -2072,6 +2076,22 @@ import java.util.concurrent.ExecutionException;
|
||||
sequenceNumber, BundleCollectionUtil.toBundleList(layout, CommandButton::toBundle));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMediaButtonPreferences(
|
||||
int sequenceNumber, List<CommandButton> mediaButtonPreferences) throws RemoteException {
|
||||
if (controllerInterfaceVersion >= 7) {
|
||||
iController.onSetMediaButtonPreferences(
|
||||
sequenceNumber,
|
||||
BundleCollectionUtil.toBundleList(mediaButtonPreferences, CommandButton::toBundle));
|
||||
} else {
|
||||
// Controller doesn't support media button preferences, send the list as a custom layout.
|
||||
// TODO: b/332877990 - More accurately reflect media button preferences as custom layout.
|
||||
iController.onSetCustomLayout(
|
||||
sequenceNumber,
|
||||
BundleCollectionUtil.toBundleList(mediaButtonPreferences, CommandButton::toBundle));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionActivityChanged(int sequenceNumber, PendingIntent sessionActivity)
|
||||
throws RemoteException {
|
||||
|
@ -87,6 +87,7 @@ import java.util.List;
|
||||
@Nullable private LegacyError legacyError;
|
||||
@Nullable private Bundle legacyExtras;
|
||||
private ImmutableList<CommandButton> customLayout;
|
||||
private ImmutableList<CommandButton> mediaButtonPreferences;
|
||||
private SessionCommands availableSessionCommands;
|
||||
private Commands availablePlayerCommands;
|
||||
|
||||
@ -94,12 +95,14 @@ import java.util.List;
|
||||
Player player,
|
||||
boolean playIfSuppressed,
|
||||
ImmutableList<CommandButton> customLayout,
|
||||
ImmutableList<CommandButton> mediaButtonPreferences,
|
||||
SessionCommands availableSessionCommands,
|
||||
Commands availablePlayerCommands,
|
||||
@Nullable Bundle legacyExtras) {
|
||||
super(player);
|
||||
this.playIfSuppressed = playIfSuppressed;
|
||||
this.customLayout = customLayout;
|
||||
this.mediaButtonPreferences = mediaButtonPreferences;
|
||||
this.availableSessionCommands = availableSessionCommands;
|
||||
this.availablePlayerCommands = availablePlayerCommands;
|
||||
this.legacyExtras = legacyExtras;
|
||||
@ -123,10 +126,18 @@ import java.util.List;
|
||||
this.customLayout = customLayout;
|
||||
}
|
||||
|
||||
public void setMediaButtonPreferences(ImmutableList<CommandButton> mediaButtonPreferences) {
|
||||
this.mediaButtonPreferences = mediaButtonPreferences;
|
||||
}
|
||||
|
||||
/* package */ ImmutableList<CommandButton> getCustomLayout() {
|
||||
return customLayout;
|
||||
}
|
||||
|
||||
/* package */ ImmutableList<CommandButton> getMediaButtonPreferences() {
|
||||
return mediaButtonPreferences;
|
||||
}
|
||||
|
||||
public void setLegacyExtras(@Nullable Bundle extras) {
|
||||
if (extras != null) {
|
||||
checkArgument(!extras.containsKey(EXTRAS_KEY_PLAYBACK_SPEED_COMPAT));
|
||||
@ -1061,8 +1072,11 @@ import java.util.List;
|
||||
.setBufferedPosition(compatBufferedPosition)
|
||||
.setExtras(extras);
|
||||
|
||||
for (int i = 0; i < customLayout.size(); i++) {
|
||||
CommandButton commandButton = customLayout.get(i);
|
||||
// TODO: b/332877990 - More accurately reflect media button preferences as custom actions.
|
||||
List<CommandButton> buttonsForCustomActions =
|
||||
mediaButtonPreferences.isEmpty() ? customLayout : mediaButtonPreferences;
|
||||
for (int i = 0; i < buttonsForCustomActions.size(); i++) {
|
||||
CommandButton commandButton = buttonsForCustomActions.get(i);
|
||||
SessionCommand sessionCommand = commandButton.sessionCommand;
|
||||
if (sessionCommand != null
|
||||
&& commandButton.isEnabled
|
||||
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.session;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.media3.common.MediaLibraryInfo;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.test.utils.TestExoPlayerBuilder;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit test for {@link ConnectionState}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ConnectionStateTest {
|
||||
|
||||
@Test
|
||||
public void roundTripViaBundle_restoresEqualInstance() {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
Player player = new TestExoPlayerBuilder(context).build();
|
||||
MediaSession session = new MediaSession.Builder(context, player).build();
|
||||
Bundle tokenExtras = new Bundle();
|
||||
tokenExtras.putString("key", "token");
|
||||
Bundle sessionExtras = new Bundle();
|
||||
sessionExtras.putString("key", "session");
|
||||
ConnectionState connectionState =
|
||||
new ConnectionState(
|
||||
MediaLibraryInfo.VERSION_INT,
|
||||
MediaSessionStub.VERSION_INT,
|
||||
new MediaSessionStub(session.getImpl()),
|
||||
/* sessionActivity= */ PendingIntent.getActivity(
|
||||
context, /* requestCode= */ 0, new Intent(), /* flags= */ 0),
|
||||
/* customLayout= */ ImmutableList.of(
|
||||
new CommandButton.Builder(CommandButton.ICON_ARTIST)
|
||||
.setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT)
|
||||
.build()),
|
||||
/* mediaButtonPreferences= */ ImmutableList.of(
|
||||
new CommandButton.Builder(CommandButton.ICON_HEART_FILLED)
|
||||
.setPlayerCommand(Player.COMMAND_PREPARE)
|
||||
.build()),
|
||||
/* commandButtonsForMediaItems= */ ImmutableList.of(
|
||||
new CommandButton.Builder(CommandButton.ICON_NEXT)
|
||||
.setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT)
|
||||
.build()),
|
||||
new SessionCommands.Builder().add(new SessionCommand("action", Bundle.EMPTY)).build(),
|
||||
/* playerCommandsFromSession= */ new Player.Commands.Builder()
|
||||
.add(Player.COMMAND_GET_AUDIO_ATTRIBUTES)
|
||||
.build(),
|
||||
/* playerCommandsFromPlayer= */ new Player.Commands.Builder()
|
||||
.add(Player.COMMAND_CHANGE_MEDIA_ITEMS)
|
||||
.build(),
|
||||
tokenExtras,
|
||||
sessionExtras,
|
||||
PlayerInfo.DEFAULT.copyWithIsPlaying(true),
|
||||
session.getPlatformToken());
|
||||
|
||||
ConnectionState restoredConnectionState =
|
||||
ConnectionState.fromBundle(
|
||||
connectionState.toBundleForRemoteProcess(MediaControllerStub.VERSION_INT));
|
||||
session.release();
|
||||
player.release();
|
||||
|
||||
assertThat(restoredConnectionState.libraryVersion).isEqualTo(connectionState.libraryVersion);
|
||||
assertThat(restoredConnectionState.sessionInterfaceVersion)
|
||||
.isEqualTo(connectionState.sessionInterfaceVersion);
|
||||
assertThat(restoredConnectionState.sessionActivity).isEqualTo(connectionState.sessionActivity);
|
||||
assertThat(restoredConnectionState.sessionBinder).isEqualTo(connectionState.sessionBinder);
|
||||
assertThat(restoredConnectionState.customLayout).isEqualTo(connectionState.customLayout);
|
||||
assertThat(restoredConnectionState.mediaButtonPreferences)
|
||||
.isEqualTo(connectionState.mediaButtonPreferences);
|
||||
assertThat(restoredConnectionState.commandButtonsForMediaItems)
|
||||
.isEqualTo(connectionState.commandButtonsForMediaItems);
|
||||
assertThat(restoredConnectionState.sessionCommands).isEqualTo(connectionState.sessionCommands);
|
||||
assertThat(restoredConnectionState.playerCommandsFromSession)
|
||||
.isEqualTo(connectionState.playerCommandsFromSession);
|
||||
assertThat(restoredConnectionState.playerCommandsFromPlayer)
|
||||
.isEqualTo(connectionState.playerCommandsFromPlayer);
|
||||
assertThat(restoredConnectionState.tokenExtras.getString("key")).isEqualTo("token");
|
||||
assertThat(restoredConnectionState.sessionExtras.getString("key")).isEqualTo("session");
|
||||
assertThat(restoredConnectionState.playerInfo.isPlaying).isTrue();
|
||||
assertThat(restoredConnectionState.platformToken).isEqualTo(connectionState.platformToken);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
roundTripViaBundle_controllerVersion6OrLower_usesMediaButtonPreferencesAsCustomLayout() {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
Player player = new TestExoPlayerBuilder(context).build();
|
||||
MediaSession session = new MediaSession.Builder(context, player).build();
|
||||
ConnectionState connectionState =
|
||||
new ConnectionState(
|
||||
MediaLibraryInfo.VERSION_INT,
|
||||
MediaSessionStub.VERSION_INT,
|
||||
new MediaSessionStub(session.getImpl()),
|
||||
/* sessionActivity= */ null,
|
||||
/* customLayout= */ ImmutableList.of(),
|
||||
/* mediaButtonPreferences= */ ImmutableList.of(
|
||||
new CommandButton.Builder(CommandButton.ICON_HEART_FILLED)
|
||||
.setPlayerCommand(Player.COMMAND_PREPARE)
|
||||
.build()),
|
||||
/* commandButtonsForMediaItems= */ ImmutableList.of(),
|
||||
SessionCommands.EMPTY,
|
||||
/* playerCommandsFromSession= */ Player.Commands.EMPTY,
|
||||
/* playerCommandsFromPlayer= */ Player.Commands.EMPTY,
|
||||
/* tokenExtras= */ Bundle.EMPTY,
|
||||
/* sessionExtras= */ Bundle.EMPTY,
|
||||
PlayerInfo.DEFAULT,
|
||||
session.getPlatformToken());
|
||||
|
||||
ConnectionState restoredConnectionState =
|
||||
ConnectionState.fromBundle(
|
||||
connectionState.toBundleForRemoteProcess(/* controllerInterfaceVersion= */ 6));
|
||||
session.release();
|
||||
player.release();
|
||||
|
||||
assertThat(restoredConnectionState.customLayout)
|
||||
.isEqualTo(connectionState.mediaButtonPreferences);
|
||||
assertThat(restoredConnectionState.mediaButtonPreferences).isEmpty();
|
||||
}
|
||||
}
|
@ -1080,12 +1080,12 @@ public final class LegacyConversionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertToCustomLayout_withNull_returnsEmptyList() {
|
||||
assertThat(LegacyConversions.convertToCustomLayout(null)).isEmpty();
|
||||
public void convertToMediaButtonPreferences_withNull_returnsEmptyList() {
|
||||
assertThat(LegacyConversions.convertToMediaButtonPreferences(null)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertToCustomLayout_withoutIconConstantInExtras() {
|
||||
public void convertToMediaButtonPreferences_withoutIconConstantInExtras() {
|
||||
String extraKey = "key";
|
||||
String extraValue = "value";
|
||||
String actionStr = "action";
|
||||
@ -1107,7 +1107,7 @@ public final class LegacyConversionsTest {
|
||||
.addCustomAction(action)
|
||||
.build();
|
||||
|
||||
ImmutableList<CommandButton> buttons = LegacyConversions.convertToCustomLayout(state);
|
||||
ImmutableList<CommandButton> buttons = LegacyConversions.convertToMediaButtonPreferences(state);
|
||||
|
||||
assertThat(buttons).hasSize(1);
|
||||
CommandButton button = buttons.get(0);
|
||||
@ -1120,7 +1120,7 @@ public final class LegacyConversionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertToCustomLayout_withIconConstantInExtras() {
|
||||
public void convertToMediaButtonPreferences_withIconConstantInExtras() {
|
||||
String actionStr = "action";
|
||||
String displayName = "display_name";
|
||||
int iconRes = 21;
|
||||
@ -1140,7 +1140,7 @@ public final class LegacyConversionsTest {
|
||||
.addCustomAction(action)
|
||||
.build();
|
||||
|
||||
ImmutableList<CommandButton> buttons = LegacyConversions.convertToCustomLayout(state);
|
||||
ImmutableList<CommandButton> buttons = LegacyConversions.convertToMediaButtonPreferences(state);
|
||||
|
||||
assertThat(buttons).hasSize(1);
|
||||
CommandButton button = buttons.get(0);
|
||||
|
@ -259,6 +259,112 @@ public class MediaSessionServiceTest {
|
||||
serviceController.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaNotificationController_setMediaButtonPreferences_correctNotificationActions()
|
||||
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(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("customAction1")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(command1)
|
||||
.build();
|
||||
CommandButton button2 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("customAction2")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(command2)
|
||||
.build();
|
||||
CommandButton button3 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("customAction3")
|
||||
.setEnabled(false)
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(command3)
|
||||
.build();
|
||||
CommandButton button4 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.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)
|
||||
.setMediaButtonPreferences(ImmutableList.of(button1, button2, button3, button4))
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
if (session.isMediaNotificationController(controller)) {
|
||||
return new MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(
|
||||
MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS
|
||||
.buildUpon()
|
||||
.add(command1)
|
||||
.add(command2)
|
||||
.add(command3)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
return new MediaSession.ConnectionResult.AcceptedResultBuilder(session).build();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
ServiceController<TestService> serviceController = Robolectric.buildService(TestService.class);
|
||||
TestService service = serviceController.create().get();
|
||||
service.setMediaNotificationProvider(
|
||||
new DefaultMediaNotificationProvider(
|
||||
service,
|
||||
mediaSession -> 2000,
|
||||
DefaultMediaNotificationProvider.DEFAULT_CHANNEL_ID,
|
||||
DefaultMediaNotificationProvider.DEFAULT_CHANNEL_NAME_RESOURCE_ID));
|
||||
service.addSession(session);
|
||||
// Play media to create a notification.
|
||||
player.setMediaItems(
|
||||
ImmutableList.of(
|
||||
MediaItem.fromUri("asset:///media/mp4/sample.mp4"),
|
||||
MediaItem.fromUri("asset:///media/mp4/sample.mp4")));
|
||||
player.prepare();
|
||||
player.play();
|
||||
runMainLooperUntil(() -> notificationManager.getActiveNotifications().length == 1);
|
||||
|
||||
StatusBarNotification mediaNotification = getStatusBarNotification(2000);
|
||||
|
||||
assertThat(mediaNotification.getNotification().actions).hasLength(5);
|
||||
assertThat(mediaNotification.getNotification().actions[0].title.toString())
|
||||
.isEqualTo("Seek to previous item");
|
||||
assertThat(mediaNotification.getNotification().actions[1].title.toString()).isEqualTo("Pause");
|
||||
assertThat(mediaNotification.getNotification().actions[2].title.toString())
|
||||
.isEqualTo("Seek to next item");
|
||||
assertThat(mediaNotification.getNotification().actions[3].title.toString())
|
||||
.isEqualTo("customAction1");
|
||||
assertThat(mediaNotification.getNotification().actions[4].title.toString())
|
||||
.isEqualTo("customAction2");
|
||||
|
||||
player.pause();
|
||||
session.setMediaButtonPreferences(
|
||||
session.getMediaNotificationControllerInfo(), ImmutableList.of(button2));
|
||||
ShadowLooper.idleMainLooper();
|
||||
mediaNotification = getStatusBarNotification(2000);
|
||||
|
||||
assertThat(mediaNotification.getNotification().actions).hasLength(4);
|
||||
assertThat(mediaNotification.getNotification().actions[0].title.toString())
|
||||
.isEqualTo("Seek to previous item");
|
||||
assertThat(mediaNotification.getNotification().actions[1].title.toString()).isEqualTo("Play");
|
||||
assertThat(mediaNotification.getNotification().actions[2].title.toString())
|
||||
.isEqualTo("Seek to next item");
|
||||
assertThat(mediaNotification.getNotification().actions[3].title.toString())
|
||||
.isEqualTo("customAction2");
|
||||
session.release();
|
||||
player.release();
|
||||
serviceController.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mediaNotificationController_setAvailableCommands_correctNotificationActions()
|
||||
throws TimeoutException {
|
||||
@ -281,7 +387,7 @@ public class MediaSessionServiceTest {
|
||||
MediaSession session =
|
||||
new MediaSession.Builder(context, player)
|
||||
.setId("1")
|
||||
.setCustomLayout(ImmutableList.of(button1, button2))
|
||||
.setMediaButtonPreferences(ImmutableList.of(button1, button2))
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
|
@ -47,7 +47,8 @@ public class PlayerWrapperTest {
|
||||
new PlayerWrapper(
|
||||
player,
|
||||
/* playIfSuppressed= */ true,
|
||||
ImmutableList.of(),
|
||||
/* customLayout= */ ImmutableList.of(),
|
||||
/* mediaButtonPreferences= */ ImmutableList.of(),
|
||||
SessionCommands.EMPTY,
|
||||
Player.Commands.EMPTY,
|
||||
/* legacyExtras= */ null);
|
||||
|
@ -31,6 +31,7 @@ interface IRemoteMediaController {
|
||||
Bundle getConnectedSessionToken(String controllerId);
|
||||
Bundle getSessionExtras(String controllerId);
|
||||
Bundle getCustomLayout(String controllerId);
|
||||
Bundle getMediaButtonPreferences(String controllerId);
|
||||
Bundle getAvailableCommands(String controllerId);
|
||||
PendingIntent getSessionActivity(String controllerId);
|
||||
void play(String controllerId);
|
||||
|
@ -32,6 +32,7 @@ interface IRemoteMediaSession {
|
||||
void release(String sessionId);
|
||||
void setAvailableCommands(String sessionId, in Bundle sessionCommands, in Bundle playerCommands);
|
||||
void setCustomLayout(String sessionId, in List<Bundle> layout);
|
||||
void setMediaButtonPreferences(String sessionId, in List<Bundle> mediaButtonPreferences);
|
||||
void setSessionExtras(String sessionId, in Bundle extras);
|
||||
void setSessionExtrasForController(String sessionId, in String controllerKey, in Bundle extras);
|
||||
void sendError(String sessionId, String controllerKey, in Bundle SessionError);
|
||||
|
@ -1461,7 +1461,11 @@ public class MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest
|
||||
.build();
|
||||
}
|
||||
};
|
||||
MediaSession mediaSession = createMediaSession(player, callback, customLayout);
|
||||
MediaSession mediaSession =
|
||||
new MediaSession.Builder(ApplicationProvider.getApplicationContext(), player)
|
||||
.setCallback(callback)
|
||||
.setCustomLayout(customLayout)
|
||||
.build();
|
||||
connectMediaNotificationController(mediaSession);
|
||||
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
|
||||
|
||||
@ -1621,6 +1625,212 @@ public class MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest
|
||||
releasePlayer(player);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
playerWithMediaButtonPreferences_sessionBuiltWithMediaButtonPreferences_customActionsInInitialPlaybackState()
|
||||
throws Exception {
|
||||
Player player = createDefaultPlayer();
|
||||
Bundle extras1 = new Bundle();
|
||||
extras1.putString("key1", "value1");
|
||||
SessionCommand command1 = new SessionCommand("command1", extras1);
|
||||
SessionCommand command2 = new SessionCommand("command2", Bundle.EMPTY);
|
||||
SessionCommand command3 = new SessionCommand("command3", Bundle.EMPTY);
|
||||
ImmutableList<CommandButton> mediaButtonPreferences =
|
||||
ImmutableList.of(
|
||||
new CommandButton.Builder(CommandButton.ICON_PLAY)
|
||||
.setDisplayName("button1")
|
||||
.setSessionCommand(command1)
|
||||
.build(),
|
||||
new CommandButton.Builder(CommandButton.ICON_PAUSE)
|
||||
.setDisplayName("button2")
|
||||
.setSessionCommand(command2)
|
||||
.build(),
|
||||
new CommandButton.Builder(CommandButton.ICON_PAUSE)
|
||||
.setDisplayName("button3")
|
||||
.setEnabled(false)
|
||||
.setSessionCommand(command3)
|
||||
.build());
|
||||
MediaSession.Callback callback =
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public ConnectionResult onConnect(
|
||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
return new AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(
|
||||
ConnectionResult.DEFAULT_SESSION_COMMANDS
|
||||
.buildUpon()
|
||||
.add(command1)
|
||||
.add(command3)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
};
|
||||
MediaSession mediaSession =
|
||||
new MediaSession.Builder(ApplicationProvider.getApplicationContext(), player)
|
||||
.setCallback(callback)
|
||||
.setMediaButtonPreferences(mediaButtonPreferences)
|
||||
.build();
|
||||
connectMediaNotificationController(mediaSession);
|
||||
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
|
||||
|
||||
assertThat(controllerCompat.getPlaybackState().getCustomActions()).hasSize(1);
|
||||
PlaybackStateCompat.CustomAction customAction =
|
||||
controllerCompat.getPlaybackState().getCustomActions().get(0);
|
||||
assertThat(customAction.getAction()).isEqualTo("command1");
|
||||
assertThat(customAction.getName().toString()).isEqualTo("button1");
|
||||
assertThat(customAction.getIcon()).isEqualTo(R.drawable.media3_icon_play);
|
||||
assertThat(customAction.getExtras().get("key1")).isEqualTo("value1");
|
||||
assertThat(customAction.getExtras().get(MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT))
|
||||
.isEqualTo(CommandButton.ICON_PLAY);
|
||||
mediaSession.release();
|
||||
releasePlayer(player);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
playerWithMediaButtonPreferences_setMediaButtonPreferences_playbackStateChangedWithCustomActionsChanged()
|
||||
throws Exception {
|
||||
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);
|
||||
ImmutableList<CommandButton> mediaButtonPreferences =
|
||||
ImmutableList.of(
|
||||
new CommandButton.Builder(CommandButton.ICON_PLAY)
|
||||
.setDisplayName("button1")
|
||||
.setSessionCommand(command1)
|
||||
.build(),
|
||||
new CommandButton.Builder(CommandButton.ICON_PAUSE)
|
||||
.setDisplayName("button2")
|
||||
.setSessionCommand(command2)
|
||||
.build());
|
||||
MediaSession.Callback callback =
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public ConnectionResult onConnect(
|
||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
return new ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(
|
||||
ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(command1).build())
|
||||
.build();
|
||||
}
|
||||
};
|
||||
MediaSession mediaSession = createMediaSession(player, callback);
|
||||
connectMediaNotificationController(mediaSession);
|
||||
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
|
||||
List<PlaybackStateCompat.CustomAction> initialCustomActions =
|
||||
controllerCompat.getPlaybackState().getCustomActions();
|
||||
AtomicReference<List<PlaybackStateCompat.CustomAction>> reportedCustomActions =
|
||||
new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
controllerCompat.registerCallback(
|
||||
new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackStateCompat state) {
|
||||
reportedCustomActions.set(state.getCustomActions());
|
||||
latch.countDown();
|
||||
}
|
||||
},
|
||||
threadTestRule.getHandler());
|
||||
|
||||
getInstrumentation()
|
||||
.runOnMainSync(() -> mediaSession.setMediaButtonPreferences(mediaButtonPreferences));
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(initialCustomActions).isEmpty();
|
||||
assertThat(reportedCustomActions.get()).hasSize(1);
|
||||
assertThat(reportedCustomActions.get().get(0).getAction()).isEqualTo("command1");
|
||||
assertThat(reportedCustomActions.get().get(0).getName().toString()).isEqualTo("button1");
|
||||
assertThat(reportedCustomActions.get().get(0).getIcon()).isEqualTo(R.drawable.media3_icon_play);
|
||||
assertThat(reportedCustomActions.get().get(0).getExtras().get("key1")).isEqualTo("value1");
|
||||
assertThat(
|
||||
reportedCustomActions
|
||||
.get()
|
||||
.get(0)
|
||||
.getExtras()
|
||||
.get(MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT))
|
||||
.isEqualTo(CommandButton.ICON_PLAY);
|
||||
mediaSession.release();
|
||||
releasePlayer(player);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
playerWithMediaButtonPreferences_setMediaButtonPreferencesForMediaNotificationController_playbackStateChangedWithCustomActionsChanged()
|
||||
throws Exception {
|
||||
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);
|
||||
ImmutableList<CommandButton> mediaButtonPreferences =
|
||||
ImmutableList.of(
|
||||
new CommandButton.Builder(CommandButton.ICON_PLAY)
|
||||
.setDisplayName("button1")
|
||||
.setSessionCommand(command1)
|
||||
.build(),
|
||||
new CommandButton.Builder(CommandButton.ICON_PAUSE)
|
||||
.setDisplayName("button2")
|
||||
.setSessionCommand(command2)
|
||||
.build());
|
||||
MediaSession.Callback callback =
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public ConnectionResult onConnect(
|
||||
MediaSession session, MediaSession.ControllerInfo controller) {
|
||||
return new ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(
|
||||
ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().add(command1).build())
|
||||
.build();
|
||||
}
|
||||
};
|
||||
MediaSession mediaSession = createMediaSession(player, callback);
|
||||
connectMediaNotificationController(mediaSession);
|
||||
MediaControllerCompat controllerCompat = createMediaControllerCompat(mediaSession);
|
||||
List<PlaybackStateCompat.CustomAction> initialCustomActions =
|
||||
controllerCompat.getPlaybackState().getCustomActions();
|
||||
AtomicReference<List<PlaybackStateCompat.CustomAction>> reportedCustomActions =
|
||||
new AtomicReference<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
controllerCompat.registerCallback(
|
||||
new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackStateCompat state) {
|
||||
reportedCustomActions.set(state.getCustomActions());
|
||||
latch.countDown();
|
||||
}
|
||||
},
|
||||
threadTestRule.getHandler());
|
||||
|
||||
getInstrumentation()
|
||||
.runOnMainSync(
|
||||
() ->
|
||||
mediaSession.setMediaButtonPreferences(
|
||||
mediaSession.getMediaNotificationControllerInfo(), mediaButtonPreferences));
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(initialCustomActions).isEmpty();
|
||||
assertThat(reportedCustomActions.get()).hasSize(1);
|
||||
assertThat(reportedCustomActions.get().get(0).getAction()).isEqualTo("command1");
|
||||
assertThat(reportedCustomActions.get().get(0).getName().toString()).isEqualTo("button1");
|
||||
assertThat(reportedCustomActions.get().get(0).getIcon()).isEqualTo(R.drawable.media3_icon_play);
|
||||
assertThat(reportedCustomActions.get().get(0).getExtras().get("key1")).isEqualTo("value1");
|
||||
assertThat(
|
||||
reportedCustomActions
|
||||
.get()
|
||||
.get(0)
|
||||
.getExtras()
|
||||
.get(MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT))
|
||||
.isEqualTo(CommandButton.ICON_PLAY);
|
||||
mediaSession.release();
|
||||
releasePlayer(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a controller that mimics the media notification controller that is connected by {@link
|
||||
* MediaNotificationManager} when the session is running in the service.
|
||||
@ -1671,14 +1881,8 @@ public class MediaControllerCompatPlaybackStateCompatActionsWithMediaSessionTest
|
||||
|
||||
private static MediaSession createMediaSession(
|
||||
Player player, @Nullable MediaSession.Callback callback) {
|
||||
return createMediaSession(player, callback, /* customLayout= */ ImmutableList.of());
|
||||
}
|
||||
|
||||
private static MediaSession createMediaSession(
|
||||
Player player, @Nullable MediaSession.Callback callback, List<CommandButton> customLayout) {
|
||||
MediaSession.Builder session =
|
||||
new MediaSession.Builder(ApplicationProvider.getApplicationContext(), player)
|
||||
.setCustomLayout(customLayout);
|
||||
new MediaSession.Builder(ApplicationProvider.getApplicationContext(), player);
|
||||
if (callback != null) {
|
||||
session.setCallback(callback);
|
||||
}
|
||||
|
@ -552,6 +552,74 @@ public class MediaControllerListenerWithMediaSessionCompatTest {
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaButtonPreferences() throws Exception {
|
||||
CommandButton button1 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button1")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command1", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button2 =
|
||||
new CommandButton.Builder(CommandButton.ICON_FAST_FORWARD)
|
||||
.setDisplayName("button2")
|
||||
.setSessionCommand(new SessionCommand("command2", Bundle.EMPTY))
|
||||
.build();
|
||||
ConditionVariable onMediaButtonPreferencesChangedCalled = new ConditionVariable();
|
||||
List<List<CommandButton>> onMediaButtonPreferencesChangedArguments = new ArrayList<>();
|
||||
List<List<CommandButton>> mediaButtonPreferencesFromGetter = new ArrayList<>();
|
||||
controllerTestRule.createController(
|
||||
session.getSessionToken(),
|
||||
new MediaController.Listener() {
|
||||
@Override
|
||||
public void onMediaButtonPreferencesChanged(
|
||||
MediaController controller, List<CommandButton> mediaButtonPreferences) {
|
||||
onMediaButtonPreferencesChangedArguments.add(mediaButtonPreferences);
|
||||
mediaButtonPreferencesFromGetter.add(controller.getMediaButtonPreferences());
|
||||
onMediaButtonPreferencesChangedCalled.open();
|
||||
}
|
||||
});
|
||||
Bundle extras1 = new Bundle();
|
||||
extras1.putString("key", "value-1");
|
||||
PlaybackStateCompat.CustomAction customAction1 =
|
||||
new PlaybackStateCompat.CustomAction.Builder(
|
||||
"command1", "button1", /* icon= */ R.drawable.media3_notification_small_icon)
|
||||
.setExtras(extras1)
|
||||
.build();
|
||||
Bundle extras2 = new Bundle();
|
||||
extras2.putString("key", "value-2");
|
||||
extras2.putInt(
|
||||
MediaConstants.EXTRAS_KEY_COMMAND_BUTTON_ICON_COMPAT, CommandButton.ICON_FAST_FORWARD);
|
||||
PlaybackStateCompat.CustomAction customAction2 =
|
||||
new PlaybackStateCompat.CustomAction.Builder(
|
||||
"command2", "button2", /* icon= */ R.drawable.media3_icon_fast_forward)
|
||||
.setExtras(extras2)
|
||||
.build();
|
||||
PlaybackStateCompat.Builder playbackState1 =
|
||||
new PlaybackStateCompat.Builder()
|
||||
.addCustomAction(customAction1)
|
||||
.addCustomAction(customAction2);
|
||||
PlaybackStateCompat.Builder playbackState2 =
|
||||
new PlaybackStateCompat.Builder().addCustomAction(customAction1);
|
||||
|
||||
session.setPlaybackState(playbackState1.build());
|
||||
assertThat(onMediaButtonPreferencesChangedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
onMediaButtonPreferencesChangedCalled.close();
|
||||
session.setPlaybackState(playbackState2.build());
|
||||
assertThat(onMediaButtonPreferencesChangedCalled.block(TIMEOUT_MS)).isTrue();
|
||||
|
||||
ImmutableList<CommandButton> expectedFirstMediaButtonPreferences =
|
||||
ImmutableList.of(button1.copyWithIsEnabled(true), button2.copyWithIsEnabled(true));
|
||||
ImmutableList<CommandButton> expectedSecondMediaButtonPreferences =
|
||||
ImmutableList.of(button1.copyWithIsEnabled(true));
|
||||
assertThat(onMediaButtonPreferencesChangedArguments)
|
||||
.containsExactly(expectedFirstMediaButtonPreferences, expectedSecondMediaButtonPreferences)
|
||||
.inOrder();
|
||||
assertThat(mediaButtonPreferencesFromGetter)
|
||||
.containsExactly(expectedFirstMediaButtonPreferences, expectedSecondMediaButtonPreferences)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentPosition_unknownPlaybackPosition_convertedToZero() throws Exception {
|
||||
session.setPlaybackState(
|
||||
|
@ -405,6 +405,7 @@ public class MediaControllerTest {
|
||||
button3.copyWithIsEnabled(false),
|
||||
button4.copyWithIsEnabled(true))
|
||||
.inOrder();
|
||||
session.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -452,6 +453,7 @@ public class MediaControllerTest {
|
||||
assertThat(getterCustomLayouts).hasSize(2);
|
||||
assertThat(getterCustomLayouts.get(0)).containsExactly(button.copyWithIsEnabled(false));
|
||||
assertThat(getterCustomLayouts.get(1)).containsExactly(button.copyWithIsEnabled(true));
|
||||
session.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -550,6 +552,369 @@ public class MediaControllerTest {
|
||||
session.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMediaButtonPreferences_mediaButtonPreferencesBuiltWithSession_includedOnConnect()
|
||||
throws Exception {
|
||||
RemoteMediaSession session =
|
||||
createRemoteMediaSession(TEST_GET_CUSTOM_LAYOUT, /* tokenExtras= */ null);
|
||||
CommandButton button1 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button1")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command1", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button2 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button2")
|
||||
.setEnabled(false)
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command2", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button3 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button3")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command3", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button4 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button4")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
|
||||
.build();
|
||||
CommandButton button5 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button5")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setPlayerCommand(Player.COMMAND_GET_TRACKS)
|
||||
.build();
|
||||
setupMediaButtonPreferences(
|
||||
session, ImmutableList.of(button1, button2, button3, button4, button5));
|
||||
MediaController controller = controllerTestRule.createController(session.getToken());
|
||||
|
||||
assertThat(threadTestRule.getHandler().postAndSync(controller::getMediaButtonPreferences))
|
||||
.containsExactly(
|
||||
button1.copyWithIsEnabled(true),
|
||||
button2.copyWithIsEnabled(false),
|
||||
button3.copyWithIsEnabled(false),
|
||||
button4.copyWithIsEnabled(true),
|
||||
button5.copyWithIsEnabled(false))
|
||||
.inOrder();
|
||||
|
||||
session.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
getMediaButtonPreferences_sessionSetMediaButtonPreferences_mediaButtonPreferencesChanged()
|
||||
throws Exception {
|
||||
RemoteMediaSession session =
|
||||
createRemoteMediaSession(TEST_GET_CUSTOM_LAYOUT, /* tokenExtras= */ null);
|
||||
CommandButton button1 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button1")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command1", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button2 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button2")
|
||||
.setEnabled(false)
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command2", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button3 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button3")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command3", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button4 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button4")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command4", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button5 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button5")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
|
||||
.build();
|
||||
CommandButton button6 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button6")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setPlayerCommand(Player.COMMAND_GET_TRACKS)
|
||||
.build();
|
||||
setupMediaButtonPreferences(session, ImmutableList.of(button1, button3));
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<List<CommandButton>> reportedMediaButtonPreferences = new AtomicReference<>();
|
||||
MediaController controller =
|
||||
controllerTestRule.createController(
|
||||
session.getToken(),
|
||||
Bundle.EMPTY,
|
||||
new MediaController.Listener() {
|
||||
@Override
|
||||
public void onMediaButtonPreferencesChanged(
|
||||
MediaController controller1, List<CommandButton> layout) {
|
||||
reportedMediaButtonPreferences.set(layout);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
ImmutableList<CommandButton> initialMediaButtonPreferencesFromGetter =
|
||||
threadTestRule.getHandler().postAndSync(controller::getMediaButtonPreferences);
|
||||
session.setMediaButtonPreferences(
|
||||
ImmutableList.of(button1, button2, button4, button5, button6));
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
|
||||
ImmutableList<CommandButton> newMediaButtonPreferencesFromGetter =
|
||||
threadTestRule.getHandler().postAndSync(controller::getMediaButtonPreferences);
|
||||
|
||||
assertThat(initialMediaButtonPreferencesFromGetter)
|
||||
.containsExactly(button1.copyWithIsEnabled(true), button3.copyWithIsEnabled(false))
|
||||
.inOrder();
|
||||
ImmutableList<CommandButton> expectedNewButtons =
|
||||
ImmutableList.of(
|
||||
button1.copyWithIsEnabled(true),
|
||||
button2.copyWithIsEnabled(false),
|
||||
button4.copyWithIsEnabled(false),
|
||||
button5.copyWithIsEnabled(true),
|
||||
button6.copyWithIsEnabled(false));
|
||||
assertThat(newMediaButtonPreferencesFromGetter)
|
||||
.containsExactlyElementsIn(expectedNewButtons)
|
||||
.inOrder();
|
||||
assertThat(reportedMediaButtonPreferences.get())
|
||||
.containsExactlyElementsIn(expectedNewButtons)
|
||||
.inOrder();
|
||||
session.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
getMediaButtonPreferences_setAvailableCommandsOnSession_reportsMediaButtonPreferencesChanged()
|
||||
throws Exception {
|
||||
RemoteMediaSession session = createRemoteMediaSession(TEST_GET_CUSTOM_LAYOUT, null);
|
||||
CommandButton button1 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button1")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command1", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button2 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button2")
|
||||
.setEnabled(false)
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command2", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button3 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button3")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
|
||||
.build();
|
||||
CommandButton button4 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button4")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setPlayerCommand(Player.COMMAND_GET_TRACKS)
|
||||
.build();
|
||||
setupMediaButtonPreferences(session, ImmutableList.of(button1, button2, button3, button4));
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
List<List<CommandButton>> reportedMediaButtonPreferencesChanged = new ArrayList<>();
|
||||
List<List<CommandButton>> getterMediaButtonPreferencesChanged = new ArrayList<>();
|
||||
MediaController.Listener listener =
|
||||
new MediaController.Listener() {
|
||||
@Override
|
||||
public void onMediaButtonPreferencesChanged(
|
||||
MediaController controller, List<CommandButton> layout) {
|
||||
reportedMediaButtonPreferencesChanged.add(layout);
|
||||
getterMediaButtonPreferencesChanged.add(controller.getMediaButtonPreferences());
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
MediaController controller =
|
||||
controllerTestRule.createController(
|
||||
session.getToken(), /* connectionHints= */ Bundle.EMPTY, listener);
|
||||
ImmutableList<CommandButton> initialMediaButtonPreferences =
|
||||
threadTestRule.getHandler().postAndSync(controller::getMediaButtonPreferences);
|
||||
|
||||
// Remove commands in custom layout from available commands.
|
||||
session.setAvailableCommands(SessionCommands.EMPTY, Player.Commands.EMPTY);
|
||||
// Add one sesion and player command back.
|
||||
session.setAvailableCommands(
|
||||
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(initialMediaButtonPreferences)
|
||||
.containsExactly(
|
||||
button1.copyWithIsEnabled(true),
|
||||
button2.copyWithIsEnabled(false),
|
||||
button3.copyWithIsEnabled(true),
|
||||
button4.copyWithIsEnabled(false));
|
||||
assertThat(reportedMediaButtonPreferencesChanged).hasSize(2);
|
||||
assertThat(reportedMediaButtonPreferencesChanged.get(0))
|
||||
.containsExactly(
|
||||
button1.copyWithIsEnabled(false),
|
||||
button2.copyWithIsEnabled(false),
|
||||
button3.copyWithIsEnabled(false),
|
||||
button4.copyWithIsEnabled(false))
|
||||
.inOrder();
|
||||
assertThat(reportedMediaButtonPreferencesChanged.get(1))
|
||||
.containsExactly(
|
||||
button1.copyWithIsEnabled(false),
|
||||
button2.copyWithIsEnabled(false),
|
||||
button3.copyWithIsEnabled(false),
|
||||
button4.copyWithIsEnabled(true))
|
||||
.inOrder();
|
||||
assertThat(getterMediaButtonPreferencesChanged).hasSize(2);
|
||||
assertThat(getterMediaButtonPreferencesChanged.get(0))
|
||||
.containsExactly(
|
||||
button1.copyWithIsEnabled(false),
|
||||
button2.copyWithIsEnabled(false),
|
||||
button3.copyWithIsEnabled(false),
|
||||
button4.copyWithIsEnabled(false))
|
||||
.inOrder();
|
||||
assertThat(getterMediaButtonPreferencesChanged.get(1))
|
||||
.containsExactly(
|
||||
button1.copyWithIsEnabled(false),
|
||||
button2.copyWithIsEnabled(false),
|
||||
button3.copyWithIsEnabled(false),
|
||||
button4.copyWithIsEnabled(true))
|
||||
.inOrder();
|
||||
session.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
getMediaButtonPreferences_setAvailableCommandsOnPlayer_reportsMediaButtonPreferencesChanged()
|
||||
throws Exception {
|
||||
RemoteMediaSession session = createRemoteMediaSession(TEST_GET_CUSTOM_LAYOUT, null);
|
||||
CommandButton button =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
|
||||
.build();
|
||||
setupMediaButtonPreferences(session, ImmutableList.of(button));
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
List<List<CommandButton>> reportedMediaButtonPreferences = new ArrayList<>();
|
||||
List<List<CommandButton>> getterMediaButtonPreferences = new ArrayList<>();
|
||||
MediaController.Listener listener =
|
||||
new MediaController.Listener() {
|
||||
@Override
|
||||
public void onMediaButtonPreferencesChanged(
|
||||
MediaController controller, List<CommandButton> layout) {
|
||||
reportedMediaButtonPreferences.add(layout);
|
||||
getterMediaButtonPreferences.add(controller.getMediaButtonPreferences());
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
MediaController controller =
|
||||
controllerTestRule.createController(
|
||||
session.getToken(), /* connectionHints= */ Bundle.EMPTY, listener);
|
||||
ImmutableList<CommandButton> initialMediaButtonPreferences =
|
||||
threadTestRule.getHandler().postAndSync(controller::getMediaButtonPreferences);
|
||||
|
||||
// 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(initialMediaButtonPreferences).containsExactly(button.copyWithIsEnabled(true));
|
||||
assertThat(reportedMediaButtonPreferences).hasSize(2);
|
||||
assertThat(reportedMediaButtonPreferences.get(0))
|
||||
.containsExactly(button.copyWithIsEnabled(false));
|
||||
assertThat(reportedMediaButtonPreferences.get(1))
|
||||
.containsExactly(button.copyWithIsEnabled(true));
|
||||
assertThat(getterMediaButtonPreferences).hasSize(2);
|
||||
assertThat(getterMediaButtonPreferences.get(0))
|
||||
.containsExactly(button.copyWithIsEnabled(false));
|
||||
assertThat(getterMediaButtonPreferences.get(1)).containsExactly(button.copyWithIsEnabled(true));
|
||||
session.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
getMediaButtonPreferences_sessionSetMediaButtonPreferencesNoChange_listenerNotCalledWithEqualPreferences()
|
||||
throws Exception {
|
||||
RemoteMediaSession session =
|
||||
createRemoteMediaSession(TEST_GET_CUSTOM_LAYOUT, /* tokenExtras= */ null);
|
||||
CommandButton button1 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button1")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command1", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button2 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button2")
|
||||
.setEnabled(false)
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command2", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button3 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button3")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command3", Bundle.EMPTY))
|
||||
.build();
|
||||
CommandButton button4 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button4")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(new SessionCommand("command4", Bundle.EMPTY))
|
||||
.build();
|
||||
setupMediaButtonPreferences(session, ImmutableList.of(button1, button2));
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
List<List<CommandButton>> reportedMediaButtonPreferences = new ArrayList<>();
|
||||
List<List<CommandButton>> getterMediaButtonPreferences = new ArrayList<>();
|
||||
MediaController.Listener listener =
|
||||
new MediaController.Listener() {
|
||||
@Override
|
||||
public void onMediaButtonPreferencesChanged(
|
||||
MediaController controller, List<CommandButton> layout) {
|
||||
reportedMediaButtonPreferences.add(layout);
|
||||
getterMediaButtonPreferences.add(controller.getMediaButtonPreferences());
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
MediaController controller =
|
||||
controllerTestRule.createController(session.getToken(), Bundle.EMPTY, listener);
|
||||
ImmutableList<CommandButton> initialMediaButtonPreferences =
|
||||
threadTestRule.getHandler().postAndSync(controller::getMediaButtonPreferences);
|
||||
|
||||
// First call does not trigger onMediaButtonPreferencesChanged.
|
||||
session.setMediaButtonPreferences(ImmutableList.of(button1, button2));
|
||||
session.setMediaButtonPreferences(ImmutableList.of(button3, button4));
|
||||
session.setMediaButtonPreferences(ImmutableList.of(button1, button2));
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
CommandButton button1Enabled = button1.copyWithIsEnabled(true);
|
||||
CommandButton button2Disabled = button2.copyWithIsEnabled(false);
|
||||
CommandButton button3Disabled = button3.copyWithIsEnabled(false);
|
||||
CommandButton button4Disabled = button4.copyWithIsEnabled(false);
|
||||
assertThat(initialMediaButtonPreferences)
|
||||
.containsExactly(button1Enabled, button2Disabled)
|
||||
.inOrder();
|
||||
assertThat(reportedMediaButtonPreferences)
|
||||
.containsExactly(
|
||||
ImmutableList.of(button3Disabled, button4Disabled),
|
||||
ImmutableList.of(button1Enabled, button2Disabled))
|
||||
.inOrder();
|
||||
assertThat(getterMediaButtonPreferences)
|
||||
.containsExactly(
|
||||
ImmutableList.of(button3Disabled, button4Disabled),
|
||||
ImmutableList.of(button1Enabled, button2Disabled))
|
||||
.inOrder();
|
||||
session.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCommandButtonsForMediaItem() throws Exception {
|
||||
RemoteMediaSession session =
|
||||
@ -2035,4 +2400,22 @@ public class MediaControllerTest {
|
||||
session.setCustomLayout(ImmutableList.copyOf(customLayout));
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
}
|
||||
|
||||
private void setupMediaButtonPreferences(
|
||||
RemoteMediaSession session, List<CommandButton> mediaButtonPreferences)
|
||||
throws RemoteException, InterruptedException, Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
controllerTestRule.createController(
|
||||
session.getToken(),
|
||||
/* connectionHints= */ null,
|
||||
new MediaController.Listener() {
|
||||
@Override
|
||||
public void onMediaButtonPreferencesChanged(
|
||||
MediaController controller, List<CommandButton> layout) {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
session.setMediaButtonPreferences(ImmutableList.copyOf(mediaButtonPreferences));
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ public class MediaSessionCallbackTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onConnect_acceptWithMissingSessionCommand_buttonDisabledAndPermissionDenied()
|
||||
public void onConnect_setCustomLayoutWithMissingSessionCommand_buttonDisabledAndPermissionDenied()
|
||||
throws Exception {
|
||||
CommandButton button1 =
|
||||
new CommandButton.Builder(CommandButton.ICON_PLAY)
|
||||
@ -169,7 +169,6 @@ public class MediaSessionCallbackTest {
|
||||
.setSessionCommand(new SessionCommand("command2", Bundle.EMPTY))
|
||||
.setEnabled(true)
|
||||
.build();
|
||||
ImmutableList<CommandButton> customLayout = ImmutableList.of(button1, button2);
|
||||
MediaSession.Callback callback =
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
@ -193,12 +192,7 @@ public class MediaSessionCallbackTest {
|
||||
};
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player)
|
||||
.setCallback(callback)
|
||||
.setCustomLayout(customLayout)
|
||||
.setId(
|
||||
"onConnect_acceptWithMissingSessionCommand_buttonDisabledAndPermissionDenied")
|
||||
.build());
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController remoteController =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
@ -211,6 +205,60 @@ public class MediaSessionCallbackTest {
|
||||
.isEqualTo(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
onConnect_setMediaButtonPreferencesWithMissingSessionCommand_buttonDisabledAndPermissionDenied()
|
||||
throws Exception {
|
||||
CommandButton button1 =
|
||||
new CommandButton.Builder(CommandButton.ICON_PLAY)
|
||||
.setDisplayName("button1")
|
||||
.setSessionCommand(new SessionCommand("command1", Bundle.EMPTY))
|
||||
.setEnabled(true)
|
||||
.build();
|
||||
CommandButton button1Disabled = button1.copyWithIsEnabled(false);
|
||||
CommandButton button2 =
|
||||
new CommandButton.Builder(CommandButton.ICON_PAUSE)
|
||||
.setDisplayName("button2")
|
||||
.setSessionCommand(new SessionCommand("command2", Bundle.EMPTY))
|
||||
.setEnabled(true)
|
||||
.build();
|
||||
MediaSession.Callback callback =
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
return new AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(
|
||||
new SessionCommands.Builder().add(button2.sessionCommand).build())
|
||||
.setMediaButtonPreferences(ImmutableList.of(button1, button2))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<SessionResult> onCustomCommand(
|
||||
MediaSession session,
|
||||
ControllerInfo controller,
|
||||
SessionCommand customCommand,
|
||||
Bundle args) {
|
||||
return Futures.immediateFuture(new SessionResult(RESULT_SUCCESS));
|
||||
}
|
||||
};
|
||||
MediaSession session =
|
||||
sessionTestRule.ensureReleaseAfterTest(
|
||||
new MediaSession.Builder(context, player).setCallback(callback).build());
|
||||
RemoteMediaController remoteController =
|
||||
remoteControllerTestRule.createRemoteController(session.getToken());
|
||||
|
||||
ImmutableList<CommandButton> mediaButtonPreferences =
|
||||
remoteController.getMediaButtonPreferences();
|
||||
|
||||
assertThat(mediaButtonPreferences).containsExactly(button1Disabled, button2).inOrder();
|
||||
assertThat(remoteController.sendCustomCommand(button1.sessionCommand, Bundle.EMPTY).resultCode)
|
||||
.isEqualTo(ERROR_PERMISSION_DENIED);
|
||||
assertThat(remoteController.sendCustomCommand(button2.sessionCommand, Bundle.EMPTY).resultCode)
|
||||
.isEqualTo(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onConnect_emptyPlayerCommands_commandReleaseAlwaysIncluded() throws Exception {
|
||||
MediaSession.Callback callback =
|
||||
|
@ -28,6 +28,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import androidx.media3.common.ForwardingPlayer;
|
||||
import androidx.media3.common.MediaItem;
|
||||
@ -236,8 +237,7 @@ public class MediaSessionServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCreate_mediaNotificationManagerController_correctSessionStateFromOnConnect()
|
||||
throws Exception {
|
||||
public void onCreate_withCustomLayout_correctSessionStateFromOnConnect() throws Exception {
|
||||
SessionCommand command1 = new SessionCommand("command1", Bundle.EMPTY);
|
||||
SessionCommand command2 = new SessionCommand("command2", Bundle.EMPTY);
|
||||
SessionCommand command3 = new SessionCommand("command3", Bundle.EMPTY);
|
||||
@ -349,6 +349,121 @@ public class MediaSessionServiceTest {
|
||||
.blockUntilAllControllersUnbind(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onCreate_withMediaButtonPreferences_correctSessionStateFromOnConnect()
|
||||
throws Exception {
|
||||
SessionCommand command1 = new SessionCommand("command1", Bundle.EMPTY);
|
||||
SessionCommand command2 = new SessionCommand("command2", Bundle.EMPTY);
|
||||
SessionCommand command3 = new SessionCommand("command3", Bundle.EMPTY);
|
||||
CommandButton button1 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button1")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(command1)
|
||||
.build();
|
||||
CommandButton button2 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button2")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(command2)
|
||||
.build();
|
||||
CommandButton button3 =
|
||||
new CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setDisplayName("button3")
|
||||
.setIconResId(R.drawable.media3_notification_small_icon)
|
||||
.setSessionCommand(command3)
|
||||
.build();
|
||||
Bundle testHints = new Bundle();
|
||||
testHints.putString("test_key", "test_value");
|
||||
List<ControllerInfo> controllerInfoList = new ArrayList<>();
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
TestHandler handler = new TestHandler(Looper.getMainLooper());
|
||||
ExoPlayer player =
|
||||
handler.postAndSync(
|
||||
() -> {
|
||||
ExoPlayer exoPlayer = new TestExoPlayerBuilder(context).build();
|
||||
exoPlayer.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4"));
|
||||
exoPlayer.prepare();
|
||||
return exoPlayer;
|
||||
});
|
||||
MediaSession mediaSession =
|
||||
new MediaSession.Builder(ApplicationProvider.getApplicationContext(), player)
|
||||
.setMediaButtonPreferences(Lists.newArrayList(button1, button2))
|
||||
.setCallback(
|
||||
new MediaSession.Callback() {
|
||||
@Override
|
||||
public MediaSession.ConnectionResult onConnect(
|
||||
MediaSession session, ControllerInfo controller) {
|
||||
controllerInfoList.add(controller);
|
||||
if (session.isMediaNotificationController(controller)) {
|
||||
latch.countDown();
|
||||
return new MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(
|
||||
SessionCommands.EMPTY.buildUpon().add(command1).add(command3).build())
|
||||
.setAvailablePlayerCommands(Player.Commands.EMPTY)
|
||||
.setMediaButtonPreferences(ImmutableList.of(button1, button3))
|
||||
.build();
|
||||
}
|
||||
latch.countDown();
|
||||
return new MediaSession.ConnectionResult.AcceptedResultBuilder(session).build();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
TestServiceRegistry.getInstance().setOnGetSessionHandler(controllerInfo -> mediaSession);
|
||||
MediaControllerCompat mediaControllerCompat =
|
||||
new MediaControllerCompat(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
MediaSessionCompat.Token.fromToken(mediaSession.getPlatformToken()));
|
||||
CountDownLatch controllerReady = new CountDownLatch(1);
|
||||
mediaControllerCompat.registerCallback(
|
||||
new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onSessionReady() {
|
||||
controllerReady.countDown();
|
||||
}
|
||||
},
|
||||
new Handler(Looper.getMainLooper()));
|
||||
controllerReady.await();
|
||||
List<PlaybackStateCompat.CustomAction> initialCustomActionsInControllerCompat =
|
||||
mediaControllerCompat.getPlaybackState().getCustomActions();
|
||||
|
||||
// Start the service by creating a remote controller.
|
||||
RemoteMediaController remoteController =
|
||||
controllerTestRule.createRemoteController(token, /* waitForConnection= */ true, testHints);
|
||||
|
||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||
assertThat(
|
||||
controllerInfoList
|
||||
.get(0)
|
||||
.getConnectionHints()
|
||||
.getBoolean(
|
||||
MediaController.KEY_MEDIA_NOTIFICATION_CONTROLLER_FLAG,
|
||||
/* defaultValue= */ false))
|
||||
.isTrue();
|
||||
assertThat(TestUtils.equals(controllerInfoList.get(1).getConnectionHints(), testHints))
|
||||
.isTrue();
|
||||
assertThat(mediaControllerCompat.getPlaybackState().getActions())
|
||||
.isEqualTo(PlaybackStateCompat.ACTION_SET_RATING);
|
||||
assertThat(remoteController.getMediaButtonPreferences())
|
||||
.containsExactly(button1.copyWithIsEnabled(false), button2.copyWithIsEnabled(false))
|
||||
.inOrder();
|
||||
assertThat(initialCustomActionsInControllerCompat).isEmpty();
|
||||
assertThat(mediaControllerCompat.getPlaybackState().getCustomActions()).hasSize(2);
|
||||
PlaybackStateCompat.CustomAction customAction1 =
|
||||
mediaControllerCompat.getPlaybackState().getCustomActions().get(0);
|
||||
PlaybackStateCompat.CustomAction customAction2 =
|
||||
mediaControllerCompat.getPlaybackState().getCustomActions().get(1);
|
||||
assertThat(customAction1.getAction()).isEqualTo("command1");
|
||||
assertThat(customAction1.getName().toString()).isEqualTo("button1");
|
||||
assertThat(customAction1.getIcon()).isEqualTo(R.drawable.media3_notification_small_icon);
|
||||
assertThat(customAction2.getAction()).isEqualTo("command3");
|
||||
assertThat(customAction2.getName().toString()).isEqualTo("button3");
|
||||
assertThat(customAction2.getIcon()).isEqualTo(R.drawable.media3_notification_small_icon);
|
||||
mediaSession.release();
|
||||
((MockMediaSessionService) TestServiceRegistry.getInstance().getServiceInstance())
|
||||
.blockUntilAllControllersUnbind(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether {@link MediaSessionService#onGetSession(ControllerInfo)} is called when
|
||||
* controller tries to connect, with the proper arguments.
|
||||
|
@ -866,6 +866,20 @@ public class MediaControllerProviderService extends Service {
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getMediaButtonPreferences(String controllerId) throws RemoteException {
|
||||
MediaController controller = mediaControllerMap.get(controllerId);
|
||||
ArrayList<Bundle> mediaButtonPreferences = new ArrayList<>();
|
||||
ImmutableList<CommandButton> commandButtons =
|
||||
runOnHandler(controller::getMediaButtonPreferences);
|
||||
for (CommandButton button : commandButtons) {
|
||||
mediaButtonPreferences.add(button.toBundle());
|
||||
}
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(KEY_COMMAND_BUTTON_LIST, mediaButtonPreferences);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getAvailableCommands(String controllerId) throws RemoteException {
|
||||
MediaController controller = mediaControllerMap.get(controllerId);
|
||||
|
@ -622,6 +622,24 @@ public class MediaSessionProviderService extends Service {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("FutureReturnValueIgnored")
|
||||
public void setMediaButtonPreferences(String sessionId, List<Bundle> mediaButtonPreferences)
|
||||
throws RemoteException {
|
||||
if (mediaButtonPreferences == null) {
|
||||
return;
|
||||
}
|
||||
runOnHandler(
|
||||
() -> {
|
||||
ImmutableList.Builder<CommandButton> builder = new ImmutableList.Builder<>();
|
||||
for (Bundle bundle : mediaButtonPreferences) {
|
||||
builder.add(CommandButton.fromBundle(bundle, MediaSessionStub.VERSION_INT));
|
||||
}
|
||||
MediaSession session = sessionMap.get(sessionId);
|
||||
session.setMediaButtonPreferences(builder.build());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionExtras(String sessionId, Bundle extras) throws RemoteException {
|
||||
runOnHandler(() -> sessionMap.get(sessionId).setSessionExtras(extras));
|
||||
|
@ -389,6 +389,17 @@ public class RemoteMediaController {
|
||||
return customLayout.build();
|
||||
}
|
||||
|
||||
public ImmutableList<CommandButton> getMediaButtonPreferences() throws RemoteException {
|
||||
Bundle mediaButtonPreferencesBundle = binder.getMediaButtonPreferences(controllerId);
|
||||
ArrayList<Bundle> list =
|
||||
mediaButtonPreferencesBundle.getParcelableArrayList(KEY_COMMAND_BUTTON_LIST);
|
||||
ImmutableList.Builder<CommandButton> mediaButtonPreferences = new ImmutableList.Builder<>();
|
||||
for (Bundle bundle : list) {
|
||||
mediaButtonPreferences.add(CommandButton.fromBundle(bundle, MediaSessionStub.VERSION_INT));
|
||||
}
|
||||
return mediaButtonPreferences.build();
|
||||
}
|
||||
|
||||
public Player.Commands getAvailableCommands() throws RemoteException {
|
||||
Bundle commandsBundle = binder.getAvailableCommands(controllerId);
|
||||
return Player.Commands.fromBundle(commandsBundle);
|
||||
|
@ -201,6 +201,15 @@ public class RemoteMediaSession {
|
||||
binder.setCustomLayout(sessionId, bundleList);
|
||||
}
|
||||
|
||||
public void setMediaButtonPreferences(List<CommandButton> mediaButtonPreferences)
|
||||
throws RemoteException {
|
||||
List<Bundle> bundleList = new ArrayList<>();
|
||||
for (CommandButton button : mediaButtonPreferences) {
|
||||
bundleList.add(button.toBundle());
|
||||
}
|
||||
binder.setMediaButtonPreferences(sessionId, bundleList);
|
||||
}
|
||||
|
||||
public void setSessionExtras(Bundle extras) throws RemoteException {
|
||||
binder.setSessionExtras(sessionId, extras);
|
||||
}
|
||||
|
@ -62,6 +62,12 @@ public final class TestMediaBrowserListener implements MediaBrowser.Listener {
|
||||
delegate.onCustomLayoutChanged(controller, layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaButtonPreferencesChanged(
|
||||
MediaController controller, List<CommandButton> mediaButtonPreferences) {
|
||||
delegate.onMediaButtonPreferencesChanged(controller, mediaButtonPreferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExtrasChanged(MediaController controller, Bundle extras) {
|
||||
delegate.onExtrasChanged(controller, extras);
|
||||
|
Loading…
x
Reference in New Issue
Block a user