Add fallback media layout for notification actions

Adds more button slots which are dynamically shown depending on which actions
are supported for the app's media.

When PlaybackState actions are available, the large play/pause button
will appear, along with prev/next on either side of the progress bar.
When Notification actions are used, those buttons will be hidden and up
to 5 actions will be shown to the right of the progress bar instead.

Also refactored view holder classes and test since both templates have 5
generic action buttons now.

Fixes: 218875984
Bug: 218876469
Test: atest MediaControlPanelTest
Change-Id: I464834d2eee53d661f2407f6280e2856c96c63ac
This commit is contained in:
Beth Thibodeau
2022-03-07 17:21:47 -05:00
parent 71437d58bf
commit f9e74f5fce
13 changed files with 505 additions and 203 deletions

View File

@@ -44,6 +44,7 @@
android:background="@drawable/qs_media_scrim"
/>
<!-- Guideline for output switcher -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_vertical_guideline"
android:layout_width="wrap_content"
@@ -51,6 +52,14 @@
android:orientation="vertical"
app:layout_constraintGuide_percent="0.6" />
<!-- Guideline for action buttons (collapsed view only) -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/action_button_guideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_end="@dimen/qs_media_session_collapsed_guideline" />
<!-- App icon -->
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
@@ -118,15 +127,7 @@
android:singleLine="true"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
app:layout_constraintHorizontal_bias="0" />
android:layout_height="wrap_content" />
<!-- Artist name -->
<TextView
@@ -136,15 +137,7 @@
style="@style/MediaPlayer.Subtitle"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
android:layout_marginTop="0dp"
app:layout_constraintTop_toBottomOf="@id/header_title"
app:layout_constraintStart_toStartOf="@id/header_title"
app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
app:layout_constraintBottom_toBottomOf="@id/actionPlayPause"
app:layout_constraintHorizontal_bias="0" />
android:layout_height="wrap_content" />
<ImageButton
android:id="@+id/actionPlayPause"
@@ -153,9 +146,33 @@
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp" />
/>
<!-- See comment in media_session_collapsed.xml for how these barriers are used -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/media_action_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/header_title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:barrierDirection="start"
app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4"
/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/media_action_barrier_end"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
app:layout_constraintBottom_toBottomOf="parent"
app:barrierDirection="end"
app:constraint_referenced_ids="actionPrev,media_progress_bar,actionNext,action0,action1,action2,action3,action4"
app:layout_constraintStart_toStartOf="parent"
/>
<!-- Button visibility will be controlled in code -->
<ImageButton
android:id="@+id/actionPrev"
style="@style/MediaPlayer.SessionAction"
@@ -163,10 +180,9 @@
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="0dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintHorizontal_chainStyle="packed" />
/>
<!-- Seek Bar -->
<!-- As per Material Design on Bidirectionality, this is forced to LTR in code -->
@@ -174,12 +190,12 @@
android:id="@+id/media_progress_bar"
style="@style/MediaPlayer.ProgressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="48dp"
android:paddingTop="@dimen/qs_media_session_enabled_seekbar_vertical_padding"
android:paddingBottom="12dp"
android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
android:splitTrack="false"
android:layout_marginBottom="0dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp" />
@@ -191,29 +207,58 @@
android:layout_height="48dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="0dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp" />
<ImageButton
android:id="@+id/actionStart"
android:id="@+id/action0"
style="@style/MediaPlayer.SessionAction"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="0dp"
android:layout_marginTop="0dp" />
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp"/>
<ImageButton
android:id="@+id/actionEnd"
android:id="@+id/action1"
style="@style/MediaPlayer.SessionAction"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="4dp"
android:layout_marginBottom="0dp"
android:layout_marginTop="0dp"
app:layout_constraintHorizontal_chainStyle="packed" />
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp" />
<ImageButton
android:id="@+id/action2"
style="@style/MediaPlayer.SessionAction"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="4dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp" />
<ImageButton
android:id="@+id/action3"
style="@style/MediaPlayer.SessionAction"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="4dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp" />
<ImageButton
android:id="@+id/action4"
style="@style/MediaPlayer.SessionAction"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="4dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:layout_marginTop="0dp" />
<!-- Long press menu -->
<TextView

View File

