Merge "Update media recommendation card layout" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
121599e660
65
packages/SystemUI/res/layout/media_recommendation_view.xml
Normal file
65
packages/SystemUI/res/layout/media_recommendation_view.xml
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 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 recommendation item inside QSPanel carousel -->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Album cover -->
|
||||
<ImageView
|
||||
android:id="@+id/media_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:translationZ="0dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:adjustViewBounds="true"
|
||||
android:clipToOutline="true"
|
||||
android:background="@drawable/bg_smartspace_media_item"/>
|
||||
|
||||
<!-- App icon -->
|
||||
<com.android.internal.widget.CachingIconView
|
||||
android:id="@+id/media_rec_app_icon"
|
||||
android:layout_width="@dimen/qs_media_rec_icon_top_margin"
|
||||
android:layout_height="@dimen/qs_media_rec_icon_top_margin"
|
||||
android:layout_marginStart="@dimen/qs_media_info_spacing"
|
||||
android:layout_marginTop="@dimen/qs_media_info_spacing"/>
|
||||
|
||||
<!-- Artist name -->
|
||||
<TextView
|
||||
android:id="@+id/media_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/qs_media_info_spacing"
|
||||
android:layout_marginEnd="@dimen/qs_media_info_spacing"
|
||||
android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin"
|
||||
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
||||
android:singleLine="true"
|
||||
android:textSize="12sp"
|
||||
android:gravity="top"
|
||||
android:layout_gravity="bottom"/>
|
||||
|
||||
<!-- Album name -->
|
||||
<TextView
|
||||
android:id="@+id/media_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qs_media_rec_album_subtitle_height"
|
||||
android:layout_marginEnd="@dimen/qs_media_info_spacing"
|
||||
android:layout_marginStart="@dimen/qs_media_info_spacing"
|
||||
android:layout_marginBottom="@dimen/qs_media_info_spacing"
|
||||
android:fontFamily="@*android:string/config_headlineFontFamily"
|
||||
android:singleLine="true"
|
||||
android:textSize="11sp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="bottom"/>
|
||||
</merge>
|
||||
75
packages/SystemUI/res/layout/media_recommendations.xml
Normal file
75
packages/SystemUI/res/layout/media_recommendations.xml
Normal file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 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 recommendations inside QSPanel carousel -->
|
||||
<com.android.systemui.util.animation.TransitionLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/media_recommendations_updated"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:forceHasOverlappingRendering="false"
|
||||
android:background="@drawable/qs_media_background"
|
||||
android:theme="@style/MediaPlayer">
|
||||
|
||||
<!-- This view just ensures the full media player is a certain height. -->
|
||||
<View
|
||||
android:id="@+id/sizing_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qs_media_session_height_expanded" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/media_rec_title"
|
||||
style="@style/MediaPlayer.Recommendation.Header"
|
||||
android:text="@string/controls_media_smartspace_rec_header"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/media_cover1_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
>
|
||||
|
||||
<include
|
||||
layout="@layout/media_recommendation_view"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/media_cover2_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
>
|
||||
|
||||
<include
|
||||
layout="@layout/media_recommendation_view"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/media_cover3_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
>
|
||||
|
||||
<include
|
||||
layout="@layout/media_recommendation_view"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<include
|
||||
layout="@layout/media_long_press_menu" />
|
||||
|
||||
</com.android.systemui.util.animation.TransitionLayout>
|
||||
@@ -1068,8 +1068,13 @@
|
||||
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
|
||||
<dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
|
||||
<dimen name="qs_media_rec_album_size">88dp</dimen>
|
||||
<dimen name="qs_media_rec_album_width">110dp</dimen>
|
||||
<dimen name="qs_media_rec_album_height_expanded">108dp</dimen>
|
||||
<dimen name="qs_media_rec_album_height_collapsed">77dp</dimen>
|
||||
<dimen name="qs_media_rec_album_side_margin">16dp</dimen>
|
||||
<dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
|
||||
<dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen>
|
||||
<dimen name="qs_media_rec_album_subtitle_height">12dp</dimen>
|
||||
|
||||
<!-- Media tap-to-transfer chip for sender device -->
|
||||
<dimen name="media_ttt_chip_outer_padding">16dp</dimen>
|
||||
|
||||
@@ -2360,6 +2360,8 @@
|
||||
<string name="controls_media_smartspace_rec_item_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
|
||||
<!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]-->
|
||||
<string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
|
||||
<!-- Header title for Smartspace recommendation card within media controls. [CHAR_LIMIT=30] -->
|
||||
<string name="controls_media_smartspace_rec_header">For You</string>
|
||||
|
||||
<!--- ****** Media tap-to-transfer ****** -->
|
||||
<!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
|
||||
|
||||
@@ -678,6 +678,17 @@
|
||||
|
||||
<style name="MediaPlayer.Recommendation"/>
|
||||
|
||||
<style name="MediaPlayer.Recommendation.Header">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_marginTop">@dimen/qs_media_padding</item>
|
||||
<item name="android:layout_marginStart">@dimen/qs_media_padding</item>
|
||||
<item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="MediaPlayer.Recommendation.AlbumContainer">
|
||||
<item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
|
||||
<item name="android:layout_height">@dimen/qs_media_rec_album_size</item>
|
||||
@@ -686,6 +697,12 @@
|
||||
<item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item>
|
||||
</style>
|
||||
|
||||
<style name="MediaPlayer.Recommendation.AlbumContainer.Updated">
|
||||
<item name="android:layout_width">@dimen/qs_media_rec_album_width</item>
|
||||
<item name="android:background">@drawable/qs_media_light_source</item>
|
||||
<item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item>
|
||||
</style>
|
||||
|
||||
<style name="MediaPlayer.Recommendation.Album">
|
||||
<item name="android:backgroundTint">@color/media_player_album_bg</item>
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 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/sizing_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qs_media_session_height_collapsed"
|
||||
/>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/media_rec_title"
|
||||
style="@style/MediaPlayer.Recommendation.Header"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/media_cover1_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
|
||||
android:layout_marginEnd="@dimen/qs_media_info_spacing"
|
||||
android:layout_marginStart="@dimen/qs_media_padding"
|
||||
app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
|
||||
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/media_cover2_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
|
||||
android:layout_marginEnd="@dimen/qs_media_info_spacing"
|
||||
app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
|
||||
app:layout_constraintStart_toEndOf="@id/media_cover1_container"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/media_cover3_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
|
||||
android:layout_marginEnd="@dimen/qs_media_padding"
|
||||
app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
|
||||
app:layout_constraintStart_toEndOf="@id/media_cover2_container"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
|
||||
</ConstraintSet>
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 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/sizing_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/qs_media_session_height_expanded"
|
||||
/>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/media_rec_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/qs_media_padding"
|
||||
android:layout_marginStart="@dimen/qs_media_padding"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
||||
android:singleLine="true"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@color/notification_primary_text_color"/>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/media_cover1_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
android:layout_height="@dimen/qs_media_rec_album_height_expanded"
|
||||
android:layout_marginEnd="@dimen/qs_media_info_spacing"
|
||||
android:layout_marginStart="@dimen/qs_media_padding"
|
||||
app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
|
||||
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/media_cover2_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
android:layout_height="@dimen/qs_media_rec_album_height_expanded"
|
||||
android:layout_marginEnd="@dimen/qs_media_info_spacing"
|
||||
app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
|
||||
app:layout_constraintStart_toEndOf="@id/media_cover1_container"
|
||||
app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/media_cover3_container"
|
||||
style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
|
||||
android:layout_height="@dimen/qs_media_rec_album_height_expanded"
|
||||
android:layout_marginEnd="@dimen/qs_media_padding"
|
||||
app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
|
||||
app:layout_constraintStart_toEndOf="@id/media_cover2_container"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
|
||||
</ConstraintSet>
|
||||
@@ -365,6 +365,10 @@ object Flags {
|
||||
// TODO(b/266157412): Tracking Bug
|
||||
val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
|
||||
|
||||
// TODO(b/266739309): Tracking Bug
|
||||
@JvmField
|
||||
val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
|
||||
|
||||
// 1000 - dock
|
||||
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.android.internal.widget.CachingIconView
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.media.controls.models.GutsViewHolder
|
||||
import com.android.systemui.media.controls.ui.IlluminationDrawable
|
||||
@@ -29,18 +30,15 @@ import com.android.systemui.util.animation.TransitionLayout
|
||||
private const val TAG = "RecommendationViewHolder"
|
||||
|
||||
/** ViewHolder for a Smartspace media recommendation. */
|
||||
class RecommendationViewHolder private constructor(itemView: View) {
|
||||
class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
|
||||
|
||||
val recommendations = itemView as TransitionLayout
|
||||
|
||||
// Recommendation screen
|
||||
val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
|
||||
val mediaCoverItems =
|
||||
listOf<ImageView>(
|
||||
itemView.requireViewById(R.id.media_cover1),
|
||||
itemView.requireViewById(R.id.media_cover2),
|
||||
itemView.requireViewById(R.id.media_cover3)
|
||||
)
|
||||
lateinit var cardIcon: ImageView
|
||||
lateinit var mediaAppIcons: List<CachingIconView>
|
||||
lateinit var cardTitle: TextView
|
||||
|
||||
val mediaCoverContainers =
|
||||
listOf<ViewGroup>(
|
||||
itemView.requireViewById(R.id.media_cover1_container),
|
||||
@@ -48,21 +46,45 @@ class RecommendationViewHolder private constructor(itemView: View) {
|
||||
itemView.requireViewById(R.id.media_cover3_container)
|
||||
)
|
||||
val mediaTitles: List<TextView> =
|
||||
listOf(
|
||||
itemView.requireViewById(R.id.media_title1),
|
||||
itemView.requireViewById(R.id.media_title2),
|
||||
itemView.requireViewById(R.id.media_title3)
|
||||
)
|
||||
if (updatedView) {
|
||||
mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
|
||||
} else {
|
||||
listOf(
|
||||
itemView.requireViewById(R.id.media_title1),
|
||||
itemView.requireViewById(R.id.media_title2),
|
||||
itemView.requireViewById(R.id.media_title3)
|
||||
)
|
||||
}
|
||||
val mediaSubtitles: List<TextView> =
|
||||
listOf(
|
||||
itemView.requireViewById(R.id.media_subtitle1),
|
||||
itemView.requireViewById(R.id.media_subtitle2),
|
||||
itemView.requireViewById(R.id.media_subtitle3)
|
||||
)
|
||||
if (updatedView) {
|
||||
mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
|
||||
} else {
|
||||
listOf(
|
||||
itemView.requireViewById(R.id.media_subtitle1),
|
||||
itemView.requireViewById(R.id.media_subtitle2),
|
||||
itemView.requireViewById(R.id.media_subtitle3)
|
||||
)
|
||||
}
|
||||
|
||||
val mediaCoverItems: List<ImageView> =
|
||||
if (updatedView) {
|
||||
mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
|
||||
} else {
|
||||
listOf(
|
||||
itemView.requireViewById(R.id.media_cover1),
|
||||
itemView.requireViewById(R.id.media_cover2),
|
||||
itemView.requireViewById(R.id.media_cover3)
|
||||
)
|
||||
}
|
||||
val gutsViewHolder = GutsViewHolder(itemView)
|
||||
|
||||
init {
|
||||
if (updatedView) {
|
||||
mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
|
||||
cardTitle = itemView.requireViewById(R.id.media_rec_title)
|
||||
} else {
|
||||
cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
|
||||
}
|
||||
(recommendations.background as IlluminationDrawable).let { background ->
|
||||
mediaCoverContainers.forEach { background.registerLightSource(it) }
|
||||
background.registerLightSource(gutsViewHolder.cancel)
|
||||
@@ -83,36 +105,52 @@ class RecommendationViewHolder private constructor(itemView: View) {
|
||||
* @param parent Parent of inflated view.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
|
||||
fun create(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
updatedView: Boolean,
|
||||
): RecommendationViewHolder {
|
||||
val itemView =
|
||||
inflater.inflate(
|
||||
R.layout.media_smartspace_recommendations,
|
||||
parent,
|
||||
false /* attachToRoot */
|
||||
)
|
||||
if (updatedView) {
|
||||
inflater.inflate(
|
||||
R.layout.media_recommendations,
|
||||
parent,
|
||||
false /* attachToRoot */
|
||||
)
|
||||
} else {
|
||||
inflater.inflate(
|
||||
R.layout.media_smartspace_recommendations,
|
||||
parent,
|
||||
false /* attachToRoot */
|
||||
)
|
||||
}
|
||||
// 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.
|
||||
itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
|
||||
return RecommendationViewHolder(itemView)
|
||||
return RecommendationViewHolder(itemView, updatedView)
|
||||
}
|
||||
|
||||
// Res Ids for the control components on the recommendation view.
|
||||
val controlsIds =
|
||||
setOf(
|
||||
R.id.recommendation_card_icon,
|
||||
R.id.media_rec_title,
|
||||
R.id.media_cover1,
|
||||
R.id.media_cover2,
|
||||
R.id.media_cover3,
|
||||
R.id.media_cover,
|
||||
R.id.media_cover1_container,
|
||||
R.id.media_cover2_container,
|
||||
R.id.media_cover3_container,
|
||||
R.id.media_title1,
|
||||
R.id.media_title2,
|
||||
R.id.media_title3,
|
||||
R.id.media_title,
|
||||
R.id.media_subtitle1,
|
||||
R.id.media_subtitle2,
|
||||
R.id.media_subtitle3
|
||||
R.id.media_subtitle3,
|
||||
R.id.media_subtitle,
|
||||
)
|
||||
|
||||
val mediaTitlesAndSubtitlesIds =
|
||||
@@ -120,9 +158,11 @@ class RecommendationViewHolder private constructor(itemView: View) {
|
||||
R.id.media_title1,
|
||||
R.id.media_title2,
|
||||
R.id.media_title3,
|
||||
R.id.media_title,
|
||||
R.id.media_subtitle1,
|
||||
R.id.media_subtitle2,
|
||||
R.id.media_subtitle3
|
||||
R.id.media_subtitle3,
|
||||
R.id.media_subtitle,
|
||||
)
|
||||
|
||||
val mediaContainersIds =
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.android.systemui.media.controls.models.recommendation.RecommendationV
|
||||
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
|
||||
import com.android.systemui.media.controls.pipeline.MediaDataManager
|
||||
import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
|
||||
import com.android.systemui.media.controls.util.MediaFlags
|
||||
import com.android.systemui.media.controls.util.MediaUiEventLogger
|
||||
import com.android.systemui.media.controls.util.SmallHash
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
@@ -88,7 +89,8 @@ constructor(
|
||||
falsingManager: FalsingManager,
|
||||
dumpManager: DumpManager,
|
||||
private val logger: MediaUiEventLogger,
|
||||
private val debugLogger: MediaCarouselControllerLogger
|
||||
private val debugLogger: MediaCarouselControllerLogger,
|
||||
private val mediaFlags: MediaFlags,
|
||||
) : Dumpable {
|
||||
/** The current width of the carousel */
|
||||
private var currentCarouselWidth: Int = 0
|
||||
@@ -647,7 +649,11 @@ constructor(
|
||||
|
||||
val newRecs = mediaControlPanelFactory.get()
|
||||
newRecs.attachRecommendation(
|
||||
RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
|
||||
RecommendationViewHolder.create(
|
||||
LayoutInflater.from(context),
|
||||
mediaContent,
|
||||
mediaFlags.isRecommendationCardUpdateEnabled()
|
||||
)
|
||||
)
|
||||
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
|
||||
val lp =
|
||||
|
||||
@@ -61,6 +61,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.constraintlayout.widget.ConstraintSet;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -752,43 +753,16 @@ public class MediaControlPanel {
|
||||
int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
|
||||
int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
|
||||
|
||||
// WallpaperColors.fromBitmap takes a good amount of time. We do that work
|
||||
// on the background executor to avoid stalling animations on the UI Thread.
|
||||
mBackgroundExecutor.execute(() -> {
|
||||
// Album art
|
||||
ColorScheme mutableColorScheme = null;
|
||||
Drawable artwork;
|
||||
boolean isArtworkBound;
|
||||
Icon artworkIcon = data.getArtwork();
|
||||
WallpaperColors wallpaperColors = null;
|
||||
if (artworkIcon != null) {
|
||||
if (artworkIcon.getType() == Icon.TYPE_BITMAP
|
||||
|| artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
|
||||
// Avoids extra processing if this is already a valid bitmap
|
||||
wallpaperColors = WallpaperColors
|
||||
.fromBitmap(artworkIcon.getBitmap());
|
||||
} else {
|
||||
Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
|
||||
if (artworkDrawable != null) {
|
||||
wallpaperColors = WallpaperColors
|
||||
.fromDrawable(artworkIcon.loadDrawable(mContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
|
||||
if (wallpaperColors != null) {
|
||||
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
|
||||
Drawable albumArt = getScaledBackground(artworkIcon, width, height);
|
||||
GradientDrawable gradient = (GradientDrawable) mContext
|
||||
.getDrawable(R.drawable.qs_media_scrim);
|
||||
gradient.setColors(new int[] {
|
||||
ColorUtilKt.getColorWithAlpha(
|
||||
MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
|
||||
0.25f),
|
||||
ColorUtilKt.getColorWithAlpha(
|
||||
MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
|
||||
0.9f),
|
||||
});
|
||||
artwork = new LayerDrawable(new Drawable[] { albumArt, gradient });
|
||||
artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
|
||||
isArtworkBound = true;
|
||||
} else {
|
||||
// If there's no artwork, use colors from the app icon
|
||||
@@ -867,6 +841,96 @@ public class MediaControlPanel {
|
||||
});
|
||||
}
|
||||
|
||||
private void bindRecommendationArtwork(
|
||||
SmartspaceAction recommendation,
|
||||
String packageName,
|
||||
int itemIndex
|
||||
) {
|
||||
final int traceCookie = recommendation.hashCode();
|
||||
final String traceName =
|
||||
"MediaControlPanel#bindRecommendationArtwork<" + packageName + ">";
|
||||
Trace.beginAsyncSection(traceName, traceCookie);
|
||||
|
||||
// Capture width & height from views in foreground for artwork scaling in background
|
||||
int width = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredWidth();
|
||||
int height = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredHeight();
|
||||
|
||||
mBackgroundExecutor.execute(() -> {
|
||||
// Album art
|
||||
ColorScheme mutableColorScheme = null;
|
||||
Drawable artwork;
|
||||
Icon artworkIcon = recommendation.getIcon();
|
||||
WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
|
||||
if (wallpaperColors != null) {
|
||||
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
|
||||
artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
|
||||
} else {
|
||||
artwork = new ColorDrawable(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
mMainExecutor.execute(() -> {
|
||||
// Bind the artwork drawable to media cover.
|
||||
ImageView mediaCover =
|
||||
mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
|
||||
mediaCover.setImageDrawable(artwork);
|
||||
|
||||
// Set up the app icon.
|
||||
ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex);
|
||||
appIconView.clearColorFilter();
|
||||
try {
|
||||
Drawable icon = mContext.getPackageManager()
|
||||
.getApplicationIcon(packageName);
|
||||
appIconView.setImageDrawable(icon);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, "Cannot find icon for package " + packageName, e);
|
||||
appIconView.setImageResource(R.drawable.ic_music_note);
|
||||
}
|
||||
Trace.endAsyncSection(traceName, traceCookie);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// This method should be called from a background thread. WallpaperColors.fromBitmap takes a
|
||||
// good amount of time. We do that work on the background executor to avoid stalling animations
|
||||
// on the UI Thread.
|
||||
private WallpaperColors getWallpaperColor(Icon artworkIcon) {
|
||||
if (artworkIcon != null) {
|
||||
if (artworkIcon.getType() == Icon.TYPE_BITMAP
|
||||
|| artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
|
||||
// Avoids extra processing if this is already a valid bitmap
|
||||
return WallpaperColors
|
||||
.fromBitmap(artworkIcon.getBitmap());
|
||||
} else {
|
||||
Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
|
||||
if (artworkDrawable != null) {
|
||||
return WallpaperColors
|
||||
.fromDrawable(artworkIcon.loadDrawable(mContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private LayerDrawable addGradientToIcon(
|
||||
Icon artworkIcon,
|
||||
ColorScheme mutableColorScheme,
|
||||
int width,
|
||||
int height
|
||||
) {
|
||||
Drawable albumArt = getScaledBackground(artworkIcon, width, height);
|
||||
GradientDrawable gradient = (GradientDrawable) AppCompatResources
|
||||
.getDrawable(mContext, R.drawable.qs_media_scrim);
|
||||
gradient.setColors(new int[] {
|
||||
ColorUtilKt.getColorWithAlpha(
|
||||
MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
|
||||
0.25f),
|
||||
ColorUtilKt.getColorWithAlpha(
|
||||
MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
|
||||
0.9f),
|
||||
});
|
||||
return new LayerDrawable(new Drawable[] { albumArt, gradient });
|
||||
}
|
||||
|
||||
private void scaleTransitionDrawableLayer(TransitionDrawable transitionDrawable, int layer,
|
||||
int targetWidth, int targetHeight) {
|
||||
Drawable drawable = transitionDrawable.getDrawable(layer);
|
||||
@@ -1224,8 +1288,10 @@ public class MediaControlPanel {
|
||||
PackageManager packageManager = mContext.getPackageManager();
|
||||
// Set up media source app's logo.
|
||||
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
|
||||
ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
|
||||
headerLogoImageView.setImageDrawable(icon);
|
||||
if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
|
||||
ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
|
||||
headerLogoImageView.setImageDrawable(icon);
|
||||
}
|
||||
fetchAndUpdateRecommendationColors(icon);
|
||||
|
||||
// Set up media rec card's tap action if applicable.
|
||||
@@ -1245,7 +1311,15 @@ public class MediaControlPanel {
|
||||
|
||||
// Set up media item cover.
|
||||
ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
|
||||
mediaCoverImageView.setImageIcon(recommendation.getIcon());
|
||||
if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
|
||||
bindRecommendationArtwork(
|
||||
recommendation,
|
||||
data.getPackageName(),
|
||||
itemIndex
|
||||
);
|
||||
} else {
|
||||
mediaCoverImageView.setImageIcon(recommendation.getIcon());
|
||||
}
|
||||
|
||||
// Set up the media item's click listener if applicable.
|
||||
ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
|
||||
@@ -1275,7 +1349,6 @@ public class MediaControlPanel {
|
||||
recommendation.getTitle(), artistName, appName));
|
||||
}
|
||||
|
||||
|
||||
// Set up title
|
||||
CharSequence title = recommendation.getTitle();
|
||||
hasTitle |= !TextUtils.isEmpty(title);
|
||||
@@ -1353,6 +1426,10 @@ public class MediaControlPanel {
|
||||
int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
|
||||
int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
|
||||
|
||||
if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
|
||||
mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
|
||||
}
|
||||
|
||||
mRecommendationViewHolder.getRecommendations()
|
||||
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
|
||||
mRecommendationViewHolder.getMediaTitles().forEach(
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.media.controls.models.GutsViewHolder
|
||||
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
||||
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
|
||||
import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
|
||||
import com.android.systemui.media.controls.util.MediaFlags
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.animation.MeasurementOutput
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
@@ -45,7 +46,8 @@ constructor(
|
||||
private val context: Context,
|
||||
private val configurationController: ConfigurationController,
|
||||
private val mediaHostStatesManager: MediaHostStatesManager,
|
||||
private val logger: MediaViewLogger
|
||||
private val logger: MediaViewLogger,
|
||||
private val mediaFlags: MediaFlags,
|
||||
) {
|
||||
|
||||
/**
|
||||
@@ -646,8 +648,13 @@ constructor(
|
||||
expandedLayout.load(context, R.xml.media_session_expanded)
|
||||
}
|
||||
TYPE.RECOMMENDATION -> {
|
||||
collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
|
||||
expandedLayout.load(context, R.xml.media_recommendation_expanded)
|
||||
if (mediaFlags.isRecommendationCardUpdateEnabled()) {
|
||||
collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
|
||||
expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
|
||||
} else {
|
||||
collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
|
||||
expandedLayout.load(context, R.xml.media_recommendation_expanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshState()
|
||||
|
||||
@@ -51,4 +51,8 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
|
||||
* whether the underlying notification was dismissed
|
||||
*/
|
||||
fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
|
||||
|
||||
/** Check whether we show the updated recommendation card. */
|
||||
fun isRecommendationCardUpdateEnabled() =
|
||||
featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.android.systemui.media.controls.models.recommendation.SmartspaceMedia
|
||||
import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
|
||||
import com.android.systemui.media.controls.pipeline.MediaDataManager
|
||||
import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
|
||||
import com.android.systemui.media.controls.util.MediaFlags
|
||||
import com.android.systemui.media.controls.util.MediaUiEventLogger
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
@@ -87,6 +88,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
@Mock lateinit var smartspaceMediaData: SmartspaceMediaData
|
||||
@Mock lateinit var mediaCarousel: MediaScrollView
|
||||
@Mock lateinit var pageIndicator: PageIndicator
|
||||
@Mock lateinit var mediaFlags: MediaFlags
|
||||
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
|
||||
@Captor
|
||||
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
|
||||
@@ -114,7 +116,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
||||
falsingManager,
|
||||
dumpManager,
|
||||
logger,
|
||||
debugLogger
|
||||
debugLogger,
|
||||
mediaFlags,
|
||||
)
|
||||
verify(configurationController).addCallback(capture(configListener))
|
||||
verify(mediaDataManager).addListener(capture(listener))
|
||||
|
||||
@@ -204,6 +204,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
@Mock private lateinit var coverContainer1: ViewGroup
|
||||
@Mock private lateinit var coverContainer2: ViewGroup
|
||||
@Mock private lateinit var coverContainer3: ViewGroup
|
||||
@Mock private lateinit var recAppIconItem: CachingIconView
|
||||
@Mock private lateinit var recCardTitle: TextView
|
||||
@Mock private lateinit var coverItem: ImageView
|
||||
private lateinit var coverItem1: ImageView
|
||||
private lateinit var coverItem2: ImageView
|
||||
private lateinit var coverItem3: ImageView
|
||||
@@ -220,6 +223,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
this.set(Flags.UMO_TURBULENCE_NOISE, false)
|
||||
this.set(Flags.MEDIA_FALSING_PENALTY, true)
|
||||
this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
|
||||
this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
|
||||
}
|
||||
|
||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||
@@ -2058,6 +2062,51 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
||||
assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bindRecommendation_setAfterExecutors() {
|
||||
fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
|
||||
whenever(recommendationViewHolder.mediaAppIcons)
|
||||
.thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
|
||||
whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
|
||||
whenever(recommendationViewHolder.mediaCoverItems)
|
||||
.thenReturn(listOf(coverItem, coverItem, coverItem))
|
||||
|
||||
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bmp)
|
||||
canvas.drawColor(Color.RED)
|
||||
val albumArt = Icon.createWithBitmap(bmp)
|
||||
val data =
|
||||
smartspaceData.copy(
|
||||
recommendations =
|
||||
listOf(
|
||||
SmartspaceAction.Builder("id1", "title1")
|
||||
.setSubtitle("subtitle1")
|
||||
.setIcon(albumArt)
|
||||
.setExtras(Bundle.EMPTY)
|
||||
.build(),
|
||||
SmartspaceAction.Builder("id2", "title2")
|
||||
.setSubtitle("subtitle1")
|
||||
.setIcon(albumArt)
|
||||
.setExtras(Bundle.EMPTY)
|
||||
.build(),
|
||||
SmartspaceAction.Builder("id3", "title3")
|
||||
.setSubtitle("subtitle1")
|
||||
.setIcon(albumArt)
|
||||
.setExtras(Bundle.EMPTY)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
|
||||
player.attachRecommendation(recommendationViewHolder)
|
||||
player.bindRecommendation(data)
|
||||
bgExecutor.runAllReady()
|
||||
mainExecutor.runAllReady()
|
||||
|
||||
verify(recCardTitle).setTextColor(any<Int>())
|
||||
verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
|
||||
verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
|
||||
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.media.controls.util.MediaFlags
|
||||
import com.android.systemui.util.animation.MeasurementInput
|
||||
import com.android.systemui.util.animation.TransitionLayout
|
||||
import com.android.systemui.util.animation.TransitionViewState
|
||||
@@ -55,6 +56,7 @@ class MediaViewControllerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var mediaTitleWidgetState: WidgetState
|
||||
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
|
||||
@Mock private lateinit var mediaContainerWidgetState: WidgetState
|
||||
@Mock private lateinit var mediaFlags: MediaFlags
|
||||
|
||||
val delta = 0.1F
|
||||
|
||||
@@ -64,7 +66,13 @@ class MediaViewControllerTest : SysuiTestCase() {
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
mediaViewController =
|
||||
MediaViewController(context, configurationController, mediaHostStatesManager, logger)
|
||||
MediaViewController(
|
||||
context,
|
||||
configurationController,
|
||||
mediaHostStatesManager,
|
||||
logger,
|
||||
mediaFlags,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user