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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user