Merge "Remove old layout and flag" into tm-dev

This commit is contained in:
TreeHugger Robot
2022-03-22 14:34:33 +00:00
committed by Android (Google) Code Review
24 changed files with 260 additions and 1426 deletions

View File

@@ -41,7 +41,7 @@ Files under [`systemui/media/`](/packages/SystemUI/src/com/android/systemui/medi
* SeekBarViewModel.kt
* Implements its own `computePosition()` for the seekbar (to avoid continually polling the `PlaybackState`, which involves binder calls)
* Does some touch falsing (ignore flings, require drags to start near the thumb - otherwise users would often accidentally trigger the seekbar when they meant to move the carousel or shade)
* PlayerViewHolder.kt
* MediaViewHolder.kt
* Holds references to the UI elements in the panel
* Animation support:
* MediaHierarchyManager.kt

View File

@@ -1,300 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<!-- Layout for media controls inside QSPanel carousel -->
<com.android.systemui.util.animation.TransitionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/qs_media_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="center_horizontal|fill_vertical"
android:forceHasOverlappingRendering="false"
android:background="@drawable/qs_media_background"
android:theme="@style/MediaPlayer">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_vertical_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.6" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_horizontal_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="48dp" />
<!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
<FrameLayout
android:id="@+id/notification_media_progress_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:forceHasOverlappingRendering="false">
<!-- width is set to "match_parent" to avoid extra layout calls -->
<TextView
android:id="@+id/media_elapsed_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:gravity="start"
android:textSize="12sp" />
<TextView
android:id="@+id/media_total_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:fontFamily="@*android:string/config_bodyFontFamily"
android:gravity="end"
android:textSize="12sp" />
</FrameLayout>
<!-- Actions must be ordered left-to-right even in RTL layout. However, they appear in a chain
with the artist name, and must as a group appear at the end of that chain. This is
accomplished by having all actions appear in a LTR chain within the parent, and then biasing it
to the right side, then this barrier is used to bound the text views. -->
<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:barrierDirection="start"
app:constraint_referenced_ids="action0,action1,action2,action3,action4"
/>
<ImageButton
android:id="@+id/action0"
style="@style/MediaPlayer.Action"
android:layout_width="48dp"
android:layout_height="48dp" />
<ImageButton
android:id="@+id/action1"
style="@style/MediaPlayer.Action"
android:layout_width="48dp"
android:layout_height="48dp" />
<ImageButton
android:id="@+id/action2"
style="@style/MediaPlayer.Action"
android:layout_width="48dp"
android:layout_height="48dp" />
<ImageButton
android:id="@+id/action3"
style="@style/MediaPlayer.Action"
android:layout_width="48dp"
android:layout_height="48dp" />
<ImageButton
android:id="@+id/action4"
style="@style/MediaPlayer.Action"
android:layout_width="48dp"
android:layout_height="48dp" />
<!-- Album Art -->
<ImageView
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size"
android:layout_height="@dimen/qs_media_album_size"
android:layout_gravity="center_vertical"
style="@style/MediaPlayer.Album"
android:background="@drawable/qs_media_art_background"
android:clipToOutline="true" />
<!-- Seamless Output Switcher -->
<LinearLayout
android:id="@+id/media_seamless"
android:layout_width="0dp"
android:layout_height="48dp"
android:orientation="horizontal"
android:gravity="top|end"
android:paddingTop="@dimen/qs_media_padding"
android:paddingEnd="@dimen/qs_media_padding"
android:background="@drawable/qs_media_light_source"
android:forceHasOverlappingRendering="false">
<LinearLayout
android:id="@+id/media_seamless_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/qs_seamless_height"
android:theme="@style/MediaPlayer.SolidButton"
android:background="@drawable/qs_media_seamless_background"
android:orientation="horizontal"
android:contentDescription="@string/quick_settings_media_device_label">
<ImageView
android:id="@+id/media_seamless_image"
android:layout_width="@dimen/qs_seamless_icon_size"
android:layout_height="@dimen/qs_seamless_icon_size"
android:layout_gravity="center"
android:tint="?android:attr/textColorPrimary"
android:src="@*android:drawable/ic_media_seamless" />
<TextView
android:id="@+id/media_seamless_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:singleLine="true"
android:text="@*android:string/ext_media_seamless_action"
android:textDirection="locale"
android:textSize="12sp"
android:lineHeight="16sp" />
</LinearLayout>
</LinearLayout>
<!-- Seek Bar -->
<!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
<SeekBar
android:id="@+id/media_progress_bar"
style="@style/MediaPlayer.ProgressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
android:paddingTop="@dimen/qs_media_enabled_seekbar_vertical_padding"
android:layout_marginTop="-22dp"
android:paddingBottom="2dp"
android:splitTrack="false" />
<!-- Song name -->
<TextView
android:id="@+id/header_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:singleLine="true"
android:textSize="16sp" />
<!-- Artist name -->
<TextView
android:id="@+id/header_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
style="@style/MediaPlayer.Subtitle"
android:textSize="14sp" />
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
style="@style/MediaPlayer.AppIcon"
android:layout_width="@dimen/qs_media_icon_size"
android:layout_height="@dimen/qs_media_icon_size" />
<!-- Long press menu -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/qs_media_padding"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding"
android:id="@+id/remove_text"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:text="@string/controls_media_close_session"
android:gravity="center_horizontal|top"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/cancel"/>
<FrameLayout
android:id="@+id/settings"
android:background="@drawable/qs_media_light_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:id="@+id/settings_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
style="@style/MediaPlayer.OutlineButton"
android:text="@string/controls_media_settings_button" />
</FrameLayout>
<FrameLayout
android:id="@+id/cancel"
android:background="@drawable/qs_media_light_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
app:layout_constraintStart_toEndOf="@id/settings"
app:layout_constraintEnd_toStartOf="@id/dismiss"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:id="@+id/cancel_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
style="@style/MediaPlayer.OutlineButton"
android:text="@string/cancel" />
</FrameLayout>
<FrameLayout
android:id="@+id/dismiss"
android:background="@drawable/qs_media_light_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
app:layout_constraintStart_toEndOf="@id/cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/remove_text">
<TextView
android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
style="@style/MediaPlayer.OutlineButton"
android:text="@string/controls_media_dismiss_button"
/>
</FrameLayout>
</com.android.systemui.util.animation.TransitionLayout>

View File

@@ -959,10 +959,7 @@
<!-- Size of media cards in the QSPanel carousel -->
<dimen name="qs_media_padding">16dp</dimen>
<dimen name="qs_media_album_size_small">72dp</dimen>
<dimen name="qs_media_album_size">84dp</dimen>
<dimen name="qs_media_album_radius">14dp</dimen>
<dimen name="qs_media_album_device_padding">26dp</dimen>
<dimen name="qs_media_info_margin">12dp</dimen>
<dimen name="qs_media_info_spacing">8dp</dimen>
<dimen name="qs_media_icon_size">20dp</dimen>
@@ -974,10 +971,7 @@
<dimen name="qs_seamless_icon_size">12dp</dimen>
<dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
<dimen name="qs_media_enabled_seekbar_height">2dp</dimen>
<dimen name="qs_media_enabled_seekbar_vertical_padding">28dp</dimen>
<dimen name="qs_media_disabled_seekbar_vertical_padding">29dp</dimen>
<!-- Sizes for alternate session-based layout -->
<dimen name="qs_media_session_enabled_seekbar_vertical_padding">15dp</dimen>
<dimen name="qs_media_session_disabled_seekbar_vertical_padding">16dp</dimen>
<dimen name="qs_media_session_height_expanded">184dp</dimen>

View File

@@ -1,176 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<ConstraintSet
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
android:id="@+id/icon"
android:layout_width="@dimen/qs_media_icon_size"
android:layout_height="@dimen/qs_media_icon_size"
android:translationY="@dimen/qs_media_icon_offset"
android:translationX="@dimen/qs_media_icon_offset"
app:layout_constraintEnd_toEndOf="@id/album_art"
app:layout_constraintBottom_toBottomOf="@id/album_art"
/>
<Constraint
android:id="@+id/media_seamless"
android:layout_width="wrap_content"
android:layout_height="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_bias="1"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
/>
<Constraint
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size_small"
android:layout_height="@dimen/qs_media_album_size_small"
android:layout_marginTop="@dimen/qs_media_padding"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginBottom="@dimen/qs_media_padding"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
<!-- Song name -->
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qs_media_info_margin"
android:layout_marginEnd="@dimen/qs_center_guideline_padding"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline"
app:layout_constraintStart_toEndOf="@id/album_art"
app:layout_constraintEnd_toStartOf="@id/center_vertical_guideline"
app:layout_constraintHorizontal_bias="0"/>
<!-- Artist name -->
<Constraint
android:id="@+id/header_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
android:layout_marginTop="@dimen/qs_media_info_spacing"
app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
app:layout_constraintStart_toStartOf="@id/header_title"
app:layout_constraintEnd_toStartOf="@id/media_action_barrier"
app:layout_constraintHorizontal_bias="0"/>
<!-- Seek Bar -->
<Constraint
android:id="@+id/media_progress_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0.0"
app:layout_constraintTop_toBottomOf="@id/album_art"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
/>
<Constraint
android:id="@+id/notification_media_progress_time"
android:alpha="0.0"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/album_art"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
/>
<Constraint
android:id="@+id/action0"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:visibility="gone"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/action1"
app:layout_constraintHorizontal_bias="1"
>
</Constraint>
<Constraint
android:id="@+id/action1"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/action0"
app:layout_constraintRight_toLeftOf="@id/action2"
>
</Constraint>
<Constraint
android:id="@+id/action2"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/action1"
app:layout_constraintRight_toLeftOf="@id/action3"
>
</Constraint>
<Constraint
android:id="@+id/action3"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/action2"
app:layout_constraintRight_toLeftOf="@id/action4"
>
</Constraint>
<Constraint
android:id="@+id/action4"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_padding"
android:visibility="gone"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/action3"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_bias="0"
>
</Constraint>
</ConstraintSet>

View File

@@ -1,174 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<ConstraintSet
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
android:id="@+id/icon"
android:layout_width="@dimen/qs_media_icon_size"
android:layout_height="@dimen/qs_media_icon_size"
android:translationY="@dimen/qs_media_icon_offset"
android:translationX="@dimen/qs_media_icon_offset"
app:layout_constraintEnd_toEndOf="@id/album_art"
app:layout_constraintBottom_toBottomOf="@id/album_art"
/>
<Constraint
android:id="@+id/media_seamless"
android:layout_width="wrap_content"
android:layout_height="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintHorizontal_bias="1"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="48dp"
app:layout_constraintHeight_min="48dp"
android:paddingTop="@dimen/qs_media_padding"
android:paddingEnd="@dimen/qs_media_padding"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
android:layout_marginBottom="4dp" />
<Constraint
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size"
android:layout_height="@dimen/qs_media_album_size"
android:layout_marginTop="@dimen/qs_media_padding"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginBottom="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
<!-- Song name -->
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="26dp"
android:layout_marginStart="@dimen/qs_media_info_margin"
android:layout_marginEnd="@dimen/qs_media_padding"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toTopOf="@+id/album_art"
app:layout_constraintStart_toEndOf="@id/album_art"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"/>
<!-- Artist name -->
<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_marginBottom="@dimen/qs_media_info_margin"
app:layout_constrainedWidth="true"
android:layout_marginTop="1dp"
app:layout_constraintTop_toBottomOf="@id/header_title"
app:layout_constraintStart_toStartOf="@id/header_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"/>
<!-- Seek Bar -->
<Constraint
android:id="@+id/media_progress_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="34dp"
app:layout_constraintTop_toBottomOf="@id/center_horizontal_guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<Constraint
android:id="@+id/notification_media_progress_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginStart="@dimen/qs_media_padding"
app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<Constraint
android:id="@+id/action0"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_action_spacing"
android:layout_marginBottom="@dimen/qs_media_action_margin"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/action1"
app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent">
</Constraint>
<Constraint
android:id="@+id/action1"
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="@dimen/qs_media_action_margin"
app:layout_constraintLeft_toRightOf="@id/action0"
app:layout_constraintRight_toLeftOf="@id/action2"
app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent">
</Constraint>
<Constraint
android:id="@+id/action2"
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="@dimen/qs_media_action_margin"
app:layout_constraintLeft_toRightOf="@id/action1"
app:layout_constraintRight_toLeftOf="@id/action3"
app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent">
</Constraint>
<Constraint
android:id="@+id/action3"
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="@dimen/qs_media_action_margin"
app:layout_constraintLeft_toRightOf="@id/action2"
app:layout_constraintRight_toLeftOf="@id/action4"
app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent">
</Constraint>
<Constraint
android:id="@+id/action4"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
android:layout_marginEnd="@dimen/qs_media_padding"
android:layout_marginBottom="@dimen/qs_media_action_margin"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toRightOf="@id/action3"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent">
</Constraint>
</ConstraintSet>

View File

@@ -146,7 +146,6 @@ public class Flags {
// 900 - media
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, true);
public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);