@@ -983,6 +983,7 @@
<dimen name="qs_media_seekbar_progress_amplitude">1.5dp</dimen>
<dimen name="qs_media_seekbar_progress_phase">8dp</dimen>
<dimen name="qs_media_seekbar_progress_stroke_width">2dp</dimen>
<dimen name="qs_media_session_collapsed_guideline">144dp</dimen>
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_aa_media_rec_album_size_collapsed">72dp</dimen>

View File

@@ -18,6 +18,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
android:id="@+id/media_action_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
app:layout_constraintStart_toEndOf="@id/action_button_guideline" />
<Constraint
android:id="@+id/album_art"
android:layout_width="match_parent"
@@ -27,34 +34,74 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
app:layout_constraintEnd_toStartOf="@id/action_button_guideline"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0" />
<Constraint
android:id="@+id/header_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginTop="0dp"
app:layout_constraintEnd_toStartOf="@id/action_button_guideline"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/header_title"
app:layout_constraintStart_toStartOf="@id/header_title"
app:layout_constraintVertical_bias="0"
app:layout_constraintHorizontal_bias="0" />
<Constraint
android:id="@+id/actionPlayPause"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/qs_media_padding"
app:layout_constraintStart_toEndOf="@id/actionEnd"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constraintVertical_bias="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
app:layout_constraintBottom_toBottomOf="parent" />
app:layout_constraintStart_toEndOf="@id/media_action_barrier_end" />
<!--
There will only be 3 action buttons shown at most in this layout (controlled in code).
Play/Pause should always be at the end, but the other buttons should remain in the same order
when in RTL.
This is accomplished by setting two barriers at the start and end of the small buttons,
anchored to a guideline set at 3x button width from the end. The text and play/pause button are
positioned relative to the barriers, and the small buttons use right/left constraints to stay
in the correct order inside the barriers.
-->
<Constraint
android:id="@+id/actionPrev"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintVertical_bias="1"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/header_artist"
app:layout_constraintEnd_toStartOf="@id/media_progress_bar"
app:layout_constraintRight_toLeftOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
app:layout_constraintTop_toBottomOf="@id/media_seamless"
app:layout_constraintLeft_toRightOf="@id/media_action_barrier" />
<Constraint
android:id="@+id/media_progress_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@id/actionPrev"
app:layout_constraintEnd_toStartOf="@id/actionNext"
app:layout_constraintVertical_bias="1"
app:layout_constraintLeft_toRightOf="@id/actionPrev"
app:layout_constraintRight_toLeftOf="@id/actionNext"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
@@ -62,29 +109,70 @@
android:id="@+id/actionNext"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintStart_toEndOf="@id/media_progress_bar"
app:layout_constraintEnd_toStartOf="@id/actionStart"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constraintVertical_bias="1"
app:layout_constraintLeft_toRightOf="@id/media_progress_bar"
app:layout_constraintRight_toLeftOf="@id/action0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
<Constraint
android:id="@+id/actionStart"
android:id="@+id/action0"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@id/actionNext"
app:layout_constraintEnd_toStartOf="@id/actionEnd"
app:layout_constraintVertical_bias="1"
app:layout_constraintLeft_toRightOf="@id/actionNext"
app:layout_constraintRight_toLeftOf="@id/action1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
<Constraint
android:id="@+id/actionEnd"
android:id="@+id/action1"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
app:layout_constraintStart_toEndOf="@id/actionStart"
app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
app:layout_constraintVertical_bias="1"
app:layout_constraintLeft_toRightOf="@id/action0"
app:layout_constraintRight_toLeftOf="@id/action2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
</ConstraintSet>
<Constraint
android:id="@+id/action2"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
app:layout_constraintVertical_bias="1"
app:layout_constraintLeft_toRightOf="@id/action1"
app:layout_constraintRight_toLeftOf="@id/action3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
<Constraint
android:id="@+id/action3"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
app:layout_constraintVertical_bias="1"
app:layout_constraintLeft_toRightOf="@id/action2"
app:layout_constraintRight_toLeftOf="@id/action4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless" />
<Constraint
android:id="@+id/action4"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="@dimen/qs_media_padding"
android:visibility="gone"
app:layout_constraintVertical_bias="1"
app:layout_constraintLeft_toRightOf="@id/action3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
app:layout_constraintRight_toLeftOf="@id/media_action_barrier_end" />
</ConstraintSet>

