/*
* Copyright 2021 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 androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.Bundleable;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.CheckReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
/**
* A button for a {@link SessionCommand} or {@link Player.Command} that can be displayed by
* controllers.
*
* @see MediaSession#setCustomLayout(MediaSession.ControllerInfo, List)
* @see MediaController.Listener#onCustomLayoutChanged(MediaController, List)
*/
public final class CommandButton implements Bundleable {
// TODO: b/328238954 - Stabilize these constants and the corresponding methods, and deprecate the
// methods that do not use these constants.
/** An icon constant for a button. Must be one of the {@code CommandButton.ICON_} constants. */
@UnstableApi
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
ICON_UNDEFINED,
ICON_PLAY,
ICON_PAUSE,
ICON_STOP,
ICON_NEXT,
ICON_PREVIOUS,
ICON_SKIP_FORWARD,
ICON_SKIP_FORWARD_5,
ICON_SKIP_FORWARD_10,
ICON_SKIP_FORWARD_15,
ICON_SKIP_FORWARD_30,
ICON_SKIP_BACK,
ICON_SKIP_BACK_5,
ICON_SKIP_BACK_10,
ICON_SKIP_BACK_15,
ICON_SKIP_BACK_30,
ICON_FAST_FORWARD,
ICON_REWIND,
ICON_REPEAT_ALL,
ICON_REPEAT_ONE,
ICON_REPEAT_OFF,
ICON_SHUFFLE_ON,
ICON_SHUFFLE_OFF,
ICON_SHUFFLE_STAR,
ICON_HEART_FILLED,
ICON_HEART_UNFILLED,
ICON_STAR_FILLED,
ICON_STAR_UNFILLED,
ICON_BOOKMARK_FILLED,
ICON_BOOKMARK_UNFILLED,
ICON_THUMB_UP_FILLED,
ICON_THUMB_UP_UNFILLED,
ICON_THUMB_DOWN_FILLED,
ICON_THUMB_DOWN_UNFILLED,
ICON_FLAG_FILLED,
ICON_FLAG_UNFILLED,
ICON_PLUS,
ICON_MINUS,
ICON_PLAYLIST_ADD,
ICON_PLAYLIST_REMOVE,
ICON_QUEUE_ADD,
ICON_QUEUE_NEXT,
ICON_QUEUE_REMOVE,
ICON_BLOCK,
ICON_PLUS_CIRCLE_FILLED,
ICON_PLUS_CIRCLE_UNFILLED,
ICON_MINUS_CIRCLE_FILLED,
ICON_MINUS_CIRCLE_UNFILLED,
ICON_CHECK_CIRCLE_FILLED,
ICON_CHECK_CIRCLE_UNFILLED,
ICON_PLAYBACK_SPEED,
ICON_PLAYBACK_SPEED_0_5,
ICON_PLAYBACK_SPEED_0_8,
ICON_PLAYBACK_SPEED_1_0,
ICON_PLAYBACK_SPEED_1_2,
ICON_PLAYBACK_SPEED_1_5,
ICON_PLAYBACK_SPEED_1_8,
ICON_PLAYBACK_SPEED_2_0,
ICON_SETTINGS,
ICON_QUALITY,
ICON_SUBTITLES,
ICON_SUBTITLES_OFF,
ICON_CLOSED_CAPTIONS,
ICON_CLOSED_CAPTIONS_OFF,
ICON_SYNC,
ICON_SHARE,
ICON_VOLUME_UP,
ICON_VOLUME_DOWN,
ICON_VOLUME_OFF,
ICON_ARTIST,
ICON_ALBUM,
ICON_RADIO,
ICON_SIGNAL,
ICON_FEED
})
public @interface Icon {}
// Note: The constant values of these icons matches the Material Design code points.
/**
* An icon constant representing an undefined icon, for example a custom icon not covered by the
* existing constants.
*/
@UnstableApi public static final int ICON_UNDEFINED = 0;
/** An icon showing a play symbol (a right facing triangle). */
@UnstableApi public static final int ICON_PLAY = 0xe037;
/** An icon showing a pause symbol (two vertical bars). */
@UnstableApi public static final int ICON_PAUSE = 0xe034;
/** An icon showing a stop symbol (a square). */
@UnstableApi public static final int ICON_STOP = 0xe047;
/** An icon showing a next symbol (a right facing triangle with a vertical bar). */
@UnstableApi public static final int ICON_NEXT = 0xe044;
/** An icon showing a previous symbol (a left facing triangle with a vertical bar). */
@UnstableApi public static final int ICON_PREVIOUS = 0xe045;
/** An icon showing a skip forward symbol (an open clock-wise arrow). */
@UnstableApi public static final int ICON_SKIP_FORWARD = 0xf6f4;
/**
* An icon showing a skip forward 5 seconds symbol (an open clockwise arrow with the number 5).
*/
@UnstableApi public static final int ICON_SKIP_FORWARD_5 = 0xe058;
/**
* An icon showing a skip forward 10 seconds symbol (an open clockwise arrow with the number 10).
*/
@UnstableApi public static final int ICON_SKIP_FORWARD_10 = 0xe056;
/**
* An icon showing a skip forward 15 seconds symbol (an open clockwise arrow with the number 15).
*/
@UnstableApi public static final int ICON_SKIP_FORWARD_15 = 0xfe056;
/**
* An icon showing a skip forward 30 seconds symbol (an open clockwise arrow with the number 30).
*/
@UnstableApi public static final int ICON_SKIP_FORWARD_30 = 0xe057;
/** An icon showing a skip back symbol (an open anti-clockwise arrow). */
@UnstableApi public static final int ICON_SKIP_BACK = 0xe042;
/**
* An icon showing a skip back 5 seconds symbol (an open anti-clockwise arrow with the number 5).
*/
@UnstableApi public static final int ICON_SKIP_BACK_5 = 0xe05b;
/**
* An icon showing a skip back 10 seconds symbol (an open anti-clockwise arrow with the number
* 10).
*/
@UnstableApi public static final int ICON_SKIP_BACK_10 = 0xe059;
/**
* An icon showing a skip back 15 seconds symbol (an open anti-clockwise arrow with the number
* 15).
*/
@UnstableApi public static final int ICON_SKIP_BACK_15 = 0xfe059;
/**
* An icon showing a skip back 30 seconds symbol (an open anti-clockwise arrow with the number
* 30).
*/
@UnstableApi public static final int ICON_SKIP_BACK_30 = 0xe05a;
/** An icon showing a fast forward symbol (two right facing triangles). */
@UnstableApi public static final int ICON_FAST_FORWARD = 0xe01f;
/** An icon showing a rewind symbol (two left facing triangles). */
@UnstableApi public static final int ICON_REWIND = 0xe020;
/** An icon showing a repeat all symbol (two open clockwise arrows). */
@UnstableApi public static final int ICON_REPEAT_ALL = 0xe040;
/** An icon showing a repeat one symbol (two open clockwise arrows with an overlaid number 1). */
@UnstableApi public static final int ICON_REPEAT_ONE = 0xe041;
/**
* An icon showing a disabled repeat symbol (two open clockwise arrows, in a color representing a
* disabled state).
*/
@UnstableApi public static final int ICON_REPEAT_OFF = 0xfe040;
/** An icon showing a shuffle symbol (two diagonal upward and downward facing arrows). */
@UnstableApi public static final int ICON_SHUFFLE_ON = 0xe043;
/**
* An icon showing a disabled shuffle symbol (two diagonal upward and downward facing arrows, in a
* color representing a disabled state).
*/
@UnstableApi public static final int ICON_SHUFFLE_OFF = 0xfe044;
/**
* An icon showing a shuffle symbol with a start (two diagonal upward and downward facing arrows
* with an overlaid star).
*/
@UnstableApi public static final int ICON_SHUFFLE_STAR = 0xfe043;
/** An icon showing a filled heart symbol. */
@UnstableApi public static final int ICON_HEART_FILLED = 0xfe87d;
/** An icon showing an unfilled heart symbol. */
@UnstableApi public static final int ICON_HEART_UNFILLED = 0xe87d;
/** An icon showing a filled star symbol. */
@UnstableApi public static final int ICON_STAR_FILLED = 0xfe838;
/** An icon showing an unfilled star symbol. */
@UnstableApi public static final int ICON_STAR_UNFILLED = 0xe838;
/** An icon showing a filled bookmark symbol. */
@UnstableApi public static final int ICON_BOOKMARK_FILLED = 0xfe866;
/** An icon showing an unfilled bookmark symbol. */
@UnstableApi public static final int ICON_BOOKMARK_UNFILLED = 0xe866;
/** An icon showing a filled thumb-up symbol. */
@UnstableApi public static final int ICON_THUMB_UP_FILLED = 0xfe8dc;
/** An icon showing an unfilled thumb-up symbol. */
@UnstableApi public static final int ICON_THUMB_UP_UNFILLED = 0xe8dc;
/** An icon showing a filled thumb-down symbol. */
@UnstableApi public static final int ICON_THUMB_DOWN_FILLED = 0xfe8db;
/** An icon showing an unfilled thumb-down symbol. */
@UnstableApi public static final int ICON_THUMB_DOWN_UNFILLED = 0xe8db;
/** An icon showing a filled flag symbol. */
@UnstableApi public static final int ICON_FLAG_FILLED = 0xfe153;
/** An icon showing an unfilled flag symbol. */
@UnstableApi public static final int ICON_FLAG_UNFILLED = 0xe153;
/** An icon showing a plus symbol. */
@UnstableApi public static final int ICON_PLUS = 0xe145;
/** An icon showing a minus symbol. */
@UnstableApi public static final int ICON_MINUS = 0xe15b;
/** An icon showing an add to playlist symbol (multiple horizontal bars with a small plus). */
@UnstableApi public static final int ICON_PLAYLIST_ADD = 0xe03b;
/**
* An icon showing an remove from playlist symbol (multiple horizontal bars with a small minus).
*/
@UnstableApi public static final int ICON_PLAYLIST_REMOVE = 0xeb80;
/** An icon showing an add to queue symbol (a stylized TV with a plus). */
@UnstableApi public static final int ICON_QUEUE_ADD = 0xe05c;
/**
* An icon showing a play next queue item symbol (a stylized TV with a plus and a right-facing
* arrow).
*/
@UnstableApi public static final int ICON_QUEUE_NEXT = 0xe066;
/** An icon showing a remove from queue symbol (a stylized TV with a minus). */
@UnstableApi public static final int ICON_QUEUE_REMOVE = 0xe067;
/** An icon showing a block symbol (a circle with a diagonal line). */
@UnstableApi public static final int ICON_BLOCK = 0xe14b;
/** An icon showing a filled circle with a plus. */
@UnstableApi public static final int ICON_PLUS_CIRCLE_FILLED = 0xfe147;
/** An icon showing an unfilled circle with a plus. */
@UnstableApi public static final int ICON_PLUS_CIRCLE_UNFILLED = 0xe147;
/** An icon showing a filled circle with a minus. */
@UnstableApi public static final int ICON_MINUS_CIRCLE_FILLED = 0xfe148;
/** An icon showing an unfilled circle with a minus. */
@UnstableApi public static final int ICON_MINUS_CIRCLE_UNFILLED = 0xfe149;
/** An icon showing a filled circle with a check mark. */
@UnstableApi public static final int ICON_CHECK_CIRCLE_FILLED = 0xfe86c;
/** An icon showing a unfilled circle with a check mark. */
@UnstableApi public static final int ICON_CHECK_CIRCLE_UNFILLED = 0xe86c;
/**
* An icon showing a playback speed symbol (a right facing triangle in a circle with half-dashed,
* half-solid contour).
*/
@UnstableApi public static final int ICON_PLAYBACK_SPEED = 0xe068;
/** An icon showing a 0.5x speed symbol. */
@UnstableApi public static final int ICON_PLAYBACK_SPEED_0_5 = 0xf4e2;
/** An icon showing a 0.8x speed symbol. */
@UnstableApi public static final int ICON_PLAYBACK_SPEED_0_8 = 0xff4e2;
/** An icon showing a 1.0x speed symbol. */
@UnstableApi public static final int ICON_PLAYBACK_SPEED_1_0 = 0xefcd;
/** An icon showing a 1.2x speed symbol. */
@UnstableApi public static final int ICON_PLAYBACK_SPEED_1_2 = 0xf4e1;
/** An icon showing a 1.5x speed symbol. */
@UnstableApi public static final int ICON_PLAYBACK_SPEED_1_5 = 0xf4e0;
/** An icon showing a 1.8x speed symbol. */
@UnstableApi public static final int ICON_PLAYBACK_SPEED_1_8 = 0xff4e0;
/** An icon showing a 2.0x speed symbol. */
@UnstableApi public static final int ICON_PLAYBACK_SPEED_2_0 = 0xf4eb;
/** An icon showing a settings symbol (a stylized cog). */
@UnstableApi public static final int ICON_SETTINGS = 0xe8b8;
/** An icon showing a quality selection symbol (multiple horizontal bars with sliders). */
@UnstableApi public static final int ICON_QUALITY = 0xe429;
/** An icon showing a subtitles symbol (a rectangle filled with dots and horizontal lines). */
@UnstableApi public static final int ICON_SUBTITLES = 0xe048;
/**
* An icon showing a subtitles off symbol (a rectangle filled with dots and horizontal lines, with
* a large diagonal line across).
*/
@UnstableApi public static final int ICON_SUBTITLES_OFF = 0xef72;
/** An icon showing a closed caption symbol (a rectangle with the letters CC). */
@UnstableApi public static final int ICON_CLOSED_CAPTIONS = 0xe01c;
/**
* An icon showing a closed caption off symbol (a rectangle with the letters CC, with a large
* diagonal line across).
*/
@UnstableApi public static final int ICON_CLOSED_CAPTIONS_OFF = 0xf1dc;
/** An icon showing a sync symbol (two open anti-clockwise arrows). */
@UnstableApi public static final int ICON_SYNC = 0xe627;
/**
* An icon showing a share symbol (three dots connected by two diagonal lines, open on the right).
*/
@UnstableApi public static final int ICON_SHARE = 0xe80d;
/** An icon showing a volume up symbol (a stylized speaker with multiple sound waves). */
@UnstableApi public static final int ICON_VOLUME_UP = 0xe050;
/** An icon showing a volume down symbol (a stylized speaker with a single small sound wave). */
@UnstableApi public static final int ICON_VOLUME_DOWN = 0xe04d;
/**
* An icon showing a volume off symbol (a stylized speaker with multiple sound waves, with a large
* diagonal line across).
*/
@UnstableApi public static final int ICON_VOLUME_OFF = 0xe04f;
/** An icon showing an artist symbol (a stylized person with a musical note). */
@UnstableApi public static final int ICON_ARTIST = 0xe01a;
/** An icon showing an album symbol (a stylized LP record). */
@UnstableApi public static final int ICON_ALBUM = 0xe019;
/** An icon showing a radio symbol (left and right facing sound waves). */
@UnstableApi public static final int ICON_RADIO = 0xe51e;
/** An icon showing an signal symbol (a vertical mast with circular sounds waves). */
@UnstableApi public static final int ICON_SIGNAL = 0xf048;
/**
* An icon showing an feed symbol (a dot in the bottom-left with multiple concentric quarter
* circles).
*/
@UnstableApi public static final int ICON_FEED = 0xe0e5;
/** A builder for {@link CommandButton}. */
public static final class Builder {
@Nullable private SessionCommand sessionCommand;
private @Player.Command int playerCommand;
private @Icon int icon;
@DrawableRes private int iconResId;
@Nullable private Uri iconUri;
private CharSequence displayName;
private Bundle extras;
private boolean enabled;
/**
* [will be deprecated] Use {@link #Builder(int)} instead to define the {@link Icon} for this
* button. A separate resource id via {@link #setIconResId(int)} or {@link #setIconUri} is no
* longer required unless for {@link #ICON_UNDEFINED}.
*/
public Builder() {
this(ICON_UNDEFINED);
}
/**
* Creates a builder.
*
* @param icon The {@link Icon} that should be shown for this button.
*/
@UnstableApi
public Builder(@Icon int icon) {
this(icon, getIconResIdForIconConstant(icon));
}
// Internal version of constructor that assigns an additionally known icon resource id
// immediately. This is needed for R8 resource shrinking efficiency to know that the icon
// doesn't need to be resolved to any of the bundled icon drawables.
/* package */ Builder(@Icon int icon, @DrawableRes int iconResId) {
this.icon = icon;
this.iconResId = iconResId;
displayName = "";
extras = Bundle.EMPTY;
playerCommand = Player.COMMAND_INVALID;
icon = ICON_UNDEFINED;
enabled = true;
}
/**
* Sets the {@link SessionCommand} that is required to be {@linkplain
* MediaController#isSessionCommandAvailable available} when the button is clicked.
*
* <p>Cannot set this if a player command is already set via {@link #setPlayerCommand(int)}.
*
* @param sessionCommand The {@link SessionCommand}.
* @return This builder for chaining.
*/
@CanIgnoreReturnValue
public Builder setSessionCommand(SessionCommand sessionCommand) {
checkNotNull(sessionCommand, "sessionCommand should not be null.");
checkArgument(
playerCommand == Player.COMMAND_INVALID,
"playerCommands is already set. Only one of sessionCommand and playerCommand should be"
+ " set.");
this.sessionCommand = sessionCommand;
return this;
}
/**
* Sets the {@link Player.Command} that is required to be {@linkplain
* MediaController#isCommandAvailable available} when the button is clicked.
*
* <p>Cannot set this if a session command is already set via {@link
* #setSessionCommand(SessionCommand)}.
*
* @param playerCommand The {@link Player.Command}.
* @return This builder for chaining.
*/
@CanIgnoreReturnValue
public Builder setPlayerCommand(@Player.Command int playerCommand) {
checkArgument(
sessionCommand == null,
"sessionCommand is already set. Only one of sessionCommand and playerCommand should be"
+ " set.");
this.playerCommand = playerCommand;
return this;
}
/**
* [will be deprecated] The icon should be defined with the constructor {@link Icon} parameter
* in {@link #Builder(int)} instead. Only in case the existing list of icons is not sufficient,
* use {@link #ICON_UNDEFINED} and set a separate resource id with {@link #setCustomIconResId}.
*/
@CanIgnoreReturnValue
public Builder setIconResId(@DrawableRes int resId) {
return setCustomIconResId(resId);
}
/**
* Sets the resource id of an icon that is used when the predefined {@link Icon} is not
* available or set to {@link #ICON_UNDEFINED}.
*
* @param resId The resource id of a custom icon.
* @return This builder for chaining.
*/
@UnstableApi
@CanIgnoreReturnValue
public Builder setCustomIconResId(@DrawableRes int resId) {
iconResId = resId;
return this;
}
/**
* Sets a {@link Uri} for the icon of this button.
*
* <p>Note that this {@link Uri} may be used when the predefined {@link Icon} is not available
* or set to {@link #ICON_UNDEFINED}. It can be used in addition or instead of {@link
* #setCustomIconResId} for consumers that are capable of loading the {@link Uri}.
*
* @param uri The uri to an icon.
* @return This builder for chaining.
*/
@UnstableApi
@CanIgnoreReturnValue
public Builder setIconUri(Uri uri) {
this.iconUri = uri;
return this;
}
/**
* Sets a display name of this button.
*
* @param displayName The display name.
* @return This builder for chaining.
*/
@CanIgnoreReturnValue
public Builder setDisplayName(CharSequence displayName) {
this.displayName = displayName;
return this;
}
/**
* Sets whether the button is enabled.
*
* <p>Note that this value will be set to {@code false} for {@link MediaController} instances if
* the corresponding command is not available to this controller (see {@link #setPlayerCommand}
* and {@link #setSessionCommand}).
*
* <p>The default value is {@code true}.
*
* @param enabled Whether the button is enabled.
* @return This builder for chaining.
*/
@CanIgnoreReturnValue
public Builder setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}
/**
* Sets an extra {@link Bundle} of this button.
*
* @param extras The extra {@link Bundle}.
* @return This builder for chaining.
*/
@CanIgnoreReturnValue
public Builder setExtras(Bundle extras) {
this.extras = new Bundle(extras);
return this;
}
/** Builds a {@link CommandButton}. */
public CommandButton build() {
checkState(
(sessionCommand == null) != (playerCommand == Player.COMMAND_INVALID),
"Exactly one of sessionCommand and playerCommand should be set");
return new CommandButton(
sessionCommand, playerCommand, icon, iconResId, iconUri, displayName, extras, enabled);
}
@DrawableRes
private static int getIconResIdForIconConstant(@Icon int icon) {
switch (icon) {
case ICON_PLAY:
return R.drawable.media3_icon_play;
case ICON_PAUSE:
return R.drawable.media3_icon_pause;
case ICON_STOP:
return R.drawable.media3_icon_stop;
case ICON_NEXT:
return R.drawable.media3_icon_next;
case ICON_PREVIOUS:
return R.drawable.media3_icon_previous;
case ICON_SKIP_FORWARD:
return R.drawable.media3_icon_skip_forward;
case ICON_SKIP_FORWARD_5:
return R.drawable.media3_icon_skip_forward_5;
case ICON_SKIP_FORWARD_10:
return R.drawable.media3_icon_skip_forward_10;
case ICON_SKIP_FORWARD_15:
return R.drawable.media3_icon_skip_forward_15;
case ICON_SKIP_FORWARD_30:
return R.drawable.media3_icon_skip_forward_30;
case ICON_SKIP_BACK:
return R.drawable.media3_icon_skip_back;
case ICON_SKIP_BACK_5:
return R.drawable.media3_icon_skip_back_5;
case ICON_SKIP_BACK_10:
return R.drawable.media3_icon_skip_back_10;
case ICON_SKIP_BACK_15:
return R.drawable.media3_icon_skip_back_15;
case ICON_SKIP_BACK_30:
return R.drawable.media3_icon_skip_back_30;
case ICON_FAST_FORWARD:
return R.drawable.media3_icon_fast_forward;
case ICON_REWIND:
return R.drawable.media3_icon_rewind;
case ICON_REPEAT_ALL:
return R.drawable.media3_icon_repeat_all;
case ICON_REPEAT_ONE:
return R.drawable.media3_icon_repeat_one;
case ICON_REPEAT_OFF:
return R.drawable.media3_icon_repeat_off;
case ICON_SHUFFLE_ON:
return R.drawable.media3_icon_shuffle_on;
case ICON_SHUFFLE_OFF:
return R.drawable.media3_icon_shuffle_off;
case ICON_SHUFFLE_STAR:
return R.drawable.media3_icon_shuffle_star;
case ICON_HEART_FILLED:
return R.drawable.media3_icon_heart_filled;
case ICON_HEART_UNFILLED:
return R.drawable.media3_icon_heart_unfilled;
case ICON_STAR_FILLED:
return R.drawable.media3_icon_star_filled;
case ICON_STAR_UNFILLED:
return R.drawable.media3_icon_star_unfilled;
case ICON_BOOKMARK_FILLED:
return R.drawable.media3_icon_bookmark_filled;
case ICON_BOOKMARK_UNFILLED:
return R.drawable.media3_icon_bookmark_unfilled;
case ICON_THUMB_UP_FILLED:
return R.drawable.media3_icon_thumb_up_filled;
case ICON_THUMB_UP_UNFILLED:
return R.drawable.media3_icon_thumb_up_unfilled;
case ICON_THUMB_DOWN_FILLED:
return R.drawable.media3_icon_thumb_down_filled;
case ICON_THUMB_DOWN_UNFILLED:
return R.drawable.media3_icon_thumb_down_unfilled;
case ICON_FLAG_FILLED:
return R.drawable.media3_icon_flag_filled;
case ICON_FLAG_UNFILLED:
return R.drawable.media3_icon_flag_unfilled;
case ICON_PLUS:
return R.drawable.media3_icon_plus;
case ICON_MINUS:
return R.drawable.media3_icon_minus;
case ICON_PLAYLIST_ADD:
return R.drawable.media3_icon_playlist_add;
case ICON_PLAYLIST_REMOVE:
return R.drawable.media3_icon_playlist_remove;
case ICON_QUEUE_ADD:
return R.drawable.media3_icon_queue_add;
case ICON_QUEUE_NEXT:
return R.drawable.media3_icon_queue_next;
case ICON_QUEUE_REMOVE:
return R.drawable.media3_icon_queue_remove;
case ICON_BLOCK:
return R.drawable.media3_icon_block;
case ICON_PLUS_CIRCLE_FILLED:
return R.drawable.media3_icon_plus_circle_filled;
case ICON_PLUS_CIRCLE_UNFILLED:
return R.drawable.media3_icon_plus_circle_unfilled;
case ICON_MINUS_CIRCLE_FILLED:
return R.drawable.media3_icon_minus_circle_filled;
case ICON_MINUS_CIRCLE_UNFILLED:
return R.drawable.media3_icon_minus_circle_unfilled;
case ICON_CHECK_CIRCLE_FILLED:
return R.drawable.media3_icon_check_circle_filled;
case ICON_CHECK_CIRCLE_UNFILLED:
return R.drawable.media3_icon_check_circle_unfilled;
case ICON_PLAYBACK_SPEED:
return R.drawable.media3_icon_playback_speed;
case ICON_PLAYBACK_SPEED_0_5:
return R.drawable.media3_icon_playback_speed_0_5;
case ICON_PLAYBACK_SPEED_0_8:
return R.drawable.media3_icon_playback_speed_0_8;
case ICON_PLAYBACK_SPEED_1_0:
return R.drawable.media3_icon_playback_speed_1_0;
case ICON_PLAYBACK_SPEED_1_2:
return R.drawable.media3_icon_playback_speed_1_2;
case ICON_PLAYBACK_SPEED_1_5:
return R.drawable.media3_icon_playback_speed_1_5;
case ICON_PLAYBACK_SPEED_1_8:
return R.drawable.media3_icon_playback_speed_1_8;
case ICON_PLAYBACK_SPEED_2_0:
return R.drawable.media3_icon_playback_speed_2_0;
case ICON_SETTINGS:
return R.drawable.media3_icon_settings;
case ICON_QUALITY:
return R.drawable.media3_icon_quality;
case ICON_SUBTITLES:
return R.drawable.media3_icon_subtitles;
case ICON_SUBTITLES_OFF:
return R.drawable.media3_icon_subtitles_off;
case ICON_CLOSED_CAPTIONS:
return R.drawable.media3_icon_closed_captions;
case ICON_CLOSED_CAPTIONS_OFF:
return R.drawable.media3_icon_closed_captions_off;
case ICON_SYNC:
return R.drawable.media3_icon_sync;
case ICON_SHARE:
return R.drawable.media3_icon_share;
case ICON_VOLUME_UP:
return R.drawable.media3_icon_volume_up;
case ICON_VOLUME_DOWN:
return R.drawable.media3_icon_volume_down;
case ICON_VOLUME_OFF:
return R.drawable.media3_icon_volume_off;
case ICON_ARTIST:
return R.drawable.media3_icon_artist;
case ICON_ALBUM:
return R.drawable.media3_icon_album;
case ICON_RADIO:
return R.drawable.media3_icon_radio;
case ICON_SIGNAL:
return R.drawable.media3_icon_signal;
case ICON_FEED:
return R.drawable.media3_icon_feed;
default:
return 0;
}
}
}
/** The session command of the button. Will be {@code null} if {@link #playerCommand} is set. */
@Nullable public final SessionCommand sessionCommand;
/**
* The {@link Player.Command} command of the button. Will be {@link Player#COMMAND_INVALID} if
* {@link #sessionCommand} is set.
*/
public final @Player.Command int playerCommand;
/** The {@link Icon} of the button. */
@UnstableApi public final @Icon int icon;
/**
* The icon resource id of the button that is used when the predefined {@link #icon} is not
* available or set to {@link #ICON_UNDEFINED}. Can be {@code 0} if not needed.
*/
@DrawableRes public final int iconResId;
/**
* The {@link Uri} for the icon of the button that is used when the predefined {@link #icon} is
* not available or set to {@link #ICON_UNDEFINED}. Can be {@code null}.
*
* <p>Note that this value can be used in addition or instead of {@link #iconResId} for consumers
* that are capable of loading the {@link Uri}.
*/
@UnstableApi @Nullable public final Uri iconUri;
/**
* The display name of the button. Can be empty if the command is predefined and a custom name
* isn't needed.
*/
public final CharSequence displayName;
/**
* The extra {@link Bundle} of the button. It's private information between session and
* controller.
*/
@UnstableApi public final Bundle extras;
/**
* Whether the button is enabled.
*
* <p>Note that this value will be set to {@code false} for {@link MediaController} instances if
* the corresponding command is not available to this controller (see {@link #playerCommand} and
* {@link #sessionCommand}).
*/
public final boolean isEnabled;
private CommandButton(
@Nullable SessionCommand sessionCommand,
@Player.Command int playerCommand,
@Icon int icon,
@DrawableRes int iconResId,
@Nullable Uri iconUri,
CharSequence displayName,
Bundle extras,
boolean enabled) {
this.sessionCommand = sessionCommand;
this.playerCommand = playerCommand;
this.icon = icon;
this.iconResId = iconResId;
this.iconUri = iconUri;
this.displayName = displayName;
this.extras = new Bundle(extras);
this.isEnabled = enabled;
}
/** Returns a copy with the new {@link #isEnabled} flag. */
@CheckReturnValue
/* package */ CommandButton copyWithIsEnabled(boolean isEnabled) {
// Because this method is supposed to be used by the library only, this method has been chosen
// over the conventional `buildUpon` approach. This aims for keeping this separate from the
// public Builder-API used by apps.
if (this.isEnabled == isEnabled) {
return this;
}
return new CommandButton(
sessionCommand,
playerCommand,
icon,
iconResId,
iconUri,
displayName,
new Bundle(extras),
isEnabled);
}
/** Checks the given command button for equality while ignoring {@link #extras}. */
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CommandButton)) {
return false;
}
CommandButton button = (CommandButton) obj;
return Objects.equal(sessionCommand, button.sessionCommand)
&& playerCommand == button.playerCommand
&& icon == button.icon
&& iconResId == button.iconResId
&& Objects.equal(iconUri, button.iconUri)
&& TextUtils.equals(displayName, button.displayName)
&& isEnabled == button.isEnabled;
}
@Override
public int hashCode() {
return Objects.hashCode(
sessionCommand, playerCommand, icon, iconResId, displayName, isEnabled, iconUri);
}
/**
* Returns a list of command buttons with the {@link CommandButton#isEnabled} flag set to false if
* the corresponding command is not available.
*/
/* package */ static ImmutableList<CommandButton> copyWithUnavailableButtonsDisabled(
List<CommandButton> commandButtons,
SessionCommands sessionCommands,
Player.Commands playerCommands) {
ImmutableList.Builder<CommandButton> updatedButtons = new ImmutableList.Builder<>();
for (int i = 0; i < commandButtons.size(); i++) {
CommandButton button = commandButtons.get(i);
if (isButtonCommandAvailable(button, sessionCommands, playerCommands)) {
updatedButtons.add(button);
} else {
updatedButtons.add(button.copyWithIsEnabled(false));
}
}
return updatedButtons.build();
}
/**
* Returns whether the required command ({@link #playerCommand} or {@link #sessionCommand}) for
* the button is available.
*
* @param button The command button.
* @param sessionCommands The available session commands.
* @param playerCommands The available player commands.
* @return Whether the command required for this button is available.
*/
/* package */ static boolean isButtonCommandAvailable(
CommandButton button, SessionCommands sessionCommands, Player.Commands playerCommands) {
return (button.sessionCommand != null && sessionCommands.contains(button.sessionCommand))
|| (button.playerCommand != Player.COMMAND_INVALID
&& playerCommands.contains(button.playerCommand));
}
// Bundleable implementation.
private static final String FIELD_SESSION_COMMAND = Util.intToStringMaxRadix(0);
private static final String FIELD_PLAYER_COMMAND = Util.intToStringMaxRadix(1);
private static final String FIELD_ICON_RES_ID = Util.intToStringMaxRadix(2);
private static final String FIELD_DISPLAY_NAME = Util.intToStringMaxRadix(3);
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(4);
private static final String FIELD_ENABLED = Util.intToStringMaxRadix(5);
private static final String FIELD_ICON_URI = Util.intToStringMaxRadix(6);
private static final String FIELD_ICON = Util.intToStringMaxRadix(7);
@UnstableApi
@Override
public Bundle toBundle() {
Bundle bundle = new Bundle();
if (sessionCommand != null) {
bundle.putBundle(FIELD_SESSION_COMMAND, sessionCommand.toBundle());
}
if (playerCommand != Player.COMMAND_INVALID) {
bundle.putInt(FIELD_PLAYER_COMMAND, playerCommand);
}
if (icon != ICON_UNDEFINED) {
bundle.putInt(FIELD_ICON, icon);
}
if (iconResId != 0) {
bundle.putInt(FIELD_ICON_RES_ID, iconResId);
}
if (displayName != "") {
bundle.putCharSequence(FIELD_DISPLAY_NAME, displayName);
}
if (!extras.isEmpty()) {
bundle.putBundle(FIELD_EXTRAS, extras);
}
if (iconUri != null) {
bundle.putParcelable(FIELD_ICON_URI, iconUri);
}
if (!isEnabled) {
bundle.putBoolean(FIELD_ENABLED, isEnabled);
}
return bundle;
}
/**
* Object that can restore {@code CommandButton} from a {@link Bundle}.
*
* @deprecated Use {@link #fromBundle} instead.
*/
@UnstableApi
@Deprecated
@SuppressWarnings("deprecation") // Deprecated instance of deprecated class
public static final Creator<CommandButton> CREATOR = CommandButton::fromBundle;
/**
* @deprecated Use {@link #fromBundle(Bundle, int)} instead.
*/
@Deprecated
@UnstableApi
public static CommandButton fromBundle(Bundle bundle) {
return fromBundle(bundle, MediaSessionStub.VERSION_INT);
}
/** Restores a {@code CommandButton} from a {@link Bundle}. */
@UnstableApi
public static CommandButton fromBundle(Bundle bundle, int sessionInterfaceVersion) {
@Nullable Bundle sessionCommandBundle = bundle.getBundle(FIELD_SESSION_COMMAND);
@Nullable
SessionCommand sessionCommand =
sessionCommandBundle == null ? null : SessionCommand.fromBundle(sessionCommandBundle);
@Player.Command
int playerCommand =
bundle.getInt(FIELD_PLAYER_COMMAND, /* defaultValue= */ Player.COMMAND_INVALID);
int iconResId = bundle.getInt(FIELD_ICON_RES_ID, /* defaultValue= */ 0);
CharSequence displayName = bundle.getCharSequence(FIELD_DISPLAY_NAME, /* defaultValue= */ "");
@Nullable Bundle extras = bundle.getBundle(FIELD_EXTRAS);
// Before sessionInterfaceVersion == 3, the session expected this value to be meaningless and we
// can only assume it was meant to be true.
boolean enabled =
sessionInterfaceVersion < 3 || bundle.getBoolean(FIELD_ENABLED, /* defaultValue= */ true);
@Nullable Uri iconUri = bundle.getParcelable(FIELD_ICON_URI);
@Icon int icon = bundle.getInt(FIELD_ICON, /* defaultValue= */ ICON_UNDEFINED);
Builder builder = new Builder(icon, iconResId);
if (sessionCommand != null) {
builder.setSessionCommand(sessionCommand);
}
if (playerCommand != Player.COMMAND_INVALID) {
builder.setPlayerCommand(playerCommand);
}
if (iconUri != null) {
builder.setIconUri(iconUri);
}
return builder
.setDisplayName(displayName)
.setExtras(extras == null ? Bundle.EMPTY : extras)
.setEnabled(enabled)
.build();
}
}