View File

@@ -45,8 +45,7 @@ class KeyguardMediaController @Inject constructor(
private val statusBarStateController: SysuiStatusBarStateController,
private val notifLockscreenUserManager: NotificationLockscreenUserManager,
private val context: Context,
configurationController: ConfigurationController,
private val mediaFlags: MediaFlags
configurationController: ConfigurationController
) {
init {
@@ -62,11 +61,7 @@ class KeyguardMediaController @Inject constructor(
})
// First let's set the desired state that we want for this host
mediaHost.expansion = if (mediaFlags.useMediaSessionLayout()) {
MediaHostState.EXPANDED
} else {
MediaHostState.COLLAPSED
}
mediaHost.expansion = MediaHostState.EXPANDED
mediaHost.showsOnlyActiveMedia = true
mediaHost.falsingProtectionNeeded = true

View File

@@ -419,15 +419,8 @@ class MediaCarouselController @Inject constructor(
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
var newPlayer = mediaControlPanelFactory.get()
if (mediaFlags.useMediaSessionLayout()) {
newPlayer.attachPlayer(
PlayerSessionViewHolder.create(LayoutInflater.from(context), mediaContent),
MediaViewController.TYPE.PLAYER_SESSION)
} else {
newPlayer.attachPlayer(
PlayerViewHolder.create(LayoutInflater.from(context), mediaContent),
MediaViewController.TYPE.PLAYER)
}
newPlayer.attachPlayer(MediaViewHolder.create(
LayoutInflater.from(context), mediaContent))
newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
@@ -544,11 +537,7 @@ class MediaCarouselController @Inject constructor(
}
private fun getForegroundColor(): Int {
return if (mediaFlags.useMediaSessionLayout()) {
context.getColor(R.color.material_dynamic_neutral_variant80)
} else {
context.getColor(R.color.material_dynamic_secondary10)
}
return context.getColor(R.color.material_dynamic_neutral_variant80)
}
private fun updatePageIndicator() {

View File

@@ -65,7 +65,6 @@ import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -96,22 +95,22 @@ public class MediaControlPanel {
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
// Button IDs for QS controls
static final int[] ACTION_IDS = {
R.id.action0,
R.id.action1,
R.id.action2,
R.id.action3,
R.id.action4
};
// Buttons to show in small player when using semantic actions
private static final List<Integer> SEMANTIC_ACTION_IDS = List.of(
private static final List<Integer> SEMANTIC_ACTIONS_COMPACT = List.of(
R.id.actionPlayPause,
R.id.actionPrev,
R.id.actionNext
);
// Buttons to show in small player when using semantic actions
private static final List<Integer> SEMANTIC_ACTIONS_ALL = List.of(
R.id.actionPlayPause,
R.id.actionPrev,
R.id.actionNext,
R.id.action0,
R.id.action1
);
private final SeekBarViewModel mSeekBarViewModel;
private SeekBarObserver mSeekBarObserver;
protected final Executor mBackgroundExecutor;
@@ -127,8 +126,6 @@ public class MediaControlPanel {
private MediaController mController;
private Lazy<MediaDataManager> mMediaDataManagerLazy;
private int mBackgroundColor;
private int mDevicePadding;
private int mAlbumArtSize;
// Instance id for logging purpose.
protected int mInstanceId = -1;
// Uid for the media app.
@@ -167,7 +164,6 @@ public class MediaControlPanel {
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
mSystemClock = systemClock;
loadDimens();
mSeekBarViewModel.setLogSmartspaceClick(() -> {
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
@@ -184,12 +180,6 @@ public class MediaControlPanel {
mMediaViewController.onDestroy();
}
private void loadDimens() {
mAlbumArtSize = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_album_size);
mDevicePadding = mContext.getResources()
.getDimensionPixelSize(R.dimen.qs_media_album_device_padding);
}
/**
* Get the view holder used to display media controls.
*
@@ -241,15 +231,14 @@ public class MediaControlPanel {
}
/** Attaches the player to the player view holder. */
public void attachPlayer(MediaViewHolder vh, MediaViewController.TYPE playerType) {
public void attachPlayer(MediaViewHolder vh) {
mMediaViewHolder = vh;
TransitionLayout player = vh.getPlayer();
boolean useSessionLayout = playerType == MediaViewController.TYPE.PLAYER_SESSION;
mSeekBarObserver = new SeekBarObserver(vh, useSessionLayout);
mSeekBarObserver = new SeekBarObserver(vh);
mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
mMediaViewController.attach(player, playerType);
mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER);
vh.getPlayer().setOnLongClickListener(v -> {
if (!mMediaViewController.isGutsVisible()) {
@@ -305,16 +294,6 @@ public class MediaControlPanel {
if (mMediaViewHolder == null) {
return;
}
bindPlayerCommon(data, key);
if (mMediaViewHolder instanceof PlayerViewHolder) {
bindNotificationPlayer(data, key);
} else if (mMediaViewHolder instanceof PlayerSessionViewHolder) {
bindSessionPlayer(data, key);
}
}
/** Bind elements common to both layouts */
private void bindPlayerCommon(@NonNull MediaData data, String key) {
mKey = key;
MediaSession.Token token = data.getToken();
PackageManager packageManager = mContext.getPackageManager();
@@ -371,18 +350,24 @@ public class MediaControlPanel {
final MediaController controller = getController();
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
// Guts label
boolean isDismissible = data.isClearable();
mMediaViewHolder.getLongPressText().setText(isDismissible
? R.string.controls_media_close_session
: R.string.controls_media_active_session);
bindOutputSwitcherChip(data);
bindLongPressMenu(data);
bindActionButtons(data);
bindArtworkAndColors(data);
// TODO: We don't need to refresh this state constantly, only if the state actually changed
// to something which might impact the measurement
mMediaViewController.refreshState();
}
private void bindOutputSwitcherChip(MediaData data) {
// Output switcher chip
ViewGroup seamlessView = mMediaViewHolder.getSeamless();
seamlessView.setVisibility(View.VISIBLE);
ImageView iconView = mMediaViewHolder.getSeamlessIcon();
TextView deviceName = mMediaViewHolder.getSeamlessText();
final MediaDeviceData device = data.getDevice();
// Disable clicking on output switcher for invalid devices and resumption controls
final boolean seamlessDisabled = (device != null && !device.getEnabled())
|| data.getResumption();
@@ -426,9 +411,16 @@ public class MediaControlPanel {
mMediaOutputDialogFactory.create(data.getPackageName(), true,
mMediaViewHolder.getSeamlessButton());
}
});
});
}
// Dismiss
private void bindLongPressMenu(MediaData data) {
boolean isDismissible = data.isClearable();
mMediaViewHolder.getLongPressText().setText(isDismissible
? R.string.controls_media_close_session
: R.string.controls_media_active_session);
// Dismiss button
mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
mMediaViewHolder.getDismiss().setEnabled(isDismissible);
mMediaViewHolder.getDismiss().setOnClickListener(v -> {
@@ -443,136 +435,16 @@ public class MediaControlPanel {
MediaViewController.GUTS_ANIMATION_DURATION + 100)) {
Log.w(TAG, "Manager failed to dismiss media " + mKey);
// Remove directly from carousel so user isn't stuck with defunct controls
mMediaCarouselController.removePlayer(key, false, false);
mMediaCarouselController.removePlayer(mKey, false, false);
}
} else {
Log.w(TAG, "Dismiss media with null notification. Token uid="
+ data.getToken().getUid());
}
});
// TODO: We don't need to refresh this state constantly, only if the state actually changed
// to something which might impact the measurement
mMediaViewController.refreshState();
}
/** Bind elements specific to PlayerViewHolder */
private void bindNotificationPlayer(@NonNull MediaData data, String key) {
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
// Album art
ImageView albumView = mMediaViewHolder.getAlbumView();
boolean hasArtwork = data.getArtwork() != null;
if (hasArtwork) {
Drawable artwork = getScaledThumbnail(data.getArtwork());
albumView.setPadding(0, 0, 0, 0);
albumView.setImageDrawable(artwork);
} else {
Drawable deviceIcon;
if (data.getDevice() != null && data.getDevice().getIcon() != null) {
deviceIcon = data.getDevice().getIcon().getConstantState().newDrawable().mutate();
} else {
deviceIcon = getContext().getDrawable(R.drawable.ic_headphone);
}
deviceIcon.setTintList(ColorStateList.valueOf(mBackgroundColor));
albumView.setPadding(mDevicePadding, mDevicePadding, mDevicePadding, mDevicePadding);
albumView.setImageDrawable(deviceIcon);
}
// App icon - use notification icon
ImageView appIconView = mMediaViewHolder.getAppIcon();
appIconView.clearColorFilter();
if (data.getAppIcon() != null && !data.getResumption()) {
appIconView.setImageIcon(data.getAppIcon());
int color = mContext.getColor(R.color.material_dynamic_secondary10);
appIconView.setColorFilter(color);
} else {
// Resume players use launcher icon
appIconView.setColorFilter(getGrayscaleFilter());
try {
Drawable icon = mContext.getPackageManager().getApplicationIcon(
data.getPackageName());
appIconView.setImageDrawable(icon);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Cannot find icon for package " + data.getPackageName(), e);
appIconView.setImageResource(R.drawable.ic_music_note);
}
}
// Media action buttons
List<MediaAction> actionIcons = data.getActions();
List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
// If we got session actions, use those instead
if (data.getSemanticActions() != null) {
MediaButton semanticActions = data.getSemanticActions();
actionIcons = new ArrayList<MediaAction>();
actionIcons.add(semanticActions.getCustom0());
actionIcons.add(semanticActions.getPrevOrCustom());
actionIcons.add(semanticActions.getPlayOrPause());
actionIcons.add(semanticActions.getNextOrCustom());
actionIcons.add(semanticActions.getCustom1());
actionsWhenCollapsed = new ArrayList<Integer>();
actionsWhenCollapsed.add(1);
actionsWhenCollapsed.add(2);
actionsWhenCollapsed.add(3);
}
int i = 0;
for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
int actionId = ACTION_IDS[i];
boolean visibleInCompat = actionsWhenCollapsed.contains(i);
final ImageButton button = mMediaViewHolder.getAction(actionId);
MediaAction mediaAction = actionIcons.get(i);
if (mediaAction != null) {
button.setImageIcon(mediaAction.getIcon());
button.setContentDescription(mediaAction.getContentDescription());
Runnable action = mediaAction.getAction();
if (action == null) {
button.setEnabled(false);
} else {
button.setEnabled(true);
button.setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
/* isRecommendationCard */ false);
action.run();
}
});
}
setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
} else {
button.setImageIcon(null);
button.setContentDescription(null);
button.setEnabled(false);
setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
// for expanded layout, set as INVISIBLE so that we still reserve space in the UI
expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE);
expandedSet.setAlpha(actionId, 0.0f);
}
}
// Hide any unused buttons
for (; i < ACTION_IDS.length; i++) {
setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */);
setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /* visible */);
}
// If no actions, set the first view as INVISIBLE so expanded height remains constant
if (actionIcons.size() == 0) {
expandedSet.setVisibility(ACTION_IDS[0], ConstraintSet.INVISIBLE);
}
}
/** Bind elements specific to PlayerSessionViewHolder */
private void bindSessionPlayer(@NonNull MediaData data, String key) {
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
private void bindArtworkAndColors(MediaData data) {
// Default colors
int surfaceColor = mBackgroundColor;
int accentPrimary = com.android.settingslib.Utils.getColorAttr(mContext,
@@ -592,7 +464,7 @@ public class MediaControlPanel {
boolean hasArtwork = data.getArtwork() != null;
if (hasArtwork) {
colorScheme = new ColorScheme(WallpaperColors.fromBitmap(data.getArtwork().getBitmap()),
true);
true);
// Scale artwork to fit background
int width = mMediaViewHolder.getPlayer().getWidth();
@@ -661,6 +533,14 @@ public class MediaControlPanel {
seekbar.setProgressTintList(textColorList);
seekbar.setProgressBackgroundTintList(ColorStateList.valueOf(textTertiary));
// Action buttons
mMediaViewHolder.getActionPlayPause().setBackgroundTintList(accentColorList);
mMediaViewHolder.getActionPlayPause().setImageTintList(
ColorStateList.valueOf(textPrimaryInverse));
for (ImageButton button : mMediaViewHolder.getTransparentActionButtons()) {
button.setImageTintList(textColorList);
}
// Output switcher
View seamlessView = mMediaViewHolder.getSeamlessButton();
seamlessView.setBackgroundTintList(accentColorList);
@@ -669,70 +549,6 @@ public class MediaControlPanel {
TextView seamlessText = mMediaViewHolder.getSeamlessText();
seamlessText.setTextColor(surfaceColor);
// 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) {
// 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), collapsedSet, expandedSet, true);
setSemanticButton(sessionHolder.getActionNext(), semanticActions.getNextOrCustom(),
textColorList, collapsedSet, expandedSet, true);
setSemanticButton(sessionHolder.getActionPrev(), semanticActions.getPrevOrCustom(),
textColorList, collapsedSet, expandedSet, true);
setSemanticButton(sessionHolder.getAction0(), semanticActions.getCustom0(),
textColorList, collapsedSet, expandedSet, false);
setSemanticButton(sessionHolder.getAction1(), semanticActions.getCustom1(),
textColorList, collapsedSet, expandedSet, false);
} else {
// 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);
@@ -743,10 +559,61 @@ public class MediaControlPanel {
mMediaViewHolder.getDismissText().setBackgroundTintList(accentColorList);
}
private void bindActionButtons(MediaData data) {
MediaButton semanticActions = data.getSemanticActions();
ImageButton[] genericButtons = new ImageButton[]{
mMediaViewHolder.getAction0(),
mMediaViewHolder.getAction1(),
mMediaViewHolder.getAction2(),
mMediaViewHolder.getAction3(),
mMediaViewHolder.getAction4()};
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
if (semanticActions != null) {
// Hide all the generic buttons
for (ImageButton b: genericButtons) {
setVisibleAndAlpha(collapsedSet, b.getId(), false);
setVisibleAndAlpha(expandedSet, b.getId(), false);
}
for (int id : SEMANTIC_ACTIONS_ALL) {
boolean showInCompact = SEMANTIC_ACTIONS_COMPACT.contains(id);
ImageButton button = mMediaViewHolder.getAction(id);
MediaAction action = semanticActions.getActionById(id);
setSemanticButton(button, action, collapsedSet, expandedSet, showInCompact);
}
} else {
// Hide buttons that only appear for semantic actions
for (int id : SEMANTIC_ACTIONS_COMPACT) {
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), collapsedSet,
expandedSet, showInCompact);
}
for (; i < 5; i++) {
// Hide any unused buttons
setSemanticButton(genericButtons[i], null, 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);
}
private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
ColorStateList fgColor, ConstraintSet collapsedSet, ConstraintSet expandedSet,
boolean showInCompact) {
button.setImageTintList(fgColor);
ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) {
if (mediaAction != null) {
button.setImageIcon(mediaAction.getIcon());
button.setContentDescription(mediaAction.getContentDescription());
@@ -1007,34 +874,6 @@ public class MediaControlPanel {
mMediaViewController.openGuts();
}
/**
* Scale drawable to fit into the square album art thumbnail
*/
@UiThread
private Drawable getScaledThumbnail(Icon icon) {
if (icon == null) {
return null;
}
// Let's scale down the View, such that the content always nicely fills the view.
// ThumbnailUtils actually scales it down such that it may not be filled for odd aspect
// ratios
Drawable drawable = icon.loadDrawable(mContext);
float aspectRatio = drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth();
Rect bounds;
if (aspectRatio > 1.0f) {
bounds = new Rect(0, 0, mAlbumArtSize, (int) (mAlbumArtSize * aspectRatio));
} else {
bounds = new Rect(0, 0, (int) (mAlbumArtSize / aspectRatio), mAlbumArtSize);
}
if (bounds.width() > mAlbumArtSize || bounds.height() > mAlbumArtSize) {
float offsetX = (bounds.width() - mAlbumArtSize) / 2.0f;
float offsetY = (bounds.height() - mAlbumArtSize) / 2.0f;
bounds.offset((int) -offsetX, (int) -offsetY);
}
drawable.setBounds(bounds);
return drawable;
}
/**
* Scale artwork to fill the background of the panel
*/

View File

@@ -20,6 +20,7 @@ import android.app.PendingIntent
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.session.MediaSession
import com.android.systemui.R
/** State of a media view. */
data class MediaData(
@@ -154,7 +155,18 @@ data class MediaButton(
* Second custom action space
*/
var custom1: MediaAction? = null
)
) {
fun getActionById(id: Int): MediaAction? {
return when (id) {
R.id.actionPlayPause -> playOrPause
R.id.actionNext -> nextOrCustom
R.id.actionPrev -> prevOrCustom
R.id.action0 -> custom0
R.id.action1 -> custom1
else -> null
}
}
}
/** State of a media action. */
data class MediaAction(

View File

@@ -34,13 +34,6 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
}
/**
* Check whether media controls should use the new session-based layout
*/
fun useMediaSessionLayout(): Boolean {
return featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT)
}
/**
* Check whether we support displaying information about mute await connections.
*/

View File

@@ -22,7 +22,10 @@ import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.R
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.*
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionLayoutController
import com.android.systemui.util.animation.TransitionViewState
import javax.inject.Inject
/**
@@ -40,7 +43,7 @@ class MediaViewController @Inject constructor(
* session-based player, or recommendation
*/
enum class TYPE {
PLAYER, PLAYER_SESSION, RECOMMENDATION
PLAYER, RECOMMENDATION
}
companion object {
@@ -259,13 +262,11 @@ class MediaViewController @Inject constructor(
*/
private fun setGutsViewState(viewState: TransitionViewState) {
val controlsIds = when (type) {
TYPE.PLAYER -> PlayerViewHolder.controlsIds
TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.controlsIds
TYPE.PLAYER -> MediaViewHolder.controlsIds
TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
}
val gutsIds = when (type) {
TYPE.PLAYER -> PlayerViewHolder.gutsIds
TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.gutsIds
TYPE.PLAYER -> MediaViewHolder.gutsIds
TYPE.RECOMMENDATION -> RecommendationViewHolder.gutsIds
}
controlsIds.forEach { id ->
@@ -287,15 +288,17 @@ class MediaViewController @Inject constructor(
/**
* Apply squishFraction to a copy of viewState such that the cached version is untouched.
*/
private fun squishViewState(viewState: TransitionViewState,
squishFraction: Float): TransitionViewState {
private fun squishViewState(
viewState: TransitionViewState,
squishFraction: Float
): TransitionViewState {
val squishedViewState = viewState.copy()
squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
val albumArtViewState = viewState.widgetStates.get(R.id.album_art)
if (albumArtViewState != null) {
albumArtViewState.height = squishedViewState.height
}
return squishedViewState;
return squishedViewState
}
/**
@@ -314,7 +317,7 @@ class MediaViewController @Inject constructor(
if (viewState != null) {
// we already have cached this measurement, let's continue
if (state.squishFraction < 1f) {
return squishViewState(viewState, state.squishFraction);
return squishViewState(viewState, state.squishFraction)
}
return viewState
}
@@ -351,7 +354,7 @@ class MediaViewController @Inject constructor(
state.expansion)
}
if (state.squishFraction < 1f) {
return squishViewState(result, state.squishFraction);
return squishViewState(result, state.squishFraction)
}
return result
}
@@ -492,10 +495,6 @@ class MediaViewController @Inject constructor(
// These XML resources contain ConstraintSets that will apply to this player type's layout
when (type) {
TYPE.PLAYER -> {
collapsedLayout.load(context, R.xml.media_collapsed)
expandedLayout.load(context, R.xml.media_expanded)
}
TYPE.PLAYER_SESSION -> {
collapsedLayout.load(context, R.xml.media_session_collapsed)
expandedLayout.load(context, R.xml.media_session_expanded)
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.media
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
@@ -29,9 +30,9 @@ import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "MediaViewHolder"
/**
* Parent class for different media player views
* Holder class for media player view
*/
abstract class MediaViewHolder constructor(itemView: View) {
class MediaViewHolder constructor(itemView: View) {
val player = itemView as TransitionLayout
// Player information
@@ -61,6 +62,9 @@ abstract class MediaViewHolder constructor(itemView: View) {
val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
// Action Buttons
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 action0 = itemView.requireViewById<ImageButton>(R.id.action0)
val action1 = itemView.requireViewById<ImageButton>(R.id.action1)
val action2 = itemView.requireViewById<ImageButton>(R.id.action2)
@@ -73,6 +77,9 @@ abstract class MediaViewHolder constructor(itemView: View) {
it.registerLightSource(cancel)
it.registerLightSource(dismiss)
it.registerLightSource(settings)
it.registerLightSource(actionPlayPause)
it.registerLightSource(actionNext)
it.registerLightSource(actionPrev)
it.registerLightSource(action0)
it.registerLightSource(action1)
it.registerLightSource(action2)
@@ -81,7 +88,33 @@ abstract class MediaViewHolder constructor(itemView: View) {
}
}
abstract fun getAction(id: Int): ImageButton
fun getAction(id: Int): ImageButton {
return when (id) {
R.id.actionPlayPause -> actionPlayPause
R.id.actionNext -> actionNext
R.id.actionPrev -> actionPrev
R.id.action0 -> action0
R.id.action1 -> action1
R.id.action2 -> action2
R.id.action3 -> action3
R.id.action4 -> action4
else -> {
throw IllegalArgumentException()
}
}
}
fun getTransparentActionButtons(): List<ImageButton> {
return listOf(
actionNext,
actionPrev,
action0,
action1,
action2,
action3,
action4
)
}
fun marquee(start: Boolean, delay: Long) {
val longPressTextHandler = longPressText.getHandler()
@@ -91,4 +124,52 @@ abstract class MediaViewHolder constructor(itemView: View) {
}
longPressTextHandler.postDelayed({ longPressText.setSelected(start) }, delay)
}
companion object {
/**
* Creates a MediaViewHolder.
*
* @param inflater LayoutInflater to use to inflate the layout.
* @param parent Parent of inflated view.
*/
@JvmStatic fun create(
inflater: LayoutInflater,
parent: ViewGroup
): MediaViewHolder {
val mediaView = inflater.inflate(R.layout.media_session_view, parent, false)
mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
return MediaViewHolder(mediaView).apply {
// Media playback is in the direction of tape, not time, so it stays LTR
seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR
}
}
val controlsIds = setOf(
R.id.icon,
R.id.app_name,
R.id.header_title,
R.id.header_artist,
R.id.media_seamless,
R.id.media_progress_bar,
R.id.actionPlayPause,
R.id.actionNext,
R.id.actionPrev,
R.id.action0,
R.id.action1,
R.id.action2,
R.id.action3,
R.id.action4,
R.id.icon
)
val gutsIds = setOf(
R.id.remove_text,
R.id.cancel,
R.id.dismiss,
R.id.settings
)
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.media
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import com.android.systemui.R
/**
* ViewHolder for a media player with MediaSession-based controls
*/
class PlayerSessionViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) {
// Action Buttons
val actionPlayPause = itemView.requireViewById<ImageButton>(R.id.actionPlayPause)
val actionNext = itemView.requireViewById<ImageButton>(R.id.actionNext)
val actionPrev = itemView.requireViewById<ImageButton>(R.id.actionPrev)
init {
(player.background as IlluminationDrawable).let {
it.registerLightSource(actionPlayPause)
it.registerLightSource(actionNext)
it.registerLightSource(actionPrev)
}
}
override fun getAction(id: Int): ImageButton {
return when (id) {
R.id.actionPlayPause -> actionPlayPause
R.id.actionNext -> actionNext
R.id.actionPrev -> actionPrev
R.id.action0 -> action0
R.id.action1 -> action1
R.id.action2 -> action2
R.id.action3 -> action3
R.id.action4 -> action4
else -> {
throw IllegalArgumentException()
}
}
}
companion object {
/**
* Creates a PlayerSessionViewHolder.
*
* @param inflater LayoutInflater to use to inflate the layout.
* @param parent Parent of inflated view.
*/
@JvmStatic fun create(
inflater: LayoutInflater,
parent: ViewGroup
): PlayerSessionViewHolder {
val mediaView = inflater.inflate(R.layout.media_session_view, parent, false)
mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
return PlayerSessionViewHolder(mediaView).apply {
// Media playback is in the direction of tape, not time, so it stays LTR
seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR
}
}
val controlsIds = setOf(
R.id.icon,
R.id.app_name,
R.id.header_title,
R.id.header_artist,
R.id.media_seamless,
R.id.media_progress_bar,
R.id.actionPlayPause,
R.id.actionNext,
R.id.actionPrev,
R.id.action0,
R.id.action1,
R.id.action2,
R.id.action3,
R.id.action4,
R.id.icon
)
val gutsIds = setOf(
R.id.remove_text,
R.id.cancel,
R.id.dismiss,
R.id.settings
)
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.media
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import com.android.systemui.R
/**
* ViewHolder for a media player.
*/
class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) {
// Seek bar
val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time)
override val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
override val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time)
override fun getAction(id: Int): ImageButton {
return when (id) {
R.id.action0 -> action0
R.id.action1 -> action1
R.id.action2 -> action2
R.id.action3 -> action3
R.id.action4 -> action4
else -> {
throw IllegalArgumentException()
}
}
}
companion object {
/**
* Creates a PlayerViewHolder.
*
* @param inflater LayoutInflater to use to inflate the layout.
* @param parent Parent of inflated view.
*/
@JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder {
val mediaView = inflater.inflate(R.layout.media_view, parent, false)
mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
return PlayerViewHolder(mediaView).apply {
// Media playback is in the direction of tape, not time, so it stays LTR
seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR
progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR
}
}
val controlsIds = setOf(
R.id.icon,
R.id.app_name,
R.id.album_art,
R.id.header_title,
R.id.header_artist,
R.id.media_seamless,
R.id.notification_media_progress_time,
R.id.media_progress_bar,
R.id.action0,
R.id.action1,
R.id.action2,
R.id.action3,
R.id.action4,
R.id.icon
)
val gutsIds = setOf(
R.id.remove_text,
R.id.cancel,
R.id.dismiss,
R.id.settings
)
}
}

View File

@@ -27,28 +27,17 @@ import com.android.systemui.R
* <p>Updates the seek bar views in response to changes to the model.
*/
class SeekBarObserver(
private val holder: MediaViewHolder,
private val useSessionLayout: Boolean
private val holder: MediaViewHolder
) : Observer<SeekBarViewModel.Progress> {
val seekBarEnabledMaxHeight = holder.seekBar.context.resources
.getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height)
val seekBarDisabledHeight = holder.seekBar.context.resources
.getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height)
val seekBarEnabledVerticalPadding = if (useSessionLayout) {
holder.seekBar.context.resources
val seekBarEnabledVerticalPadding = holder.seekBar.context.resources
.getDimensionPixelSize(R.dimen.qs_media_session_enabled_seekbar_vertical_padding)
} else {
holder.seekBar.context.resources
.getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_vertical_padding)
}
val seekBarDisabledVerticalPadding = if (useSessionLayout) {
holder.seekBar.context.resources
val seekBarDisabledVerticalPadding = holder.seekBar.context.resources
.getDimensionPixelSize(R.dimen.qs_media_session_disabled_seekbar_vertical_padding)
} else {
holder.seekBar.context.resources
.getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding)
}
init {
val seekBarProgressWavelength = holder.seekBar.context.resources

View File

@@ -33,7 +33,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.media.MediaFlags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostState;
@@ -46,7 +45,6 @@ import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Utils;
import javax.inject.Inject;
import javax.inject.Named;
@@ -65,7 +63,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
private final FalsingManager mFalsingManager;
private final BrightnessController mBrightnessController;
private final BrightnessSliderController mBrightnessSliderController;
private final MediaFlags mMediaFlags;
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
private final FeatureFlags mFeatureFlags;
@@ -75,7 +72,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
updateMediaExpansion();
mView.updateResources();
mQsSecurityFooter.onConfigurationChanged();
if (mView.isListening()) {
@@ -105,8 +101,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
FalsingManager falsingManager, FeatureFlags featureFlags,
MediaFlags mediaFlags) {
FalsingManager falsingManager, FeatureFlags featureFlags) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
mQSFgsManagerFooter = qsFgsManagerFooter;
@@ -117,7 +112,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mFalsingManager = falsingManager;
mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
mMediaFlags = mediaFlags;
mView.setBrightnessView(mBrightnessSliderController.getRootView());
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
@@ -129,7 +123,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
@Override
public void onInit() {
super.onInit();
updateMediaExpansion();
mMediaHost.setExpansion(MediaHostState.EXPANDED);
mMediaHost.setShowsOnlyActiveMedia(false);
mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
mQsCustomizerController.init();
@@ -137,17 +131,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mQSFgsManagerFooter.init();
}
private void updateMediaExpansion() {
boolean inSplitShade = Utils.shouldUseSplitNotificationShade(getResources());
float expansion;
if (inSplitShade && !mMediaFlags.useMediaSessionLayout()) {
expansion = MediaHostState.COLLAPSED;
} else {
expansion = MediaHostState.EXPANDED;
}
mMediaHost.setExpansion(expansion);
}
@Override
protected void onViewAttached() {
super.onViewAttached();

View File

@@ -26,7 +26,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaFlags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
@@ -53,7 +52,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
}
};
private final MediaFlags mMediaFlags;
private final boolean mUsingCollapsedLandscapeMedia;
@Inject
@@ -62,14 +60,12 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
@Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia,
MediaFlags mediaFlags,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager
) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager);
mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia;
mMediaFlags = mediaFlags;
}
@Override
@@ -84,8 +80,7 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
int rotation = getRotation();
boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE
|| rotation == RotationUtils.ROTATION_SEASCAPE;
if (mMediaFlags.useMediaSessionLayout()
&& (!mUsingCollapsedLandscapeMedia || !isLandscape)) {
if (!mUsingCollapsedLandscapeMedia || !isLandscape) {
mMediaHost.setExpansion(MediaHost.EXPANDED);
} else {
mMediaHost.setExpansion(MediaHost.COLLAPSED);

View File

@@ -52,8 +52,6 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
private lateinit var mediaFlags: MediaFlags
@Mock
private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
@@ -73,15 +71,13 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
.thenReturn(true)
whenever(mediaHost.hostView).thenReturn(hostView)
hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
keyguardMediaController = KeyguardMediaController(
mediaHost,
bypassController,
statusBarStateController,
notificationLockscreenUserManager,
context,
configurationController,
mediaFlags
configurationController
)
keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
keyguardMediaController.useSplitShade = false
@@ -157,22 +153,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
}
@Test
fun testNotificationLayout_collapsedPlayer() {
verify(mediaHost).expansion = MediaHostState.COLLAPSED
}
@Test
fun testSessionLayout_expandedPlayer() {
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
keyguardMediaController = KeyguardMediaController(
mediaHost,
bypassController,
statusBarStateController,
notificationLockscreenUserManager,
context,
configurationController,
mediaFlags
)
fun testMediaHost_expandedPlayer() {
verify(mediaHost).expansion = MediaHostState.EXPANDED
}
}

View File

@@ -89,8 +89,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var holder: PlayerViewHolder
@Mock private lateinit var sessionHolder: PlayerSessionViewHolder
@Mock private lateinit var viewHolder: MediaViewHolder
@Mock private lateinit var view: TransitionLayout
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
@@ -179,8 +178,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
actionPrev = ImageButton(context).also { it.setId(R.id.actionPrev) }
actionNext = ImageButton(context).also { it.setId(R.id.actionNext) }
initPlayerHolderMocks()
initSessionHolderMocks()
initMediaViewHolderMocks()
// Create media session
val metadataBuilder = MediaMetadata.Builder().apply {
@@ -217,9 +215,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
/**
* Initialize elements common to both view holders
* Initialize elements in media view holder
*/
private fun initMediaViewHolderMocks(viewHolder: MediaViewHolder) {
private fun initMediaViewHolderMocks() {
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
whenever(viewHolder.albumView).thenReturn(albumView)
@@ -233,6 +231,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.seekBar).thenReturn(seekBar)
// Action buttons
whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause)
whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
whenever(viewHolder.actionNext).thenReturn(actionNext)
whenever(viewHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
whenever(viewHolder.actionPrev).thenReturn(actionPrev)
whenever(viewHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
whenever(viewHolder.action0).thenReturn(action0)
whenever(viewHolder.getAction(R.id.action0)).thenReturn(action0)
whenever(viewHolder.action1).thenReturn(action1)
@@ -255,27 +259,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
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() {
initMediaViewHolderMocks(sessionHolder)
// Semantic action buttons
whenever(sessionHolder.actionPlayPause).thenReturn(actionPlayPause)
whenever(sessionHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
whenever(sessionHolder.actionNext).thenReturn(actionNext)
whenever(sessionHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
whenever(sessionHolder.actionPrev).thenReturn(actionPrev)
whenever(sessionHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
}
@After
fun tearDown() {
session.release()
@@ -290,41 +273,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
fun bindSemanticActionsOldLayout() {
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"),
nextOrCustom = MediaAction(icon, Runnable {}, "next"),
custom0 = MediaAction(icon, null, "custom 0"),
custom1 = MediaAction(icon, null, "custom 1")
)
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.bindPlayer(state, PACKAGE)
verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
assertThat(action0.contentDescription).isEqualTo("custom 0")
assertThat(action0.isEnabled()).isFalse()
verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
assertThat(action1.isEnabled()).isFalse()
verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
assertThat(action2.isEnabled()).isTrue()
assertThat(action2.contentDescription).isEqualTo("play")
verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE)
assertThat(action3.isEnabled()).isTrue()
assertThat(action3.contentDescription).isEqualTo("next")
verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
assertThat(action4.contentDescription).isEqualTo("custom 1")
assertThat(action4.isEnabled()).isFalse()
}
@Test
fun bindSemanticActionsNewLayout() {
fun bindSemanticActions() {
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"),
@@ -334,7 +283,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
)
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
assertThat(actionPrev.isEnabled()).isFalse()
@@ -370,7 +319,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
fun bindNotificationActionsNewLayout() {
fun bindNotificationActions() {
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
val actions = listOf(
MediaAction(icon, Runnable {}, "previous"),
@@ -383,7 +332,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
actionsToShowInCompact = listOf(1, 2),
semanticActions = null)
player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
// Verify semantic actions are hidden
@@ -420,7 +369,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindText() {
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, PACKAGE)
assertThat(titleText.getText()).isEqualTo(TITLE)
assertThat(artistText.getText()).isEqualTo(ARTIST)
@@ -428,7 +377,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindDevice() {
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, PACKAGE)
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME)
@@ -438,7 +387,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindDisabledDevice() {
seamless.id = 1
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
val state = mediaData.copy(device = disabledDevice)
player.bindPlayer(state, PACKAGE)
assertThat(seamless.isEnabled()).isFalse()
@@ -449,7 +398,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindNullDevice() {
val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
val state = mediaData.copy(device = null)
player.bindPlayer(state, PACKAGE)
assertThat(seamless.isEnabled()).isTrue()
@@ -459,7 +408,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindDeviceResumptionPlayer() {
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
val state = mediaData.copy(resumption = true)
player.bindPlayer(state, PACKAGE)
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
@@ -468,32 +417,32 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun longClick_gutsClosed() {
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
whenever(mediaViewController.isGutsVisible).thenReturn(false)
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
verify(holder.player).setOnLongClickListener(captor.capture())
verify(viewHolder.player).setOnLongClickListener(captor.capture())
captor.value.onLongClick(holder.player)
captor.value.onLongClick(viewHolder.player)
verify(mediaViewController).openGuts()
}
@Test
fun longClick_gutsOpen() {
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
whenever(mediaViewController.isGutsVisible).thenReturn(true)
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
verify(holder.player).setOnLongClickListener(captor.capture())
verify(viewHolder.player).setOnLongClickListener(captor.capture())
captor.value.onLongClick(holder.player)
captor.value.onLongClick(viewHolder.player)
verify(mediaViewController, never()).openGuts()
verify(mediaViewController).closeGuts(false)
}
@Test
fun cancelButtonClick_animation() {
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
cancel.callOnClick()
@@ -502,7 +451,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun settingsButtonClick() {
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
settings.callOnClick()
@@ -515,7 +464,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun dismissButtonClick() {
val mediaKey = "key for dismissal"
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
val state = mediaData.copy(notificationKey = KEY)
player.bindPlayer(state, mediaKey)
@@ -527,7 +476,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun dismissButtonDisabled() {
val mediaKey = "key for dismissal"
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
val state = mediaData.copy(isClearable = false, notificationKey = KEY)
player.bindPlayer(state, mediaKey)
@@ -539,7 +488,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
val mediaKey = "key for dismissal"
whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
player.attachPlayer(viewHolder)
val state = mediaData.copy(notificationKey = KEY)
player.bindPlayer(state, mediaKey)

View File

@@ -1,62 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.media
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
/**
* Tests for PlayerViewHolder.
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class PlayerViewHolderTest : SysuiTestCase() {
private lateinit var inflater: LayoutInflater
private lateinit var parent: ViewGroup
@Before
fun setUp() {
inflater = LayoutInflater.from(context)
parent = FrameLayout(context)
}
@Test
fun create() {
val holder = PlayerViewHolder.create(inflater, parent)
assertThat(holder.player).isNotNull()
}
@Test
fun backgroundIsIlluminationDrawable() {
val holder = PlayerViewHolder.create(inflater, parent)
assertThat(holder.player.background as IlluminationDrawable).isNotNull()
}
}

View File

@@ -43,7 +43,7 @@ class SeekBarObserverTest : SysuiTestCase() {
private val enabledHeight = 2
private lateinit var observer: SeekBarObserver
@Mock private lateinit var mockHolder: PlayerViewHolder
@Mock private lateinit var mockHolder: MediaViewHolder
@Mock private lateinit var mockSquigglyProgress: SquigglyProgress
private lateinit var seekBarView: SeekBar
private lateinit var elapsedTimeView: TextView
@@ -64,10 +64,8 @@ class SeekBarObserverTest : SysuiTestCase() {
elapsedTimeView = TextView(context)
totalTimeView = TextView(context)
whenever(mockHolder.seekBar).thenReturn(seekBarView)
whenever(mockHolder.elapsedTimeView).thenReturn(elapsedTimeView)
whenever(mockHolder.totalTimeView).thenReturn(totalTimeView)
observer = SeekBarObserver(mockHolder, false /* useSessionLayout */)
observer = SeekBarObserver(mockHolder)
}
@Test
@@ -79,8 +77,6 @@ class SeekBarObserverTest : SysuiTestCase() {
// THEN seek bar shows just a thin line with no text
assertThat(seekBarView.isEnabled()).isFalse()
assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
assertThat(elapsedTimeView.getText()).isEqualTo("")
assertThat(totalTimeView.getText()).isEqualTo("")
assertThat(seekBarView.contentDescription).isEqualTo("")
assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight)
}
@@ -93,8 +89,6 @@ class SeekBarObserverTest : SysuiTestCase() {
observer.onChanged(data)
// THEN seek bar is visible and thick
assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.VISIBLE)
assertThat(totalTimeView.getVisibility()).isEqualTo(View.VISIBLE)
assertThat(seekBarView.maxHeight).isEqualTo(enabledHeight)
}
@@ -106,8 +100,6 @@ class SeekBarObserverTest : SysuiTestCase() {
// THEN seek bar shows the progress
assertThat(seekBarView.progress).isEqualTo(3000)
assertThat(seekBarView.max).isEqualTo(120000)
assertThat(elapsedTimeView.getText()).isEqualTo("00:03")
assertThat(totalTimeView.getText()).isEqualTo("02:00")
val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
assertThat(seekBarView.contentDescription).isEqualTo(desc)

View File

@@ -8,7 +8,6 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.MediaFlags
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -49,7 +48,6 @@ class QSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var brightnessSliderFactory: BrightnessSliderController.Factory
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var mediaFlags: MediaFlags
@Mock private lateinit var mediaHost: MediaHost
private lateinit var controller: QSPanelController
@@ -79,8 +77,7 @@ class QSPanelControllerTest : SysuiTestCase() {
brightnessControllerFactory,
brightnessSliderFactory,
falsingManager,
featureFlags,
mediaFlags
featureFlags
)
}
@@ -90,45 +87,12 @@ class QSPanelControllerTest : SysuiTestCase() {
}
@Test
fun onInit_notSplitShade_newMediaLayoutAvailable_setsMediaAsExpanded() {
setSplitShadeEnabled(false)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
fun onInit_setsMediaAsExpanded() {
controller.onInit()
verify(mediaHost).expansion = MediaHostState.EXPANDED
}
@Test
fun onInit_notSplitShade_newMediaLayoutNotAvailable_setsMediaAsExpanded() {
setSplitShadeEnabled(false)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
controller.onInit()
verify(mediaHost).expansion = MediaHostState.EXPANDED
}
@Test
fun onInit_inSplitShade_newMediaLayoutAvailable_setsMediaAsExpanded() {
setSplitShadeEnabled(true)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(true)
controller.onInit()
verify(mediaHost).expansion = MediaHostState.EXPANDED
}
@Test
fun onInit_inSplitShade_newMediaLayoutNotAvailable_setsMediaAsCollapsed() {
setSplitShadeEnabled(true)
whenever(mediaFlags.useMediaSessionLayout()).thenReturn(false)
controller.onInit()
verify(mediaHost).expansion = MediaHostState.COLLAPSED
}
private fun setSplitShadeEnabled(enabled: Boolean) {
mContext.orCreateTestableResources
.addOverride(R.bool.config_use_split_notification_shade, enabled)

View File

@@ -23,7 +23,6 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.MediaFlags
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.qs.QSTile
@@ -58,8 +57,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@Mock
private lateinit var mediaHost: MediaHost
@Mock
private lateinit var mediaFlags: MediaFlags
@Mock
private lateinit var metricsLogger: MetricsLogger
private val uiEventLogger = UiEventLoggerFake()
@Mock
@@ -85,7 +82,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
`when`(quickQSPanel.dumpableTag).thenReturn("")
`when`(quickQSPanel.resources).thenReturn(mContext.resources)
`when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
`when`(mediaFlags.useMediaSessionLayout()).thenReturn(false)
controller = TestQuickQSPanelController(
quickQSPanel,
@@ -94,7 +90,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
false,
mediaHost,
true,
mediaFlags,
metricsLogger,
uiEventLogger,
qsLogger,
@@ -131,20 +126,17 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@Test
fun testMediaExpansionUpdatedWhenConfigurationChanged() {
`when`(mediaFlags.useMediaSessionLayout()).thenReturn(true)
// times(2) because both controller and base controller are registering their listeners
verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
// verify that media starts in the expanded state by default
verify(mediaHost).expansion = MediaHostState.EXPANDED
// Rotate device, verify media size updated
controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
// times(2) because init will have set to collapsed because the flag was off
verify(mediaHost, times(2)).expansion = MediaHostState.COLLAPSED
verify(mediaHost).expansion = MediaHostState.COLLAPSED
}
class TestQuickQSPanelController(
@@ -154,13 +146,12 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
usingMediaPlayer: Boolean,
mediaHost: MediaHost,
usingCollapsedLandscapeMedia: Boolean,
mediaFlags: MediaFlags,
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
dumpManager: DumpManager
) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer,
mediaHost, usingCollapsedLandscapeMedia, mediaFlags, metricsLogger, uiEventLogger, qsLogger,
mediaHost, usingCollapsedLandscapeMedia, metricsLogger, uiEventLogger, qsLogger,
dumpManager) {
private var rotation = RotationUtils.ROTATION_NONE