Add new layout for session based media players
When the flag is enabled to use session-based actions, use the alternate layout as well. This layout has only one size and will show all controls regardless of location. Bug: 209657555 Bug: 209655299 Test: atest Test: manual - flip flag and check player layout Change-Id: I3d048d52cdd11872d40f7735892f3e6c83462950
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/media_seamless_border">
|
||||
<item android:id="@android:id/background">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/media_seamless_border" />
|
||||
<size android:width="48dp" android:height="48dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
313
packages/SystemUI/res/layout/media_session_view.xml
Normal file
313
packages/SystemUI/res/layout/media_session_view.xml
Normal file
@@ -0,0 +1,313 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<!-- Layout for media session-based controls -->
|
||||
<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" />
|
||||
|
||||
<!-- App icon -->
|
||||
<com.android.internal.widget.CachingIconView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="@dimen/qs_media_padding"
|
||||
android:layout_marginTop="@dimen/qs_media_padding"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Seamless Output Switcher -->
|
||||
<LinearLayout
|
||||
android:id="@+id/media_seamless"
|
||||
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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="@dimen/qs_center_guideline_padding"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
|
||||
app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
|
||||
<LinearLayout
|
||||
android:id="@+id/media_seamless_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/qs_seamless_height"
|
||||
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>
|
||||
|
||||
<!-- Song name -->
|
||||
<TextView
|
||||
android:id="@+id/header_title"
|
||||
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
||||
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" />
|
||||
|
||||
<!-- Artist name -->
|
||||
<TextView
|
||||
android:id="@+id/header_artist"
|
||||
android:fontFamily="@*android:string/config_headlineFontFamily"
|
||||
android:singleLine="true"
|
||||
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="1dp"
|
||||
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" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/actionPlayPause"
|
||||
style="@style/MediaPlayer.SessionAction.Primary"
|
||||
android:layout_width="48dp"
|
||||
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"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/media_seamless"
|
||||
app:layout_constraintBottom_toTopOf="@id/actionEnd" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/actionPrev"
|
||||
style="@style/MediaPlayer.SessionAction"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_progress_bar"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
|
||||
|
||||
<!-- Seek Bar -->
|
||||
<!-- As per Material Design on Bidirectionality, 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: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_marginTop="0dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
app:layout_constraintStart_toEndOf="@id/actionPrev"
|
||||
app:layout_constraintEnd_toStartOf="@id/actionNext"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/actionNext"
|
||||
style="@style/MediaPlayer.SessionAction"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="@dimen/qs_media_action_spacing"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
app:layout_constraintStart_toEndOf="@id/media_progress_bar"
|
||||
app:layout_constraintEnd_toStartOf="@id/actionStart"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/actionStart"
|
||||
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"
|
||||
app:layout_constraintStart_toEndOf="@id/actionNext"
|
||||
app:layout_constraintEnd_toStartOf="@id/actionEnd"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/actionEnd"
|
||||
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"
|
||||
app:layout_constraintStart_toEndOf="@id/actionStart"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/actionPlayPause" />
|
||||
|
||||
<!-- 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="@dimen/min_clickable_item_size"
|
||||
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
|
||||
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="@dimen/min_clickable_item_size"
|
||||
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
|
||||
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: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="@dimen/min_clickable_item_size"
|
||||
app:layout_constraintHeight_min="@dimen/min_clickable_item_size"
|
||||
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: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>
|
||||
@@ -969,6 +969,10 @@
|
||||
<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>
|
||||
|
||||
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
|
||||
<dimen name="qs_aa_media_rec_album_size_collapsed">72dp</dimen>
|
||||
<dimen name="qs_aa_media_rec_album_size_expanded">76dp</dimen>
|
||||
|
||||
@@ -581,6 +581,23 @@
|
||||
<item name="android:scaleType">centerInside</item>
|
||||
</style>
|
||||
|
||||
<style name="MediaPlayer.SessionAction"
|
||||
parent="@android:style/Widget.Material.Button.Borderless.Small">
|
||||
<item name="android:background">@drawable/qs_media_light_source</item>
|
||||
<item name="android:tint">?android:attr/textColorPrimary</item>
|
||||
<item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
|
||||
<item name="android:paddingTop">12dp</item>
|
||||
<item name="android:paddingStart">12dp</item>
|
||||
<item name="android:paddingEnd">12dp</item>
|
||||
<item name="android:paddingBottom">12dp</item>
|
||||
<item name="android:scaleType">centerInside</item>
|
||||
</style>
|
||||
|
||||
<style name="MediaPlayer.SessionAction.Primary" parent="MediaPlayer.SessionAction">
|
||||
<item name="android:background">@drawable/qs_media_round_button_background</item>
|
||||
<item name="android:backgroundTint">@color/media_player_solid_button_bg</item>
|
||||
</style>
|
||||
|
||||
<style name="MediaPlayer.OutlineButton">
|
||||
<item name="android:background">@drawable/qs_media_button_background</item>
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.android.systemui.classifier.FalsingCollector
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.dump.DumpManager
|
||||
import com.android.systemui.media.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.qs.PageIndicator
|
||||
@@ -56,7 +57,8 @@ class MediaCarouselController @Inject constructor(
|
||||
configurationController: ConfigurationController,
|
||||
falsingCollector: FalsingCollector,
|
||||
falsingManager: FalsingManager,
|
||||
dumpManager: DumpManager
|
||||
dumpManager: DumpManager,
|
||||
private val mediaFlags: MediaFlags
|
||||
) : Dumpable {
|
||||
/**
|
||||
* The current width of the carousel
|
||||
@@ -382,7 +384,7 @@ class MediaCarouselController @Inject constructor(
|
||||
private fun reorderAllPlayers(previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?) {
|
||||
mediaContent.removeAllViews()
|
||||
for (mediaPlayer in MediaPlayerData.players()) {
|
||||
mediaPlayer.playerViewHolder?.let {
|
||||
mediaPlayer.mediaViewHolder?.let {
|
||||
mediaContent.addView(it.player)
|
||||
} ?: mediaPlayer.recommendationViewHolder?.let {
|
||||
mediaContent.addView(it.recommendations)
|
||||
@@ -416,12 +418,19 @@ class MediaCarouselController @Inject constructor(
|
||||
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
|
||||
if (existingPlayer == null) {
|
||||
var newPlayer = mediaControlPanelFactory.get()
|
||||
newPlayer.attachPlayer(
|
||||
PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
|
||||
if (mediaFlags.areMediaSessionActionsEnabled()) {
|
||||
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.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
|
||||
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
newPlayer.playerViewHolder?.player?.setLayoutParams(lp)
|
||||
newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
|
||||
newPlayer.bindPlayer(dataCopy, key)
|
||||
newPlayer.setListening(currentlyExpanded)
|
||||
MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer, systemClock)
|
||||
@@ -493,7 +502,7 @@ class MediaCarouselController @Inject constructor(
|
||||
val removed = MediaPlayerData.removeMediaPlayer(key)
|
||||
removed?.apply {
|
||||
mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
|
||||
mediaContent.removeView(removed.playerViewHolder?.player)
|
||||
mediaContent.removeView(removed.mediaViewHolder?.player)
|
||||
mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
|
||||
removed.onDestroy()
|
||||
mediaCarouselScrollHandler.onPlayersChanged()
|
||||
@@ -836,7 +845,7 @@ class MediaCarouselController @Inject constructor(
|
||||
MediaPlayerData.players().forEachIndexed {
|
||||
index, it ->
|
||||
if (it.mIsImpressed) {
|
||||
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
|
||||
it.mInstanceId,
|
||||
it.mUid,
|
||||
it.recommendationViewHolder != null,
|
||||
|
||||
@@ -528,7 +528,7 @@ class MediaCarouselScrollHandler(
|
||||
* where it was and update our scroll position.
|
||||
*/
|
||||
fun onPrePlayerRemoved(removed: MediaControlPanel) {
|
||||
val removedIndex = mediaContent.indexOfChild(removed.playerViewHolder?.player)
|
||||
val removedIndex = mediaContent.indexOfChild(removed.mediaViewHolder?.player)
|
||||
// If the removed index is less than the visibleMediaIndex, then we need to decrement it.
|
||||
// RTL has no effect on this, because indices are always relative (start-to-end).
|
||||
// Update the index 'manually' since we won't always get a call to onMediaScrollingChanged
|
||||
|
||||
@@ -57,12 +57,10 @@ import com.android.systemui.media.dialog.MediaOutputDialogFactory;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.shared.system.SysUiStatsLog;
|
||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||
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;
|
||||
|
||||
@@ -87,6 +85,10 @@ public class MediaControlPanel {
|
||||
private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name";
|
||||
private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
|
||||
|
||||
// Event types logged by smartspace
|
||||
private static final int SMARTSPACE_CARD_CLICK_EVENT = 760;
|
||||
protected static final int SMARTSPACE_CARD_DISMISS_EVENT = 761;
|
||||
|
||||
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
|
||||
|
||||
// Button IDs for QS controls
|
||||
@@ -104,13 +106,12 @@ public class MediaControlPanel {
|
||||
private final ActivityStarter mActivityStarter;
|
||||
|
||||
private Context mContext;
|
||||
private PlayerViewHolder mPlayerViewHolder;
|
||||
private MediaViewHolder mMediaViewHolder;
|
||||
private RecommendationViewHolder mRecommendationViewHolder;
|
||||
private String mKey;
|
||||
private MediaViewController mMediaViewController;
|
||||
private MediaSession.Token mToken;
|
||||
private MediaController mController;
|
||||
private KeyguardDismissUtil mKeyguardDismissUtil;
|
||||
private Lazy<MediaDataManager> mMediaDataManagerLazy;
|
||||
private int mBackgroundColor;
|
||||
private int mDevicePadding;
|
||||
@@ -139,8 +140,8 @@ public class MediaControlPanel {
|
||||
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
|
||||
ActivityStarter activityStarter, MediaViewController mediaViewController,
|
||||
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
|
||||
KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
|
||||
mediaOutputDialogFactory, MediaCarouselController mediaCarouselController,
|
||||
MediaOutputDialogFactory mediaOutputDialogFactory,
|
||||
MediaCarouselController mediaCarouselController,
|
||||
FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) {
|
||||
mContext = context;
|
||||
mBackgroundExecutor = backgroundExecutor;
|
||||
@@ -148,7 +149,6 @@ public class MediaControlPanel {
|
||||
mSeekBarViewModel = seekBarViewModel;
|
||||
mMediaViewController = mediaViewController;
|
||||
mMediaDataManagerLazy = lazyMediaDataManager;
|
||||
mKeyguardDismissUtil = keyguardDismissUtil;
|
||||
mMediaOutputDialogFactory = mediaOutputDialogFactory;
|
||||
mMediaCarouselController = mediaCarouselController;
|
||||
mFalsingManager = falsingManager;
|
||||
@@ -157,8 +157,7 @@ public class MediaControlPanel {
|
||||
loadDimens();
|
||||
|
||||
mSeekBarViewModel.setLogSmartspaceClick(() -> {
|
||||
logSmartspaceCardReported(
|
||||
760, // SMARTSPACE_CARD_CLICK
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
|
||||
/* isRecommendationCard */ false);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
@@ -179,13 +178,13 @@ public class MediaControlPanel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player view holder used to display media controls.
|
||||
* Get the view holder used to display media controls.
|
||||
*
|
||||
* @return the player view holder
|
||||
* @return the media view holder
|
||||
*/
|
||||
@Nullable
|
||||
public PlayerViewHolder getPlayerViewHolder() {
|
||||
return mPlayerViewHolder;
|
||||
public MediaViewHolder getMediaViewHolder() {
|
||||
return mMediaViewHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,16 +228,17 @@ public class MediaControlPanel {
|
||||
}
|
||||
|
||||
/** Attaches the player to the player view holder. */
|
||||
public void attachPlayer(PlayerViewHolder vh) {
|
||||
mPlayerViewHolder = vh;
|
||||
public void attachPlayer(MediaViewHolder vh, MediaViewController.TYPE playerType) {
|
||||
mMediaViewHolder = vh;
|
||||
TransitionLayout player = vh.getPlayer();
|
||||
|
||||
mSeekBarObserver = new SeekBarObserver(vh);
|
||||
boolean useSessionLayout = playerType == MediaViewController.TYPE.PLAYER_SESSION;
|
||||
mSeekBarObserver = new SeekBarObserver(vh, useSessionLayout);
|
||||
mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
|
||||
mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
|
||||
mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER);
|
||||
mMediaViewController.attach(player, playerType);
|
||||
|
||||
mPlayerViewHolder.getPlayer().setOnLongClickListener(v -> {
|
||||
vh.getPlayer().setOnLongClickListener(v -> {
|
||||
if (!mMediaViewController.isGutsVisible()) {
|
||||
openGuts();
|
||||
return true;
|
||||
@@ -247,12 +247,12 @@ public class MediaControlPanel {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mPlayerViewHolder.getCancel().setOnClickListener(v -> {
|
||||
vh.getCancel().setOnClickListener(v -> {
|
||||
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||
closeGuts();
|
||||
}
|
||||
});
|
||||
mPlayerViewHolder.getSettings().setOnClickListener(v -> {
|
||||
vh.getSettings().setOnClickListener(v -> {
|
||||
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||
mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
|
||||
}
|
||||
@@ -289,9 +289,19 @@ public class MediaControlPanel {
|
||||
|
||||
/** Bind this player view based on the data given. */
|
||||
public void bindPlayer(@NonNull MediaData data, String key) {
|
||||
if (mPlayerViewHolder == null) {
|
||||
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();
|
||||
@@ -316,99 +326,63 @@ public class MediaControlPanel {
|
||||
mController = null;
|
||||
}
|
||||
|
||||
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
|
||||
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
|
||||
|
||||
// Click action
|
||||
PendingIntent clickIntent = data.getClickIntent();
|
||||
if (clickIntent != null) {
|
||||
mPlayerViewHolder.getPlayer().setOnClickListener(v -> {
|
||||
mMediaViewHolder.getPlayer().setOnClickListener(v -> {
|
||||
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
|
||||
if (mMediaViewController.isGutsVisible()) return;
|
||||
|
||||
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
|
||||
/* isRecommendationCard */ false);
|
||||
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
|
||||
buildLaunchAnimatorController(mPlayerViewHolder.getPlayer()));
|
||||
buildLaunchAnimatorController(mMediaViewHolder.getPlayer()));
|
||||
});
|
||||
}
|
||||
|
||||
// Accessibility label
|
||||
mPlayerViewHolder.getPlayer().setContentDescription(
|
||||
mMediaViewHolder.getPlayer().setContentDescription(
|
||||
mContext.getString(
|
||||
R.string.controls_media_playing_item_description,
|
||||
data.getSong(), data.getArtist(), data.getApp()));
|
||||
|
||||
ImageView albumView = mPlayerViewHolder.getAlbumView();
|
||||
boolean hasArtwork = data.getArtwork() != null;
|
||||
if (hasArtwork) {
|
||||
Drawable artwork = scaleDrawable(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
|
||||
ImageView appIconView = mPlayerViewHolder.getAppIcon();
|
||||
appIconView.clearColorFilter();
|
||||
if (data.getAppIcon() != null && !data.getResumption()) {
|
||||
appIconView.setImageIcon(data.getAppIcon());
|
||||
int color = mContext.getColor(android.R.color.system_accent2_900);
|
||||
appIconView.setColorFilter(color);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Song name
|
||||
TextView titleText = mPlayerViewHolder.getTitleText();
|
||||
TextView titleText = mMediaViewHolder.getTitleText();
|
||||
titleText.setText(data.getSong());
|
||||
|
||||
// Artist name
|
||||
TextView artistText = mPlayerViewHolder.getArtistText();
|
||||
TextView artistText = mMediaViewHolder.getArtistText();
|
||||
artistText.setText(data.getArtist());
|
||||
|
||||
// Transfer chip
|
||||
ViewGroup seamlessView = mPlayerViewHolder.getSeamless();
|
||||
// Seek Bar
|
||||
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);
|
||||
|
||||
// Output switcher chip
|
||||
ViewGroup seamlessView = mMediaViewHolder.getSeamless();
|
||||
seamlessView.setVisibility(View.VISIBLE);
|
||||
setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
|
||||
setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
|
||||
seamlessView.setOnClickListener(
|
||||
v -> {
|
||||
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||
mMediaOutputDialogFactory.create(data.getPackageName(), true,
|
||||
mPlayerViewHolder.getSeamlessButton());
|
||||
mMediaViewHolder.getSeamlessButton());
|
||||
}
|
||||
});
|
||||
|
||||
ImageView iconView = mPlayerViewHolder.getSeamlessIcon();
|
||||
TextView deviceName = mPlayerViewHolder.getSeamlessText();
|
||||
|
||||
ImageView iconView = mMediaViewHolder.getSeamlessIcon();
|
||||
TextView deviceName = mMediaViewHolder.getSeamlessText();
|
||||
final MediaDeviceData device = data.getDevice();
|
||||
final int seamlessId = mPlayerViewHolder.getSeamless().getId();
|
||||
// Disable clicking on output switcher for invalid devices and resumption controls
|
||||
final boolean seamlessDisabled = (device != null && !device.getEnabled())
|
||||
|| data.getResumption();
|
||||
final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
|
||||
expandedSet.setAlpha(seamlessId, seamlessAlpha);
|
||||
collapsedSet.setAlpha(seamlessId, seamlessAlpha);
|
||||
mPlayerViewHolder.getSeamless().setEnabled(!seamlessDisabled);
|
||||
mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha);
|
||||
seamlessView.setEnabled(!seamlessDisabled);
|
||||
String deviceString = null;
|
||||
if (device != null && device.getEnabled()) {
|
||||
Drawable icon = device.getIcon();
|
||||
@@ -429,32 +403,88 @@ public class MediaControlPanel {
|
||||
deviceName.setText(deviceString);
|
||||
seamlessView.setContentDescription(deviceString);
|
||||
|
||||
// Dismiss
|
||||
mMediaViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
|
||||
mMediaViewHolder.getDismiss().setEnabled(isDismissible);
|
||||
mMediaViewHolder.getDismiss().setOnClickListener(v -> {
|
||||
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
|
||||
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
|
||||
/* isRecommendationCard */ false);
|
||||
|
||||
if (mKey != null) {
|
||||
closeGuts();
|
||||
if (!mMediaDataManagerLazy.get().dismissMediaData(mKey,
|
||||
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);
|
||||
}
|
||||
} 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
|
||||
PlayerViewHolder playerHolder = (PlayerViewHolder) mMediaViewHolder;
|
||||
ImageView albumView = playerHolder.getAlbumView();
|
||||
boolean hasArtwork = data.getArtwork() != null;
|
||||
if (hasArtwork) {
|
||||
Drawable artwork = scaleDrawable(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(android.R.color.system_accent2_900);
|
||||
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 (mMediaFlags.areMediaSessionActionsEnabled() && data.getSemanticActions() != null) {
|
||||
// Use PlaybackState actions instead
|
||||
MediaButton semanticActions = data.getSemanticActions();
|
||||
|
||||
actionIcons = new ArrayList<MediaAction>();
|
||||
actionIcons.add(semanticActions.getStartCustom());
|
||||
actionIcons.add(semanticActions.getPrevOrCustom());
|
||||
actionIcons.add(semanticActions.getPlayOrPause());
|
||||
actionIcons.add(semanticActions.getNextOrCustom());
|
||||
actionIcons.add(semanticActions.getEndCustom());
|
||||
|
||||
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 = mPlayerViewHolder.getAction(actionId);
|
||||
final ImageButton button = mMediaViewHolder.getAction(actionId);
|
||||
MediaAction mediaAction = actionIcons.get(i);
|
||||
if (mediaAction != null) {
|
||||
button.setImageIcon(mediaAction.getIcon());
|
||||
@@ -467,7 +497,7 @@ public class MediaControlPanel {
|
||||
button.setEnabled(true);
|
||||
button.setOnClickListener(v -> {
|
||||
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
|
||||
/* isRecommendationCard */ false);
|
||||
action.run();
|
||||
}
|
||||
@@ -495,43 +525,71 @@ public class MediaControlPanel {
|
||||
if (actionIcons.size() == 0) {
|
||||
expandedSet.setVisibility(ACTION_IDS[0], ConstraintSet.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
// Seek Bar
|
||||
final MediaController controller = getController();
|
||||
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
|
||||
|
||||
// Guts label
|
||||
boolean isDismissible = data.isClearable();
|
||||
mPlayerViewHolder.getLongPressText().setText(isDismissible
|
||||
? R.string.controls_media_close_session
|
||||
: R.string.controls_media_active_session);
|
||||
|
||||
// Dismiss
|
||||
mPlayerViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
|
||||
mPlayerViewHolder.getDismiss().setEnabled(isDismissible);
|
||||
mPlayerViewHolder.getDismiss().setOnClickListener(v -> {
|
||||
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
|
||||
|
||||
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
|
||||
/* isRecommendationCard */ false);
|
||||
|
||||
if (mKey != null) {
|
||||
closeGuts();
|
||||
if (!mMediaDataManagerLazy.get().dismissMediaData(mKey,
|
||||
MediaViewController.GUTS_ANIMATION_DURATION + 100)) {
|
||||
Log.w(TAG, "Manager failed to dismiss media " + mKey);
|
||||
// Remove directly from carousel to let user recover - TODO(b/190799184)
|
||||
mMediaCarouselController.removePlayer(key, false, false);
|
||||
}
|
||||
/** Bind elements specific to PlayerSessionViewHolder */
|
||||
private void bindSessionPlayer(@NonNull MediaData data, String key) {
|
||||
// App icon - use launcher icon
|
||||
ImageView appIconView = mMediaViewHolder.getAppIcon();
|
||||
appIconView.clearColorFilter();
|
||||
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);
|
||||
// Fall back to notification icon
|
||||
if (data.getAppIcon() != null) {
|
||||
appIconView.setImageIcon(data.getAppIcon());
|
||||
} else {
|
||||
Log.w(TAG, "Dismiss media with null notification. Token uid="
|
||||
+ data.getToken().getUid());
|
||||
appIconView.setImageResource(R.drawable.ic_music_note);
|
||||
}
|
||||
});
|
||||
int color = mContext.getColor(android.R.color.system_accent2_900);
|
||||
appIconView.setColorFilter(color);
|
||||
}
|
||||
|
||||
// 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();
|
||||
// Media action buttons
|
||||
MediaButton semanticActions = data.getSemanticActions();
|
||||
if (semanticActions != null) {
|
||||
PlayerSessionViewHolder sessionHolder = (PlayerSessionViewHolder) mMediaViewHolder;
|
||||
setSemanticButton(sessionHolder.getActionPlayPause(),
|
||||
semanticActions.getPlayOrPause());
|
||||
setSemanticButton(sessionHolder.getActionNext(),
|
||||
semanticActions.getNextOrCustom());
|
||||
setSemanticButton(sessionHolder.getActionPrev(),
|
||||
semanticActions.getPrevOrCustom());
|
||||
setSemanticButton(sessionHolder.getActionStart(),
|
||||
semanticActions.getStartCustom());
|
||||
setSemanticButton(sessionHolder.getActionEnd(),
|
||||
semanticActions.getEndCustom());
|
||||
} else {
|
||||
Log.w(TAG, "Using semantic player, but did not get buttons");
|
||||
}
|
||||
}
|
||||
|
||||
private void setSemanticButton(final ImageButton button, MediaAction mediaAction) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
button.setImageIcon(null);
|
||||
button.setContentDescription(null);
|
||||
button.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -696,7 +754,7 @@ public class MediaControlPanel {
|
||||
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
|
||||
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
|
||||
|
||||
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
|
||||
/* isRecommendationCard */ true);
|
||||
closeGuts();
|
||||
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
|
||||
@@ -729,8 +787,8 @@ public class MediaControlPanel {
|
||||
* @param immediate {@code true} if it should be closed without animation
|
||||
*/
|
||||
public void closeGuts(boolean immediate) {
|
||||
if (mPlayerViewHolder != null) {
|
||||
mPlayerViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
|
||||
if (mMediaViewHolder != null) {
|
||||
mMediaViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
|
||||
} else if (mRecommendationViewHolder != null) {
|
||||
mRecommendationViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
|
||||
}
|
||||
@@ -747,9 +805,9 @@ public class MediaControlPanel {
|
||||
|
||||
boolean wasTruncated = false;
|
||||
Layout l = null;
|
||||
if (mPlayerViewHolder != null) {
|
||||
mPlayerViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
|
||||
l = mPlayerViewHolder.getSettingsText().getLayout();
|
||||
if (mMediaViewHolder != null) {
|
||||
mMediaViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
|
||||
l = mMediaViewHolder.getSettingsText().getLayout();
|
||||
} else if (mRecommendationViewHolder != null) {
|
||||
mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
|
||||
l = mRecommendationViewHolder.getSettingsText().getLayout();
|
||||
@@ -853,7 +911,7 @@ public class MediaControlPanel {
|
||||
view.setOnClickListener(v -> {
|
||||
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
|
||||
|
||||
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
|
||||
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
|
||||
/* isRecommendationCard */ true,
|
||||
interactedSubcardRank,
|
||||
getSmartspaceSubCardCardinality());
|
||||
|
||||
@@ -37,9 +37,12 @@ class MediaViewController @Inject constructor(
|
||||
private val mediaHostStatesManager: MediaHostStatesManager
|
||||
) {
|
||||
|
||||
/** Indicating the media view controller is for a player or recommendation. */
|
||||
/**
|
||||
* Indicating that the media view controller is for a notification-based player,
|
||||
* session-based player, or recommendation
|
||||
*/
|
||||
enum class TYPE {
|
||||
PLAYER, RECOMMENDATION
|
||||
PLAYER, PLAYER_SESSION, RECOMMENDATION
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -257,31 +260,29 @@ class MediaViewController @Inject constructor(
|
||||
* [TransitionViewState].
|
||||
*/
|
||||
private fun setGutsViewState(viewState: TransitionViewState) {
|
||||
if (type == TYPE.PLAYER) {
|
||||
PlayerViewHolder.controlsIds.forEach { id ->
|
||||
viewState.widgetStates.get(id)?.let { state ->
|
||||
// Make sure to use the unmodified state if guts are not visible.
|
||||
state.alpha = if (isGutsVisible) 0f else state.alpha
|
||||
state.gone = if (isGutsVisible) true else state.gone
|
||||
}
|
||||
}
|
||||
PlayerViewHolder.gutsIds.forEach { id ->
|
||||
viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f
|
||||
viewState.widgetStates.get(id)?.gone = !isGutsVisible
|
||||
}
|
||||
} else {
|
||||
RecommendationViewHolder.controlsIds.forEach { id ->
|
||||
viewState.widgetStates.get(id)?.let { state ->
|
||||
// Make sure to use the unmodified state if guts are not visible.
|
||||
state.alpha = if (isGutsVisible) 0f else state.alpha
|
||||
state.gone = if (isGutsVisible) true else state.gone
|
||||
}
|
||||
}
|
||||
RecommendationViewHolder.gutsIds.forEach { id ->
|
||||
viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f
|
||||
viewState.widgetStates.get(id)?.gone = !isGutsVisible
|
||||
val controlsIds = when (type) {
|
||||
TYPE.PLAYER -> PlayerViewHolder.controlsIds
|
||||
TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.controlsIds
|
||||
TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
|
||||
}
|
||||
val gutsIds = when (type) {
|
||||
TYPE.PLAYER -> PlayerViewHolder.gutsIds
|
||||
TYPE.PLAYER_SESSION -> PlayerSessionViewHolder.gutsIds
|
||||
TYPE.RECOMMENDATION -> RecommendationViewHolder.gutsIds
|
||||
}
|
||||
|
||||
controlsIds.forEach { id ->
|
||||
viewState.widgetStates.get(id)?.let { state ->
|
||||
// Make sure to use the unmodified state if guts are not visible.
|
||||
state.alpha = if (isGutsVisible) 0f else state.alpha
|
||||
state.gone = if (isGutsVisible) true else state.gone
|
||||
}
|
||||
}
|
||||
gutsIds.forEach { id ->
|
||||
viewState.widgetStates.get(id)?.alpha = if (isGutsVisible) 1f else 0f
|
||||
viewState.widgetStates.get(id)?.gone = !isGutsVisible
|
||||
}
|
||||
|
||||
if (shouldHideGutsSettings) {
|
||||
viewState.widgetStates.get(R.id.settings)?.gone = true
|
||||
}
|
||||
@@ -470,12 +471,19 @@ class MediaViewController @Inject constructor(
|
||||
|
||||
private fun updateMediaViewControllerType(type: TYPE) {
|
||||
this.type = type
|
||||
if (type == TYPE.PLAYER) {
|
||||
collapsedLayout.load(context, R.xml.media_collapsed)
|
||||
expandedLayout.load(context, R.xml.media_expanded)
|
||||
} else {
|
||||
collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
|
||||
expandedLayout.load(context, R.xml.media_recommendation_expanded)
|
||||
when (type) {
|
||||
TYPE.PLAYER -> {
|
||||
collapsedLayout.load(context, R.xml.media_collapsed)
|
||||
expandedLayout.load(context, R.xml.media_expanded)
|
||||
}
|
||||
TYPE.PLAYER_SESSION -> {
|
||||
collapsedLayout.clone(context, R.layout.media_session_view)
|
||||
expandedLayout.clone(context, R.layout.media_session_view)
|
||||
}
|
||||
TYPE.RECOMMENDATION -> {
|
||||
collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
|
||||
expandedLayout.load(context, R.xml.media_recommendation_expanded)
|
||||
}
|
||||
}
|
||||
refreshState()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
|
||||
private const val TAG = "MediaViewHolder"
|
||||
|
||||
/**
|
||||
* Parent class for different media player views
|
||||
*/
|
||||
abstract class MediaViewHolder constructor(itemView: View) {
|
||||
val player = itemView as TransitionLayout
|
||||
|
||||
// Player information
|
||||
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
|
||||
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
|
||||
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
|
||||
|
||||
// Output switcher
|
||||
val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
|
||||
val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
|
||||
val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
|
||||
val seamlessButton = itemView.requireViewById<View>(R.id.media_seamless_button)
|
||||
|
||||
// Seekbar views
|
||||
val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
|
||||
open val elapsedTimeView: TextView? = null
|
||||
open val totalTimeView: TextView? = null
|
||||
|
||||
// Settings screen
|
||||
val longPressText = itemView.requireViewById<TextView>(R.id.remove_text)
|
||||
val cancel = itemView.requireViewById<View>(R.id.cancel)
|
||||
val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
|
||||
val dismissLabel = dismiss.getChildAt(0)
|
||||
val settings = itemView.requireViewById<View>(R.id.settings)
|
||||
val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
|
||||
|
||||
init {
|
||||
(player.background as IlluminationDrawable).let {
|
||||
it.registerLightSource(seamless)
|
||||
it.registerLightSource(cancel)
|
||||
it.registerLightSource(dismiss)
|
||||
it.registerLightSource(settings)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun getAction(id: Int): ImageButton
|
||||
|
||||
fun marquee(start: Boolean, delay: Long) {
|
||||
val longPressTextHandler = longPressText.getHandler()
|
||||
if (longPressTextHandler == null) {
|
||||
Log.d(TAG, "marquee while longPressText.getHandler() is null", Exception())
|
||||
return
|
||||
}
|
||||
longPressTextHandler.postDelayed({ longPressText.setSelected(start) }, delay)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAction(id: Int): ImageButton {
|
||||
return when (id) {
|
||||
R.id.actionPlayPause -> actionPlayPause
|
||||
R.id.actionNext -> actionNext
|
||||
R.id.actionPrev -> actionPrev
|
||||
R.id.actionStart -> actionStart
|
||||
R.id.actionEnd -> actionEnd
|
||||
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.actionStart,
|
||||
R.id.actionEnd,
|
||||
R.id.icon
|
||||
)
|
||||
val gutsIds = setOf(
|
||||
R.id.remove_text,
|
||||
R.id.cancel,
|
||||
R.id.dismiss,
|
||||
R.id.settings
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,43 +16,26 @@
|
||||
|
||||
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
|
||||
import android.widget.ImageView
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
|
||||
private const val TAG = "PlayerViewHolder"
|
||||
|
||||
/**
|
||||
* ViewHolder for a media player.
|
||||
*/
|
||||
class PlayerViewHolder private constructor(itemView: View) {
|
||||
|
||||
val player = itemView as TransitionLayout
|
||||
class PlayerViewHolder private constructor(itemView: View) : MediaViewHolder(itemView) {
|
||||
|
||||
// Player information
|
||||
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
|
||||
val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
|
||||
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
|
||||
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
|
||||
|
||||
// Output switcher
|
||||
val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
|
||||
val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
|
||||
val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
|
||||
val seamlessButton = itemView.requireViewById<View>(R.id.media_seamless_button)
|
||||
|
||||
// Seek bar
|
||||
val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
|
||||
val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time)
|
||||
val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time)
|
||||
val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time)
|
||||
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)
|
||||
@@ -61,29 +44,17 @@ class PlayerViewHolder private constructor(itemView: View) {
|
||||
val action3 = itemView.requireViewById<ImageButton>(R.id.action3)
|
||||
val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
|
||||
|
||||
// Settings screen
|
||||
val longPressText = itemView.requireViewById<TextView>(R.id.remove_text)
|
||||
val cancel = itemView.requireViewById<View>(R.id.cancel)
|
||||
val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
|
||||
val dismissLabel = dismiss.getChildAt(0)
|
||||
val settings = itemView.requireViewById<View>(R.id.settings)
|
||||
val settingsText = itemView.requireViewById<TextView>(R.id.settings_text)
|
||||
|
||||
init {
|
||||
(player.background as IlluminationDrawable).let {
|
||||
it.registerLightSource(seamless)
|
||||
it.registerLightSource(action0)
|
||||
it.registerLightSource(action1)
|
||||
it.registerLightSource(action2)
|
||||
it.registerLightSource(action3)
|
||||
it.registerLightSource(action4)
|
||||
it.registerLightSource(cancel)
|
||||
it.registerLightSource(dismiss)
|
||||
it.registerLightSource(settings)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAction(id: Int): ImageButton {
|
||||
override fun getAction(id: Int): ImageButton {
|
||||
return when (id) {
|
||||
R.id.action0 -> action0
|
||||
R.id.action1 -> action1
|
||||
@@ -96,15 +67,6 @@ class PlayerViewHolder private constructor(itemView: View) {
|
||||
}
|
||||
}
|
||||
|
||||
fun marquee(start: Boolean, delay: Long) {
|
||||
val longPressTextHandler = longPressText.getHandler()
|
||||
if (longPressTextHandler == null) {
|
||||
Log.d(TAG, "marquee while longPressText.getHandler() is null", Exception())
|
||||
return
|
||||
}
|
||||
longPressTextHandler.postDelayed({ longPressText.setSelected(start) }, delay)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Creates a PlayerViewHolder.
|
||||
|
||||
@@ -26,16 +26,29 @@ import com.android.systemui.R
|
||||
*
|
||||
* <p>Updates the seek bar views in response to changes to the model.
|
||||
*/
|
||||
class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarViewModel.Progress> {
|
||||
class SeekBarObserver(
|
||||
private val holder: MediaViewHolder,
|
||||
private val useSessionLayout: Boolean
|
||||
) : 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 = holder.seekBar.context.resources
|
||||
.getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_vertical_padding)
|
||||
val seekBarDisabledVerticalPadding = holder.seekBar.context.resources
|
||||
.getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding)
|
||||
val seekBarEnabledVerticalPadding = if (useSessionLayout) {
|
||||
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
|
||||
.getDimensionPixelSize(R.dimen.qs_media_session_disabled_seekbar_vertical_padding)
|
||||
} else {
|
||||
holder.seekBar.context.resources
|
||||
.getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding)
|
||||
}
|
||||
|
||||
/** Updates seek bar views when the data model changes. */
|
||||
@UiThread
|
||||
@@ -48,8 +61,8 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi
|
||||
holder.seekBar.setEnabled(false)
|
||||
holder.seekBar.getThumb().setAlpha(0)
|
||||
holder.seekBar.setProgress(0)
|
||||
holder.elapsedTimeView.setText("")
|
||||
holder.totalTimeView.setText("")
|
||||
holder.elapsedTimeView?.setText("")
|
||||
holder.totalTimeView?.setText("")
|
||||
holder.seekBar.contentDescription = ""
|
||||
return
|
||||
}
|
||||
@@ -65,13 +78,13 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi
|
||||
holder.seekBar.setMax(data.duration)
|
||||
val totalTimeString = DateUtils.formatElapsedTime(
|
||||
data.duration / DateUtils.SECOND_IN_MILLIS)
|
||||
holder.totalTimeView.setText(totalTimeString)
|
||||
holder.totalTimeView?.setText(totalTimeString)
|
||||
|
||||
data.elapsedTime?.let {
|
||||
holder.seekBar.setProgress(it)
|
||||
val elapsedTimeString = DateUtils.formatElapsedTime(
|
||||
it / DateUtils.SECOND_IN_MILLIS)
|
||||
holder.elapsedTimeView.setText(elapsedTimeString)
|
||||
holder.elapsedTimeView?.setText(elapsedTimeString)
|
||||
|
||||
holder.seekBar.contentDescription = holder.seekBar.context.getString(
|
||||
R.string.controls_media_seekbar_description,
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import javax.inject.Provider
|
||||
|
||||
private val DATA = MediaData(
|
||||
@@ -74,6 +75,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
@Mock lateinit var falsingCollector: FalsingCollector
|
||||
@Mock lateinit var falsingManager: FalsingManager
|
||||
@Mock lateinit var dumpManager: DumpManager
|
||||
@Mock lateinit var mediaFlags: MediaFlags
|
||||
|
||||
private val clock = FakeSystemClock()
|
||||
private lateinit var mediaCarouselController: MediaCarouselController
|
||||
@@ -81,7 +83,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
|
||||
mediaCarouselController = MediaCarouselController(
|
||||
context,
|
||||
mediaControlPanelFactory,
|
||||
@@ -94,7 +96,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
configurationController,
|
||||
falsingCollector,
|
||||
falsingManager,
|
||||
dumpManager
|
||||
dumpManager,
|
||||
mediaFlags
|
||||
)
|
||||
|
||||
MediaPlayerData.clear()
|
||||
|
||||
@@ -43,7 +43,6 @@ import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.eq
|
||||
@@ -87,11 +86,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Mock private lateinit var activityStarter: ActivityStarter
|
||||
|
||||
@Mock private lateinit var holder: PlayerViewHolder
|
||||
@Mock private lateinit var sessionHolder: PlayerSessionViewHolder
|
||||
@Mock private lateinit var view: TransitionLayout
|
||||
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
|
||||
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
|
||||
@Mock private lateinit var mediaViewController: MediaViewController
|
||||
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
|
||||
@Mock private lateinit var mediaDataManager: MediaDataManager
|
||||
@Mock private lateinit var expandedSet: ConstraintSet
|
||||
@Mock private lateinit var collapsedSet: ConstraintSet
|
||||
@@ -104,6 +103,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
private lateinit var titleText: TextView
|
||||
private lateinit var artistText: TextView
|
||||
private lateinit var seamless: ViewGroup
|
||||
private lateinit var seamlessButton: View
|
||||
@Mock private lateinit var seamlessBackground: RippleDrawable
|
||||
private lateinit var seamlessIcon: ImageView
|
||||
private lateinit var seamlessText: TextView
|
||||
private lateinit var seekBar: SeekBar
|
||||
@@ -114,6 +115,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
private lateinit var action2: ImageButton
|
||||
private lateinit var action3: ImageButton
|
||||
private lateinit var action4: ImageButton
|
||||
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
|
||||
@@ -137,62 +143,30 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
|
||||
|
||||
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
|
||||
seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
|
||||
seekBarViewModel, Lazy { mediaDataManager },
|
||||
mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock)
|
||||
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
|
||||
|
||||
// Mock out a view holder for the player to attach to.
|
||||
whenever(holder.player).thenReturn(view)
|
||||
// Set up mock views for the players
|
||||
appIcon = ImageView(context)
|
||||
whenever(holder.appIcon).thenReturn(appIcon)
|
||||
albumView = ImageView(context)
|
||||
whenever(holder.albumView).thenReturn(albumView)
|
||||
titleText = TextView(context)
|
||||
whenever(holder.titleText).thenReturn(titleText)
|
||||
artistText = TextView(context)
|
||||
whenever(holder.artistText).thenReturn(artistText)
|
||||
seamless = FrameLayout(context)
|
||||
val seamlessBackground = mock(RippleDrawable::class.java)
|
||||
seamless.foreground = seamlessBackground
|
||||
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
|
||||
whenever(holder.seamless).thenReturn(seamless)
|
||||
seamlessButton = View(context)
|
||||
seamlessIcon = ImageView(context)
|
||||
whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
|
||||
seamlessText = TextView(context)
|
||||
whenever(holder.seamlessText).thenReturn(seamlessText)
|
||||
seekBar = SeekBar(context)
|
||||
whenever(holder.seekBar).thenReturn(seekBar)
|
||||
elapsedTimeView = TextView(context)
|
||||
whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
|
||||
totalTimeView = TextView(context)
|
||||
whenever(holder.totalTimeView).thenReturn(totalTimeView)
|
||||
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(holder.longPressText).thenReturn(longPressText)
|
||||
whenever(longPressText.handler).thenReturn(handler)
|
||||
settings = View(context)
|
||||
whenever(holder.settings).thenReturn(settings)
|
||||
settingsText = TextView(context)
|
||||
whenever(holder.settingsText).thenReturn(settingsText)
|
||||
cancel = View(context)
|
||||
whenever(holder.cancel).thenReturn(cancel)
|
||||
dismiss = FrameLayout(context)
|
||||
whenever(holder.dismiss).thenReturn(dismiss)
|
||||
dismissLabel = View(context)
|
||||
whenever(holder.dismissLabel).thenReturn(dismissLabel)
|
||||
initPlayerHolderMocks()
|
||||
initSessionHolderMocks()
|
||||
|
||||
// Create media session
|
||||
val metadataBuilder = MediaMetadata.Builder().apply {
|
||||
@@ -230,6 +204,89 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
whenever(mediaFlags.areMediaSessionActionsEnabled()).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)
|
||||
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)
|
||||
|
||||
// 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)
|
||||
|
||||
// Long press menu
|
||||
whenever(holder.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.dismiss).thenReturn(dismiss)
|
||||
whenever(holder.dismissLabel).thenReturn(dismissLabel)
|
||||
}
|
||||
|
||||
/** Mock view holder for session player */
|
||||
private fun initSessionHolderMocks() {
|
||||
whenever(sessionHolder.player).thenReturn(view)
|
||||
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)
|
||||
|
||||
// Action buttons
|
||||
actionPlayPause = ImageButton(context)
|
||||
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.dismiss).thenReturn(dismiss)
|
||||
whenever(sessionHolder.dismissLabel).thenReturn(dismissLabel)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
session.release()
|
||||
@@ -255,32 +312,28 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
)
|
||||
val state = mediaData.copy(semanticActions = semanticActions)
|
||||
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(sessionHolder, MediaViewController.TYPE.PLAYER_SESSION)
|
||||
player.bindPlayer(state, PACKAGE)
|
||||
|
||||
verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
|
||||
assertThat(action0.contentDescription).isEqualTo("custom 1")
|
||||
assertThat(action0.isEnabled()).isFalse()
|
||||
assertThat(actionStart.contentDescription).isEqualTo("custom 1")
|
||||
assertThat(actionStart.isEnabled()).isFalse()
|
||||
|
||||
verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
|
||||
assertThat(action1.isEnabled()).isFalse()
|
||||
assertThat(actionPrev.isEnabled()).isFalse()
|
||||
assertThat(actionPrev.drawable).isNull()
|
||||
|
||||
verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
|
||||
assertThat(action2.isEnabled()).isTrue()
|
||||
assertThat(action2.contentDescription).isEqualTo("play")
|
||||
assertThat(actionPlayPause.isEnabled()).isTrue()
|
||||
assertThat(actionPlayPause.contentDescription).isEqualTo("play")
|
||||
|
||||
verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE)
|
||||
assertThat(action3.isEnabled()).isTrue()
|
||||
assertThat(action3.contentDescription).isEqualTo("next")
|
||||
assertThat(actionNext.isEnabled()).isTrue()
|
||||
assertThat(actionNext.contentDescription).isEqualTo("next")
|
||||
|
||||
verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
|
||||
assertThat(action4.contentDescription).isEqualTo("custom 2")
|
||||
assertThat(action4.isEnabled()).isFalse()
|
||||
assertThat(actionEnd.contentDescription).isEqualTo("custom 2")
|
||||
assertThat(actionEnd.isEnabled()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bindText() {
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
player.bindPlayer(mediaData, PACKAGE)
|
||||
assertThat(titleText.getText()).isEqualTo(TITLE)
|
||||
assertThat(artistText.getText()).isEqualTo(ARTIST)
|
||||
@@ -288,7 +341,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun bindDevice() {
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
player.bindPlayer(mediaData, PACKAGE)
|
||||
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
|
||||
assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME)
|
||||
@@ -299,7 +352,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
fun bindDisabledDevice() {
|
||||
seamless.id = 1
|
||||
val fallbackString = context.getString(R.string.media_seamless_other_device)
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
val state = mediaData.copy(device = disabledDevice)
|
||||
player.bindPlayer(state, PACKAGE)
|
||||
assertThat(seamless.isEnabled()).isFalse()
|
||||
@@ -310,7 +363,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun bindNullDevice() {
|
||||
val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
val state = mediaData.copy(device = null)
|
||||
player.bindPlayer(state, PACKAGE)
|
||||
assertThat(seamless.isEnabled()).isTrue()
|
||||
@@ -320,7 +373,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun bindDeviceResumptionPlayer() {
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
val state = mediaData.copy(resumption = true)
|
||||
player.bindPlayer(state, PACKAGE)
|
||||
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
|
||||
@@ -329,7 +382,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun longClick_gutsClosed() {
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
whenever(mediaViewController.isGutsVisible).thenReturn(false)
|
||||
|
||||
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
|
||||
@@ -341,7 +394,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun longClick_gutsOpen() {
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
whenever(mediaViewController.isGutsVisible).thenReturn(true)
|
||||
|
||||
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
|
||||
@@ -354,7 +407,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun cancelButtonClick_animation() {
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
|
||||
cancel.callOnClick()
|
||||
|
||||
@@ -363,7 +416,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun settingsButtonClick() {
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
|
||||
settings.callOnClick()
|
||||
|
||||
@@ -376,7 +429,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun dismissButtonClick() {
|
||||
val mediaKey = "key for dismissal"
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
val state = mediaData.copy(notificationKey = KEY)
|
||||
player.bindPlayer(state, mediaKey)
|
||||
|
||||
@@ -388,7 +441,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun dismissButtonDisabled() {
|
||||
val mediaKey = "key for dismissal"
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
val state = mediaData.copy(isClearable = false, notificationKey = KEY)
|
||||
player.bindPlayer(state, mediaKey)
|
||||
|
||||
@@ -400,7 +453,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
val mediaKey = "key for dismissal"
|
||||
whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
|
||||
|
||||
player.attachPlayer(holder)
|
||||
player.attachPlayer(holder, MediaViewController.TYPE.PLAYER)
|
||||
val state = mediaData.copy(notificationKey = KEY)
|
||||
player.bindPlayer(state, mediaKey)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ public class SeekBarObserverTest : SysuiTestCase() {
|
||||
whenever(mockHolder.elapsedTimeView).thenReturn(elapsedTimeView)
|
||||
whenever(mockHolder.totalTimeView).thenReturn(totalTimeView)
|
||||
|
||||
observer = SeekBarObserver(mockHolder)
|
||||
observer = SeekBarObserver(mockHolder, false /* useSessionLayout */)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user