32
32
import androidx .media3 .common .util .Util ;
33
33
import com .google .common .base .Objects ;
34
34
import com .google .common .collect .ImmutableList ;
35
+ import com .google .common .primitives .ImmutableIntArray ;
35
36
import com .google .errorprone .annotations .CanIgnoreReturnValue ;
36
37
import com .google .errorprone .annotations .CheckReturnValue ;
37
38
import java .lang .annotation .Documented ;
@@ -411,17 +412,68 @@ public final class CommandButton {
411
412
*/
412
413
@ UnstableApi public static final int ICON_FEED = 0xe0e5 ;
413
414
415
+ // TODO: b/332877990 - Stabilize these constants and other slot APIs
416
+ /**
417
+ * A slot at which a button can be displayed in a UI surface. Must be one of the {@code
418
+ * CommandButton.SLOT_} constants.
419
+ */
420
+ @ UnstableApi
421
+ @ Documented
422
+ @ Retention (RetentionPolicy .SOURCE )
423
+ @ Target (TYPE_USE )
424
+ @ IntDef ({
425
+ SLOT_CENTRAL ,
426
+ SLOT_BACK ,
427
+ SLOT_FORWARD ,
428
+ SLOT_BACK_SECONDARY ,
429
+ SLOT_FORWARD_SECONDARY ,
430
+ SLOT_OVERFLOW
431
+ })
432
+ public @interface Slot {}
433
+
434
+ /** A central slot in a playback control UI, most commonly used for play or pause actions. */
435
+ @ UnstableApi public static final int SLOT_CENTRAL = 1 ;
436
+
437
+ /**
438
+ * A slot in a playback control UI for backward-directed playback actions, most commonly used for
439
+ * previous or rewind actions.
440
+ */
441
+ @ UnstableApi public static final int SLOT_BACK = 2 ;
442
+
443
+ /**
444
+ * A slot in a playback control UI for forward-directed playback actions, most commonly used for
445
+ * next or fast-forward actions.
446
+ */
447
+ @ UnstableApi public static final int SLOT_FORWARD = 3 ;
448
+
449
+ /**
450
+ * A slot in a playback control UI for secondary backward-directed playback actions, most commonly
451
+ * used for previous or rewind actions.
452
+ */
453
+ @ UnstableApi public static final int SLOT_BACK_SECONDARY = 4 ;
454
+
455
+ /**
456
+ * A slot in a playback control UI for secondary forward-directed playback actions, most commonly
457
+ * used for next or fast-forward actions.
458
+ */
459
+ @ UnstableApi public static final int SLOT_FORWARD_SECONDARY = 5 ;
460
+
461
+ /** A slot in a playback control UI for additional actions that don't fit into other slots. */
462
+ @ UnstableApi public static final int SLOT_OVERFLOW = 6 ;
463
+
414
464
/** A builder for {@link CommandButton}. */
415
465
public static final class Builder {
416
466
467
+ private final @ Icon int icon ;
468
+
417
469
@ Nullable private SessionCommand sessionCommand ;
418
470
private @ Player .Command int playerCommand ;
419
- private @ Icon int icon ;
420
471
@ DrawableRes private int iconResId ;
421
472
@ Nullable private Uri iconUri ;
422
473
private CharSequence displayName ;
423
474
private Bundle extras ;
424
475
private boolean enabled ;
476
+ @ Nullable private ImmutableIntArray slots ;
425
477
426
478
/**
427
479
* [will be deprecated] Use {@link #Builder(int)} instead to define the {@link Icon} for this
@@ -451,7 +503,6 @@ public Builder(@Icon int icon) {
451
503
displayName = "" ;
452
504
extras = Bundle .EMPTY ;
453
505
playerCommand = Player .COMMAND_INVALID ;
454
- icon = ICON_UNDEFINED ;
455
506
enabled = true ;
456
507
}
457
508
@@ -581,13 +632,63 @@ public Builder setExtras(Bundle extras) {
581
632
return this ;
582
633
}
583
634
635
+ /**
636
+ * Sets the allowed {@link Slot} positions for this button.
637
+ *
638
+ * <p>The button is only allowed in the defined slots. If none of the slots can display the
639
+ * button, either because the slots do not exist, are already occupied or the UI surface does
640
+ * not allow the specific type of button in these slots, the button will not be displayed at
641
+ * all.
642
+ *
643
+ * <p>When multiple slots are provided, they define a preference order. The button will be
644
+ * placed in the first slot in the list that exists, isn't already occupied and that allows this
645
+ * type of button.
646
+ *
647
+ * <p>When not specified, the default value depends on the associated {@link #setPlayerCommand
648
+ * player command} and the {@link Icon} set in the constructor:
649
+ *
650
+ * <ul>
651
+ * <li>{@link Player#COMMAND_PLAY_PAUSE} and/or {@link #ICON_PLAY}, {@link #ICON_PAUSE}:
652
+ * {@link #SLOT_CENTRAL}
653
+ * <li>{@link Player#COMMAND_SEEK_TO_PREVIOUS}, {@link
654
+ * Player#COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM}, {@link Player#COMMAND_SEEK_BACK} and/or
655
+ * {@link #ICON_PREVIOUS}, {@link #ICON_SKIP_BACK}, {@link #ICON_REWIND}: {@link
656
+ * #SLOT_BACK}
657
+ * <li>{@link Player#COMMAND_SEEK_TO_NEXT}, {@link Player#COMMAND_SEEK_TO_NEXT_MEDIA_ITEM},
658
+ * {@link Player#COMMAND_SEEK_FORWARD} and/or {@link #ICON_NEXT}, {@link
659
+ * #ICON_SKIP_FORWARD}, {@link #ICON_FAST_FORWARD}: {@link #SLOT_FORWARD}
660
+ * <li>Anything else: {@link #SLOT_OVERFLOW}
661
+ * </ul>
662
+ *
663
+ * @param slots The list of allowed {@link Slot} positions. Must not be empty.
664
+ * @return This builder for chaining.
665
+ */
666
+ @ UnstableApi
667
+ @ CanIgnoreReturnValue
668
+ public Builder setSlots (@ Slot int ... slots ) {
669
+ checkArgument (slots .length != 0 );
670
+ this .slots = ImmutableIntArray .copyOf (slots );
671
+ return this ;
672
+ }
673
+
584
674
/** Builds a {@link CommandButton}. */
585
675
public CommandButton build () {
586
676
checkState (
587
677
(sessionCommand == null ) != (playerCommand == Player .COMMAND_INVALID ),
588
678
"Exactly one of sessionCommand and playerCommand should be set" );
679
+ if (slots == null ) {
680
+ slots = ImmutableIntArray .of (getDefaultSlot (playerCommand , icon ));
681
+ }
589
682
return new CommandButton (
590
- sessionCommand , playerCommand , icon , iconResId , iconUri , displayName , extras , enabled );
683
+ sessionCommand ,
684
+ playerCommand ,
685
+ icon ,
686
+ iconResId ,
687
+ iconUri ,
688
+ displayName ,
689
+ extras ,
690
+ enabled ,
691
+ slots );
591
692
}
592
693
}
593
694
@@ -630,6 +731,19 @@ public CommandButton build() {
630
731
*/
631
732
@ UnstableApi public final Bundle extras ;
632
733
734
+ /**
735
+ * The allowed {@link Slot} positions for this button.
736
+ *
737
+ * <p>The button is only allowed in the defined slots. If none of the slots can display the
738
+ * button, either because the slots do not exist, are already occupied or the UI surface does not
739
+ * allow the specific type of button in these slots, the button will not be displayed at all.
740
+ *
741
+ * <p>When multiple slots are provided, they define a preference order. The button will be placed
742
+ * in the first slot in the list that exists, isn't already occupied and that allows this type of
743
+ * button.
744
+ */
745
+ @ UnstableApi public final ImmutableIntArray slots ;
746
+
633
747
/**
634
748
* Whether the button is enabled.
635
749
*
@@ -647,7 +761,8 @@ private CommandButton(
647
761
@ Nullable Uri iconUri ,
648
762
CharSequence displayName ,
649
763
Bundle extras ,
650
- boolean enabled ) {
764
+ boolean enabled ,
765
+ ImmutableIntArray slots ) {
651
766
this .sessionCommand = sessionCommand ;
652
767
this .playerCommand = playerCommand ;
653
768
this .icon = icon ;
@@ -656,6 +771,7 @@ private CommandButton(
656
771
this .displayName = displayName ;
657
772
this .extras = new Bundle (extras );
658
773
this .isEnabled = enabled ;
774
+ this .slots = slots ;
659
775
}
660
776
661
777
/** Returns a copy with the new {@link #isEnabled} flag. */
@@ -675,7 +791,8 @@ private CommandButton(
675
791
iconUri ,
676
792
displayName ,
677
793
new Bundle (extras ),
678
- isEnabled );
794
+ isEnabled ,
795
+ slots );
679
796
}
680
797
681
798
/** Checks the given command button for equality while ignoring {@link #extras}. */
@@ -694,13 +811,14 @@ public boolean equals(@Nullable Object obj) {
694
811
&& iconResId == button .iconResId
695
812
&& Objects .equal (iconUri , button .iconUri )
696
813
&& TextUtils .equals (displayName , button .displayName )
697
- && isEnabled == button .isEnabled ;
814
+ && isEnabled == button .isEnabled
815
+ && slots .equals (button .slots );
698
816
}
699
817
700
818
@ Override
701
819
public int hashCode () {
702
820
return Objects .hashCode (
703
- sessionCommand , playerCommand , icon , iconResId , displayName , isEnabled , iconUri );
821
+ sessionCommand , playerCommand , icon , iconResId , displayName , isEnabled , iconUri , slots );
704
822
}
705
823
706
824
/**
@@ -747,6 +865,7 @@ public int hashCode() {
747
865
private static final String FIELD_ENABLED = Util .intToStringMaxRadix (5 );
748
866
private static final String FIELD_ICON_URI = Util .intToStringMaxRadix (6 );
749
867
private static final String FIELD_ICON = Util .intToStringMaxRadix (7 );
868
+ private static final String FIELD_SLOTS = Util .intToStringMaxRadix (8 );
750
869
751
870
@ UnstableApi
752
871
public Bundle toBundle () {
@@ -775,6 +894,9 @@ public Bundle toBundle() {
775
894
if (!isEnabled ) {
776
895
bundle .putBoolean (FIELD_ENABLED , isEnabled );
777
896
}
897
+ if (slots .length () != 1 || slots .get (0 ) != SLOT_OVERFLOW ) {
898
+ bundle .putIntArray (FIELD_SLOTS , slots .toArray ());
899
+ }
778
900
return bundle ;
779
901
}
780
902
@@ -806,6 +928,9 @@ public static CommandButton fromBundle(Bundle bundle, int sessionInterfaceVersio
806
928
sessionInterfaceVersion < 3 || bundle .getBoolean (FIELD_ENABLED , /* defaultValue= */ true );
807
929
@ Nullable Uri iconUri = bundle .getParcelable (FIELD_ICON_URI );
808
930
@ Icon int icon = bundle .getInt (FIELD_ICON , /* defaultValue= */ ICON_UNDEFINED );
931
+ @ Nullable
932
+ @ Slot
933
+ int [] slots = bundle .getIntArray (FIELD_SLOTS );
809
934
Builder builder = new Builder (icon , iconResId );
810
935
if (sessionCommand != null ) {
811
936
builder .setSessionCommand (sessionCommand );
@@ -820,6 +945,7 @@ public static CommandButton fromBundle(Bundle bundle, int sessionInterfaceVersio
820
945
.setDisplayName (displayName )
821
946
.setExtras (extras == null ? Bundle .EMPTY : extras )
822
947
.setEnabled (enabled )
948
+ .setSlots (slots == null ? new int [] {SLOT_OVERFLOW } : slots )
823
949
.build ();
824
950
}
825
951
@@ -983,4 +1109,42 @@ public static int getIconResIdForIconConstant(@Icon int icon) {
983
1109
return 0 ;
984
1110
}
985
1111
}
1112
+
1113
+ /**
1114
+ * Returns the default {@link Slot} for a button.
1115
+ *
1116
+ * @param playerCommand The {@link Player.Command} associated with this button.
1117
+ * @param icon The {@link Icon} of this button.
1118
+ * @return The default {@link Slot} for this button.
1119
+ */
1120
+ @ UnstableApi
1121
+ public static @ Slot int getDefaultSlot (@ Player .Command int playerCommand , @ Icon int icon ) {
1122
+ if (playerCommand == Player .COMMAND_PLAY_PAUSE || icon == ICON_PLAY || icon == ICON_PAUSE ) {
1123
+ return SLOT_CENTRAL ;
1124
+ } else if (playerCommand == Player .COMMAND_SEEK_BACK
1125
+ || playerCommand == Player .COMMAND_SEEK_TO_PREVIOUS
1126
+ || playerCommand == Player .COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM
1127
+ || icon == ICON_PREVIOUS
1128
+ || icon == ICON_REWIND
1129
+ || icon == ICON_SKIP_BACK
1130
+ || icon == ICON_SKIP_BACK_5
1131
+ || icon == ICON_SKIP_BACK_10
1132
+ || icon == ICON_SKIP_BACK_15
1133
+ || icon == ICON_SKIP_BACK_30 ) {
1134
+ return SLOT_BACK ;
1135
+ } else if (playerCommand == Player .COMMAND_SEEK_FORWARD
1136
+ || playerCommand == Player .COMMAND_SEEK_TO_NEXT
1137
+ || playerCommand == Player .COMMAND_SEEK_TO_NEXT_MEDIA_ITEM
1138
+ || icon == ICON_NEXT
1139
+ || icon == ICON_FAST_FORWARD
1140
+ || icon == ICON_SKIP_FORWARD
1141
+ || icon == ICON_SKIP_FORWARD_5
1142
+ || icon == ICON_SKIP_FORWARD_10
1143
+ || icon == ICON_SKIP_FORWARD_15
1144
+ || icon == ICON_SKIP_FORWARD_30 ) {
1145
+ return SLOT_FORWARD ;
1146
+ } else {
1147
+ return SLOT_OVERFLOW ;
1148
+ }
1149
+ }
986
1150
}
0 commit comments