View File

@@ -27,58 +27,119 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0" />
<Constraint
android:id="@+id/header_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginTop="0dp"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/actionPlayPause"
app:layout_constraintBottom_toTopOf="@id/media_action_barrier"
app:layout_constraintTop_toBottomOf="@id/header_title"
app:layout_constraintStart_toStartOf="@id/header_title"
app:layout_constraintVertical_bias="0"
app:layout_constraintHorizontal_bias="0" />
<Constraint
android:id="@+id/actionPlayPause"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginBottom="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
app:layout_constraintBottom_toTopOf="@id/actionEnd" />
app:layout_constraintBottom_toBottomOf="@id/header_artist" />
<!--
The bottom row of action buttons should remain in the same order when RTL, so their constraints
are set with right/left instead of start/end.
The chain is set to "spread" so that the progress bar can be weighted to fill any empty space.
-->
<Constraint
android:id="@+id/actionPrev"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/media_progress_bar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
app:layout_constraintTop_toBottomOf="@id/header_artist"
app:layout_constraintHorizontal_chainStyle="spread" />
<Constraint
android:id="@+id/media_progress_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/actionPrev"
app:layout_constraintEnd_toStartOf="@id/actionNext"
android:layout_height="48dp"
app:layout_constraintLeft_toRightOf="@id/actionPrev"
app:layout_constraintRight_toLeftOf="@id/actionNext"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
app:layout_constraintTop_toBottomOf="@id/header_artist"
app:layout_constraintHorizontal_weight="1" />
<Constraint
android:id="@+id/actionNext"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintStart_toEndOf="@id/media_progress_bar"
app:layout_constraintEnd_toStartOf="@id/actionStart"
app:layout_constraintLeft_toRightOf="@id/media_progress_bar"
app:layout_constraintRight_toLeftOf="@id/action0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
app:layout_constraintTop_toBottomOf="@id/header_artist" />
<Constraint
android:id="@+id/actionStart"
android:id="@+id/action0"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintStart_toEndOf="@id/actionNext"
app:layout_constraintEnd_toStartOf="@id/actionEnd"
app:layout_constraintLeft_toRightOf="@id/actionNext"
app:layout_constraintRight_toLeftOf="@id/action1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
app:layout_constraintTop_toBottomOf="@id/header_artist" />
<Constraint
android:id="@+id/actionEnd"
android:id="@+id/action1"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintStart_toEndOf="@id/actionStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toRightOf="@id/action0"
app:layout_constraintRight_toLeftOf="@id/action2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
app:layout_constraintTop_toBottomOf="@id/header_artist" />
</ConstraintSet>
<Constraint
android:id="@+id/action2"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintLeft_toRightOf="@id/action1"
app:layout_constraintRight_toLeftOf="@id/action3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_artist" />
<Constraint
android:id="@+id/action3"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintLeft_toRightOf="@id/action2"
app:layout_constraintRight_toLeftOf="@id/action4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_artist" />
<Constraint
android:id="@+id/action4"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintLeft_toRightOf="@id/action3"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_artist" />
</ConstraintSet>

View File

@@ -104,6 +104,13 @@ public class MediaControlPanel {
R.id.action4
};
// Buttons to show in small player when using semantic actions
private static final List<Integer> SEMANTIC_ACTION_IDS = List.of(
R.id.actionPlayPause,
R.id.actionPrev,
R.id.actionNext
);
private final SeekBarViewModel mSeekBarViewModel;
private SeekBarObserver mSeekBarObserver;
protected final Executor mBackgroundExecutor;
@@ -501,11 +508,11 @@ public class MediaControlPanel {
MediaButton semanticActions = data.getSemanticActions();
actionIcons = new ArrayList<MediaAction>();
actionIcons.add(semanticActions.getStartCustom());
actionIcons.add(semanticActions.getCustom0());
actionIcons.add(semanticActions.getPrevOrCustom());
actionIcons.add(semanticActions.getPlayOrPause());
actionIcons.add(semanticActions.getNextOrCustom());
actionIcons.add(semanticActions.getEndCustom());
actionIcons.add(semanticActions.getCustom1());
actionsWhenCollapsed = new ArrayList<Integer>();
actionsWhenCollapsed.add(1);
@@ -562,6 +569,9 @@ public class MediaControlPanel {
/** Bind elements specific to PlayerSessionViewHolder */
private void bindSessionPlayer(@NonNull MediaData data, String key) {
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
// Default colors
int surfaceColor = mBackgroundColor;
int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
@@ -660,26 +670,68 @@ public class MediaControlPanel {
// Media action buttons
MediaButton semanticActions = data.getSemanticActions();
PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
ImageButton[] genericButtons = new ImageButton[]{
sessionHolder.getAction0(),
sessionHolder.getAction1(),
sessionHolder.getAction2(),
sessionHolder.getAction3(),
sessionHolder.getAction4()};
ImageButton[] semanticButtons = new ImageButton[]{
sessionHolder.getActionPlayPause(),
sessionHolder.getActionNext(),
sessionHolder.getActionPrev()};
if (semanticActions != null) {
PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
// Hide all the generic buttons
for (ImageButton b: genericButtons) {
setVisibleAndAlpha(collapsedSet, b.getId(), false);
setVisibleAndAlpha(expandedSet, b.getId(), false);
}
// Play/pause button has a background
sessionHolder.getActionPlayPause().setBackgroundTintList(accentColorList);
setSemanticButton(sessionHolder.getActionPlayPause(), semanticActions.getPlayOrPause(),
ColorStateList.valueOf(textPrimaryInverse));
ColorStateList.valueOf(textPrimaryInverse), collapsedSet, expandedSet, true);
setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(),
textColorList);
textColorList, collapsedSet, expandedSet, true);
setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(),
textColorList);
setSemanticButton(sessionHolder.getActionStart(), semanticActions.getStartCustom(),
textColorList);
setSemanticButton(sessionHolder.getActionEnd(), semanticActions.getEndCustom(),
textColorList);
textColorList, collapsedSet, expandedSet, true);
setSemanticButton(sessionHolder.getAction0(), semanticActions.getCustom0(),
textColorList, collapsedSet, expandedSet, false);
setSemanticButton(sessionHolder.getAction1(), semanticActions.getCustom1(),
textColorList, collapsedSet, expandedSet, false);
} else {
Log.w(TAG, "Using semantic player, but did not get buttons");
// Hide all the semantic buttons
for (int id : SEMANTIC_ACTION_IDS) {
setVisibleAndAlpha(collapsedSet, id, false);
setVisibleAndAlpha(expandedSet, id, false);
}
// Set all the generic buttons
List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
List<MediaAction> actions = data.getActions();
int i = 0;
for (; i < actions.size(); i++) {
boolean showInCompact = actionsWhenCollapsed.contains(i);
setSemanticButton(genericButtons[i], actions.get(i), textColorList, collapsedSet,
expandedSet, showInCompact);
}
for (; i < 5; i++) {
// Hide any unused buttons
setSemanticButton(genericButtons[i], null, textColorList, collapsedSet,
expandedSet, false);
}
}
// If disabled, set progress bar to INVISIBLE instead of GONE so layout weights still work
boolean seekbarEnabled = mSeekBarViewModel.getEnabled();
expandedSet.setVisibility(R.id.media_progress_bar,
seekbarEnabled ? ConstraintSet.VISIBLE : ConstraintSet.INVISIBLE);
expandedSet.setAlpha(R.id.media_progress_bar, seekbarEnabled ? 1.0f : 0.0f);
// Long press buttons
mMediaViewHolder.getLongPressText().setTextColor(textColorList);
mMediaViewHolder.getSettingsText().setTextColor(textColorList);
@@ -692,7 +744,8 @@ public class MediaControlPanel {
}
private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
ColorStateList fgColor) {
ColorStateList fgColor, ConstraintSet collapsedSet, ConstraintSet expandedSet,
boolean showInCompact) {
button.setImageTintList(fgColor);
if (mediaAction != null) {
button.setImageIcon(mediaAction.getIcon());
@@ -716,6 +769,9 @@ public class MediaControlPanel {
button.setContentDescription(null);
button.setEnabled(false);
}
setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact);
setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
}
@Nullable

View File

@@ -149,11 +149,11 @@ data class MediaButton(
/**
* First custom action space
*/
var startCustom: MediaAction? = null,
var custom0: MediaAction? = null,
/**
* Last custom action space
* Second custom action space
*/
var endCustom: MediaAction? = null
var custom1: MediaAction? = null
)
/** State of a media action. */

View File

@@ -752,8 +752,8 @@ class MediaDataManager(
null
}
actions.startCustom = customActions[customIdx++]
actions.endCustom = customActions[customIdx++]
actions.custom0 = customActions[customIdx++]
actions.custom1 = customActions[customIdx++]
}
return actions
}

View File

@@ -60,12 +60,24 @@ abstract class MediaViewHolder constructor(itemView: View) {
val settings = itemView.requireViewById<View>(R.id.settings)
val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
// Action Buttons
val action0 = itemView.requireViewById<ImageButton>(R.id.action0)
val action1 = itemView.requireViewById<ImageButton>(R.id.action1)
val action2 = itemView.requireViewById<ImageButton>(R.id.action2)
val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
init {
(player.background as IlluminationDrawable).let {
it.registerLightSource(seamless)
it.registerLightSource(cancel)
it.registerLightSource(dismiss)
it.registerLightSource(settings)
it.registerLightSource(action0)
it.registerLightSource(action1)
it.registerLightSource(action2)
it.registerLightSource(action3)
it.registerLightSource(action4)
}
}

View File

@@ -31,16 +31,12 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol
val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause)
val actionNext = itemView.requireViewById<ImageButton>(R.id.actionNext)
val actionPrev = itemView.requireViewById<ImageButton>(R.id.actionPrev)
val actionStart = itemView.requireViewById<ImageButton>(R.id.actionStart)
val actionEnd = itemView.requireViewById<ImageButton>(R.id.actionEnd)
init {
(player.background as IlluminationDrawable).let {
it.registerLightSource(actionPlayPause)
it.registerLightSource(actionNext)
it.registerLightSource(actionPrev)
it.registerLightSource(actionStart)
it.registerLightSource(actionEnd)
}
}
@@ -49,8 +45,11 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol
R.id.actionPlayPause -> actionPlayPause
R.id.actionNext -> actionNext
R.id.actionPrev -> actionPrev
R.id.actionStart -> actionStart
R.id.actionEnd -> actionEnd
R.id.action0 -> action0
R.id.action1 -> action1
R.id.action2 -> action2
R.id.action3 -> action3
R.id.action4 -> action4
else -> {
throw IllegalArgumentException()
}
@@ -90,8 +89,11 @@ class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHol
R.id.actionPlayPause,
R.id.actionNext,
R.id.actionPrev,
R.id.actionStart,
R.id.actionEnd,
R.id.action0,
R.id.action1,
R.id.action2,
R.id.action3,
R.id.action4,
R.id.icon
)
val gutsIds = setOf(

View File

@@ -33,23 +33,6 @@ class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(ite
override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
override val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time)
// Action Buttons
val action0 = itemView.requireViewById<ImageButton>(R.id.action0)
val action1 = itemView.requireViewById<ImageButton>(R.id.action1)
val action2 = itemView.requireViewById<ImageButton>(R.id.action2)
val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
init {
(player.background as IlluminationDrawable).let {
it.registerLightSource(action0)
it.registerLightSource(action1)
it.registerLightSource(action2)
it.registerLightSource(action3)
it.registerLightSource(action4)
}
}
override fun getAction(id: Int): ImageButton {
return when (id) {
R.id.action0 -> action0

View File

@@ -132,6 +132,8 @@ class SeekBarViewModel @Inject constructor(
lateinit var logSmartspaceClick: () -> Unit
fun getEnabled() = _data.enabled
/**
* Event indicating that the user has started interacting with the seek bar.
*/

View File

@@ -59,6 +59,7 @@ import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
@@ -119,8 +120,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var actionPlayPause: ImageButton
private lateinit var actionNext: ImageButton
private lateinit var actionPrev: ImageButton
private lateinit var actionStart: ImageButton
private lateinit var actionEnd: ImageButton
@Mock private lateinit var longPressText: TextView
@Mock private lateinit var handler: Handler
private lateinit var settings: View
@@ -168,6 +167,17 @@ public class MediaControlPanelTest : SysuiTestCase() {
cancelText = TextView(context)
dismiss = FrameLayout(context)
dismissText = TextView(context)
action0 = ImageButton(context).also { it.setId(R.id.action0) }
action1 = ImageButton(context).also { it.setId(R.id.action1) }
action2 = ImageButton(context).also { it.setId(R.id.action2) }
action3 = ImageButton(context).also { it.setId(R.id.action3) }
action4 = ImageButton(context).also { it.setId(R.id.action4) }
actionPlayPause = ImageButton(context).also { it.setId(R.id.actionPlayPause) }
actionPrev = ImageButton(context).also { it.setId(R.id.actionPrev) }
actionNext = ImageButton(context).also { it.setId(R.id.actionNext) }
initPlayerHolderMocks()
initSessionHolderMocks()
@@ -208,90 +218,64 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
}
/** Mock view holder for the notification player */
private fun initPlayerHolderMocks() {
whenever(holder.player).thenReturn(view)
whenever(holder.appIcon).thenReturn(appIcon)
whenever(holder.albumView).thenReturn(albumView)
whenever(holder.titleText).thenReturn(titleText)
whenever(holder.artistText).thenReturn(artistText)
/**
* Initialize elements common to both view holders
*/
private fun initMediaViewHolderMocks(viewHolder: MediaViewHolder) {
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
whenever(viewHolder.albumView).thenReturn(albumView)
whenever(viewHolder.titleText).thenReturn(titleText)
whenever(viewHolder.artistText).thenReturn(artistText)
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
whenever(holder.seamless).thenReturn(seamless)
whenever(holder.seamlessButton).thenReturn(seamlessButton)
whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
whenever(holder.seamlessText).thenReturn(seamlessText)
whenever(holder.seekBar).thenReturn(seekBar)
whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
whenever(holder.totalTimeView).thenReturn(totalTimeView)
whenever(viewHolder.seamless).thenReturn(seamless)
whenever(viewHolder.seamlessButton).thenReturn(seamlessButton)
whenever(viewHolder.seamlessIcon).thenReturn(seamlessIcon)
whenever(viewHolder.seamlessText).thenReturn(seamlessText)
whenever(viewHolder.seekBar).thenReturn(seekBar)
// Action buttons
action0 = ImageButton(context)
whenever(holder.action0).thenReturn(action0)
whenever(holder.getAction(R.id.action0)).thenReturn(action0)
action1 = ImageButton(context)
whenever(holder.action1).thenReturn(action1)
whenever(holder.getAction(R.id.action1)).thenReturn(action1)
action2 = ImageButton(context)
whenever(holder.action2).thenReturn(action2)
whenever(holder.getAction(R.id.action2)).thenReturn(action2)
action3 = ImageButton(context)
whenever(holder.action3).thenReturn(action3)
whenever(holder.getAction(R.id.action3)).thenReturn(action3)
action4 = ImageButton(context)
whenever(holder.action4).thenReturn(action4)
whenever(holder.getAction(R.id.action4)).thenReturn(action4)
whenever(viewHolder.action0).thenReturn(action0)
whenever(viewHolder.getAction(R.id.action0)).thenReturn(action0)
whenever(viewHolder.action1).thenReturn(action1)
whenever(viewHolder.getAction(R.id.action1)).thenReturn(action1)
whenever(viewHolder.action2).thenReturn(action2)
whenever(viewHolder.getAction(R.id.action2)).thenReturn(action2)
whenever(viewHolder.action3).thenReturn(action3)
whenever(viewHolder.getAction(R.id.action3)).thenReturn(action3)
whenever(viewHolder.action4).thenReturn(action4)
whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4)
// Long press menu
whenever(holder.longPressText).thenReturn(longPressText)
whenever(viewHolder.longPressText).thenReturn(longPressText)
whenever(longPressText.handler).thenReturn(handler)
whenever(holder.settings).thenReturn(settings)
whenever(holder.settingsText).thenReturn(settingsText)
whenever(holder.cancel).thenReturn(cancel)
whenever(holder.cancelText).thenReturn(cancelText)
whenever(holder.dismiss).thenReturn(dismiss)
whenever(holder.dismissText).thenReturn(dismissText)
whenever(viewHolder.settings).thenReturn(settings)
whenever(viewHolder.settingsText).thenReturn(settingsText)
whenever(viewHolder.cancel).thenReturn(cancel)
whenever(viewHolder.cancelText).thenReturn(cancelText)
whenever(viewHolder.dismiss).thenReturn(dismiss)
whenever(viewHolder.dismissText).thenReturn(dismissText)
}
/** Mock view holder for the notification player */
private fun initPlayerHolderMocks() {
initMediaViewHolderMocks(holder)
whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
whenever(holder.totalTimeView).thenReturn(totalTimeView)
}
/** Mock view holder for session player */
private fun initSessionHolderMocks() {
whenever(sessionHolder.player).thenReturn(view)
whenever(sessionHolder.albumView).thenReturn(albumView)
whenever(sessionHolder.appIcon).thenReturn(appIcon)
whenever(sessionHolder.titleText).thenReturn(titleText)
whenever(sessionHolder.artistText).thenReturn(artistText)
val seamlessBackground = mock(RippleDrawable::class.java)
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
whenever(sessionHolder.seamless).thenReturn(seamless)
whenever(sessionHolder.seamlessButton).thenReturn(seamlessButton)
whenever(sessionHolder.seamlessIcon).thenReturn(seamlessIcon)
whenever(sessionHolder.seamlessText).thenReturn(seamlessText)
whenever(sessionHolder.seekBar).thenReturn(seekBar)
initMediaViewHolderMocks(sessionHolder)
// Action buttons
actionPlayPause = ImageButton(context)
// Semantic action buttons
whenever(sessionHolder.actionPlayPause).thenReturn(actionPlayPause)
whenever(sessionHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
actionNext = ImageButton(context)
whenever(sessionHolder.actionNext).thenReturn(actionNext)
whenever(sessionHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
actionPrev = ImageButton(context)
whenever(sessionHolder.actionPrev).thenReturn(actionPrev)
whenever(sessionHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
actionStart = ImageButton(context)
whenever(sessionHolder.actionStart).thenReturn(actionStart)
whenever(sessionHolder.getAction(R.id.actionStart)).thenReturn(actionStart)
actionEnd = ImageButton(context)
whenever(sessionHolder.actionEnd).thenReturn(actionEnd)
whenever(sessionHolder.getAction(R.id.actionEnd)).thenReturn(actionEnd)
// Long press menu
whenever(sessionHolder.longPressText).thenReturn(longPressText)
whenever(sessionHolder.settings).thenReturn(settings)
whenever(sessionHolder.settingsText).thenReturn(settingsText)
whenever(sessionHolder.cancel).thenReturn(cancel)
whenever(sessionHolder.cancelText).thenReturn(cancelText)
whenever(sessionHolder.dismiss).thenReturn(dismiss)
whenever(sessionHolder.dismissText).thenReturn(dismissText)
}
@After
@@ -316,8 +300,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"),
nextOrCustom = MediaAction(icon, Runnable {}, "next"),
startCustom = MediaAction(icon, null, "custom 1"),
endCustom = MediaAction(icon, null, "custom 2")
custom0 = MediaAction(icon, null, "custom 0"),
custom1 = MediaAction(icon, null, "custom 1")
)
val state = mediaData.copy(semanticActions = semanticActions)
@@ -325,7 +309,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.bindPlayer(state, PACKAGE)
verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
assertThat(action0.contentDescription).isEqualTo("custom 1")
assertThat(action0.contentDescription).isEqualTo("custom 0")
assertThat(action0.isEnabled()).isFalse()
verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
@@ -340,7 +324,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
assertThat(action3.contentDescription).isEqualTo("next")
verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
assertThat(action4.contentDescription).isEqualTo("custom 2")
assertThat(action4.contentDescription).isEqualTo("custom 1")
assertThat(action4.isEnabled()).isFalse()
}
@@ -353,28 +337,96 @@ public class MediaControlPanelTest : SysuiTestCase() {
val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"),
nextOrCustom = MediaAction(icon, Runnable {}, "next"),
startCustom = MediaAction(icon, null, "custom 1"),
endCustom = MediaAction(icon, null, "custom 2")
custom0 = MediaAction(icon, null, "custom 0"),
custom1 = MediaAction(icon, null, "custom 1")
)
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
player.bindPlayer(state, PACKAGE)
assertThat(actionStart.contentDescription).isEqualTo("custom 1")
assertThat(actionStart.isEnabled()).isFalse()
assertThat(actionPrev.isEnabled()).isFalse()
assertThat(actionPrev.drawable).isNull()
verify(collapsedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE)
assertThat(actionPlayPause.isEnabled()).isTrue()
assertThat(actionPlayPause.contentDescription).isEqualTo("play")
verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.VISIBLE)
assertThat(actionNext.isEnabled()).isTrue()
assertThat(actionNext.contentDescription).isEqualTo("next")
verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.VISIBLE)
assertThat(actionEnd.contentDescription).isEqualTo("custom 2")
assertThat(actionEnd.isEnabled()).isFalse()
// Called twice since these IDs are used as generic buttons
assertThat(action0.contentDescription).isEqualTo("custom 0")
assertThat(action0.isEnabled()).isFalse()
verify(collapsedSet, times(2)).setVisibility(R.id.action0, ConstraintSet.GONE)
assertThat(action1.contentDescription).isEqualTo("custom 1")
assertThat(action1.isEnabled()).isFalse()
verify(collapsedSet, times(2)).setVisibility(R.id.action1, ConstraintSet.GONE)
// Verify generic buttons are hidden
verify(collapsedSet).setVisibility(R.id.action2, ConstraintSet.GONE)
verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.GONE)
verify(collapsedSet).setVisibility(R.id.action3, ConstraintSet.GONE)
verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.GONE)
verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE)
verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.GONE)
}
@Test
fun bindNotificationActionsNewLayout() {
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
val actions = listOf(
MediaAction(icon, Runnable {}, "previous"),
MediaAction(icon, Runnable {}, "play"),
MediaAction(icon, null, "next"),
MediaAction(icon, null, "custom 0"),
MediaAction(icon, Runnable {}, "custom 1")
)
val state = mediaData.copy(actions = actions,
actionsToShowInCompact = listOf(1, 2),
semanticActions = null)
player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
player.bindPlayer(state, PACKAGE)
// Verify semantic actions are hidden
verify(collapsedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE)
verify(expandedSet).setVisibility(R.id.actionPrev, ConstraintSet.GONE)
verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.GONE)
verify(expandedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.GONE)
verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE)
verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.GONE)
// Generic actions all enabled
assertThat(action0.contentDescription).isEqualTo("previous")
assertThat(action0.isEnabled()).isTrue()
verify(collapsedSet).setVisibility(R.id.action0, ConstraintSet.GONE)
assertThat(action1.contentDescription).isEqualTo("play")
assertThat(action1.isEnabled()).isTrue()
verify(collapsedSet).setVisibility(R.id.action1, ConstraintSet.VISIBLE)
assertThat(action2.contentDescription).isEqualTo("next")
assertThat(action2.isEnabled()).isFalse()
verify(collapsedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
assertThat(action3.contentDescription).isEqualTo("custom 0")
assertThat(action3.isEnabled()).isFalse()
verify(collapsedSet).setVisibility(R.id.action3, ConstraintSet.GONE)
assertThat(action4.contentDescription).isEqualTo("custom 1")
assertThat(action4.isEnabled()).isTrue()
verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE)
}
@Test

View File

@@ -659,11 +659,11 @@ class MediaDataManagerTest : SysuiTestCase() {
actions.nextOrCustom!!.action!!.run()
verify(transportControls).skipToNext()
assertThat(actions.startCustom).isNotNull()
assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
assertThat(actions.custom0).isNotNull()
assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0])
assertThat(actions.endCustom).isNotNull()
assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
assertThat(actions.custom1).isNotNull()
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
}
@Test
@@ -697,11 +697,11 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(actions.nextOrCustom).isNotNull()
assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1])
assertThat(actions.startCustom).isNotNull()
assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2])
assertThat(actions.custom0).isNotNull()
assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[2])
assertThat(actions.endCustom).isNotNull()
assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3])
assertThat(actions.custom1).isNotNull()
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3])
}
@Test
@@ -737,10 +737,10 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(actions.prevOrCustom).isNull()
assertThat(actions.nextOrCustom).isNull()
assertThat(actions.startCustom).isNotNull()
assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
assertThat(actions.custom0).isNotNull()
assertThat(actions.custom0!!.contentDescription).isEqualTo(customDesc[0])
assertThat(actions.endCustom).isNotNull()
assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
assertThat(actions.custom1).isNotNull()
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
}
}