Adjust the number of recommendations according to display size
We currently have 3 recommendations to show in the card. When the
display size gets bigger, the 3 recommendation covers cannot fit in the
media carousel. So we calculate the width of the screen compared to the
the default width of media controls. Then we get min(3, available width / cover)
as the number of recommendations that can be visibile.
Added changes to title/subtitle visibility that were missing from
collapsed layout.
Bug: 267295550
Test: atest MediaControlPanelTest
Test: checked recommendation card in both expanded and collapsed
layouts. And tried all combinations of display/text sizes.
Change-Id: Ic047c020fd91f9ed835619fe14748a7a40132798
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 082f385..1602189 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -64,6 +64,10 @@
<!-- The number of rows in the QuickSettings -->
<integer name="quick_settings_max_rows">4</integer>
+ <!-- If the dp width of the available space is <= this value, potentially adjust the number
+ of media recommendation items-->
+ <integer name="default_qs_media_rec_width_dp">380</integer>
+
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<!-- The default tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ff86c59..412e70f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1094,6 +1094,7 @@
<dimen name="qs_media_session_collapsed_guideline">144dp</dimen>
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
+ <dimen name="qs_media_rec_default_width">380dp</dimen>
<dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
<dimen name="qs_media_rec_album_icon_size">16dp</dimen>
<dimen name="qs_media_rec_album_size">88dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 9c7b48d..f08b9769 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -109,10 +109,11 @@
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : Dumpable {
/** The current width of the carousel */
- private var currentCarouselWidth: Int = 0
+ var currentCarouselWidth: Int = 0
+ private set
/** The current height of the carousel */
- @VisibleForTesting var currentCarouselHeight: Int = 0
+ private var currentCarouselHeight: Int = 0
/** Are we currently showing only active players */
private var currentlyShowingOnlyActive: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 15d999a..cb1f12cf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -32,6 +32,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Color;
@@ -54,6 +56,7 @@
import android.os.Trace;
import android.text.TextUtils;
import android.util.Log;
+import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -468,6 +471,7 @@
TransitionLayout recommendations = vh.getRecommendations();
mMediaViewController.attach(recommendations, MediaViewController.TYPE.RECOMMENDATION);
+ mMediaViewController.configurationChangeListener = this::updateRecommendationsVisibility;
mRecommendationViewHolder.getRecommendations().setOnLongClickListener(v -> {
if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
@@ -1364,6 +1368,7 @@
boolean hasTitle = false;
boolean hasSubtitle = false;
+ int fittedRecsNum = getNumberOfFittedRecommendations();
for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) {
SmartspaceAction recommendation = recommendations.get(itemIndex);
@@ -1444,12 +1449,20 @@
// If there's no subtitles and/or titles for any of the albums, hide those views.
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+ ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
final boolean titlesVisible = hasTitle;
final boolean subtitlesVisible = hasSubtitle;
- mRecommendationViewHolder.getMediaTitles().forEach((titleView) ->
- setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible));
- mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) ->
- setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible));
+ mRecommendationViewHolder.getMediaTitles().forEach((titleView) -> {
+ setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible);
+ setVisibleAndAlpha(collapsedSet, titleView.getId(), titlesVisible);
+ });
+ mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) -> {
+ setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible);
+ setVisibleAndAlpha(collapsedSet, subtitleView.getId(), subtitlesVisible);
+ });
+
+ // Media covers visibility.
+ setMediaCoversVisibility(fittedRecsNum);
// Guts
Runnable onDismissClickedRunnable = () -> {
@@ -1486,6 +1499,51 @@
Trace.endSection();
}
+ private Unit updateRecommendationsVisibility() {
+ int fittedRecsNum = getNumberOfFittedRecommendations();
+ setMediaCoversVisibility(fittedRecsNum);
+ return Unit.INSTANCE;
+ }
+
+ private void setMediaCoversVisibility(int fittedRecsNum) {
+ ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+ ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
+ List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
+ // Hide media cover that cannot fit in the recommendation card.
+ for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) {
+ setVisibleAndAlpha(expandedSet, mediaCoverContainers.get(itemIndex).getId(),
+ itemIndex < fittedRecsNum);
+ setVisibleAndAlpha(collapsedSet, mediaCoverContainers.get(itemIndex).getId(),
+ itemIndex < fittedRecsNum);
+ }
+ }
+
+ @VisibleForTesting
+ protected int getNumberOfFittedRecommendations() {
+ Resources res = mContext.getResources();
+ Configuration config = res.getConfiguration();
+ int defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp);
+ int recCoverWidth = res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
+ + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2;
+
+ // On landscape, media controls should take half of the screen width.
+ int displayAvailableDpWidth = config.screenWidthDp;
+ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ displayAvailableDpWidth = displayAvailableDpWidth / 2;
+ }
+ int fittedNum;
+ if (displayAvailableDpWidth > defaultDpWidth) {
+ int recCoverDefaultWidth = res.getDimensionPixelSize(
+ R.dimen.qs_media_rec_default_width);
+ fittedNum = recCoverDefaultWidth / recCoverWidth;
+ } else {
+ int displayAvailableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ displayAvailableDpWidth, res.getDisplayMetrics());
+ fittedNum = displayAvailableWidth / recCoverWidth;
+ }
+ return Math.min(fittedNum, NUM_REQUIRED_RECOMMENDATIONS);
+ }
+
private void fetchAndUpdateRecommendationColors(Drawable appIcon) {
mBackgroundExecutor.execute(() -> {
ColorScheme colorScheme = new ColorScheme(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 7a1302c..cd51d92 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -96,6 +96,7 @@
/** A listener when the current dimensions of the player change */
lateinit var sizeChangedListener: () -> Unit
+ lateinit var configurationChangeListener: () -> Unit
private var firstRefresh: Boolean = true
@VisibleForTesting private var transitionLayout: TransitionLayout? = null
private val layoutController = TransitionLayoutController()
@@ -195,6 +196,10 @@
)
}
}
+ if (this@MediaViewController::configurationChangeListener.isInitialized) {
+ configurationChangeListener.invoke()
+ refreshState()
+ }
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 55b57f1..543875d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -209,12 +209,6 @@
@Mock private lateinit var coverContainer3: ViewGroup
@Mock private lateinit var recAppIconItem: CachingIconView
@Mock private lateinit var recCardTitle: TextView
- @Mock private lateinit var recProgressBar1: SeekBar
- @Mock private lateinit var recProgressBar2: SeekBar
- @Mock private lateinit var recProgressBar3: SeekBar
- @Mock private lateinit var recSubtitleMock1: TextView
- @Mock private lateinit var recSubtitleMock2: TextView
- @Mock private lateinit var recSubtitleMock3: TextView
@Mock private lateinit var coverItem: ImageView
@Mock private lateinit var matrix: Matrix
private lateinit var coverItem1: ImageView
@@ -226,6 +220,9 @@
private lateinit var recSubtitle1: TextView
private lateinit var recSubtitle2: TextView
private lateinit var recSubtitle3: TextView
+ @Mock private lateinit var recProgressBar1: SeekBar
+ @Mock private lateinit var recProgressBar2: SeekBar
+ @Mock private lateinit var recProgressBar3: SeekBar
private var shouldShowBroadcastButton: Boolean = false
private val fakeFeatureFlag =
FakeFeatureFlags().apply {
@@ -636,10 +633,7 @@
@Test
fun bindAlbumView_setAfterExecutors() {
- val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val canvas = Canvas(bmp)
- canvas.drawColor(Color.RED)
- val albumArt = Icon.createWithBitmap(bmp)
+ val albumArt = getColorIcon(Color.RED)
val state = mediaData.copy(artwork = albumArt)
player.attachPlayer(viewHolder)
@@ -652,15 +646,8 @@
@Test
fun bindAlbumView_bitmapInLaterStates_setAfterExecutors() {
- val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val redCanvas = Canvas(redBmp)
- redCanvas.drawColor(Color.RED)
- val redArt = Icon.createWithBitmap(redBmp)
-
- val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val greenCanvas = Canvas(greenBmp)
- greenCanvas.drawColor(Color.GREEN)
- val greenArt = Icon.createWithBitmap(greenBmp)
+ val redArt = getColorIcon(Color.RED)
+ val greenArt = getColorIcon(Color.GREEN)
val state0 = mediaData.copy(artwork = null)
val state1 = mediaData.copy(artwork = redArt)
@@ -705,18 +692,12 @@
@Test
fun addTwoPlayerGradients_differentStates() {
// Setup redArtwork and its color scheme.
- val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val redCanvas = Canvas(redBmp)
- redCanvas.drawColor(Color.RED)
- val redArt = Icon.createWithBitmap(redBmp)
+ val redArt = getColorIcon(Color.RED)
val redWallpaperColor = player.getWallpaperColor(redArt)
val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT)
// Setup greenArt and its color scheme.
- val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val greenCanvas = Canvas(greenBmp)
- greenCanvas.drawColor(Color.GREEN)
- val greenArt = Icon.createWithBitmap(greenBmp)
+ val greenArt = getColorIcon(Color.GREEN)
val greenWallpaperColor = player.getWallpaperColor(greenArt)
val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
@@ -2040,12 +2021,12 @@
.setExtras(Bundle.EMPTY)
.build(),
SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("")
+ .setSubtitle("subtitle2")
.setIcon(icon)
.setExtras(Bundle.EMPTY)
.build(),
SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle3")
+ .setSubtitle("")
.setIcon(icon)
.setExtras(Bundle.EMPTY)
.build()
@@ -2125,26 +2106,18 @@
assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.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))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
- whenever(coverItem.imageMatrix).thenReturn(matrix)
-
- val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val canvas = Canvas(bmp)
- canvas.drawColor(Color.RED)
- val albumArt = Icon.createWithBitmap(bmp)
+ setupUpdatedRecommendationViewHolder()
+ val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
recommendations =
@@ -2180,21 +2153,9 @@
@Test
fun bindRecommendationWithProgressBars() {
- 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))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
-
- val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val canvas = Canvas(bmp)
- canvas.drawColor(Color.RED)
- val albumArt = Icon.createWithBitmap(bmp)
+ useRealConstraintSets()
+ setupUpdatedRecommendationViewHolder()
+ val albumArt = getColorIcon(Color.RED)
val bundle =
Bundle().apply {
putInt(
@@ -2232,26 +2193,61 @@
verify(recProgressBar1).visibility = View.VISIBLE
verify(recProgressBar2).visibility = View.GONE
verify(recProgressBar3).visibility = View.GONE
- verify(recSubtitleMock1).visibility = View.GONE
- verify(recSubtitleMock2).visibility = View.VISIBLE
- verify(recSubtitleMock3).visibility = View.VISIBLE
+ assertThat(recSubtitle1.visibility).isEqualTo(View.GONE)
+ assertThat(recSubtitle2.visibility).isEqualTo(View.VISIBLE)
+ assertThat(recSubtitle3.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun bindRecommendation_carouselNotFitThreeRecs() {
+ useRealConstraintSets()
+ setupUpdatedRecommendationViewHolder()
+ val albumArt = getColorIcon(Color.RED)
+ 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()
+ )
+ )
+
+ // set the screen width less than the width of media controls.
+ player.context.resources.configuration.screenWidthDp = 350
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(data)
+
+ assertThat(player.numberOfFittedRecommendations).isEqualTo(2)
+ assertThat(expandedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(collapsedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(expandedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(collapsedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(expandedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
}
@Test
fun addTwoRecommendationGradients_differentStates() {
// Setup redArtwork and its color scheme.
- val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val redCanvas = Canvas(redBmp)
- redCanvas.drawColor(Color.RED)
- val redArt = Icon.createWithBitmap(redBmp)
+ val redArt = getColorIcon(Color.RED)
val redWallpaperColor = player.getWallpaperColor(redArt)
val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT)
// Setup greenArt and its color scheme.
- val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val greenCanvas = Canvas(greenBmp)
- greenCanvas.drawColor(Color.GREEN)
- val greenArt = Icon.createWithBitmap(greenBmp)
+ val greenArt = getColorIcon(Color.GREEN)
val greenWallpaperColor = player.getWallpaperColor(greenArt)
val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
@@ -2392,6 +2388,34 @@
verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent))
}
+ private fun setupUpdatedRecommendationViewHolder() {
+ fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+ whenever(recommendationViewHolder.mediaCoverContainers)
+ .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
+ whenever(recommendationViewHolder.mediaCoverItems)
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(recommendationViewHolder.mediaSubtitles)
+ .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
+ whenever(coverItem.imageMatrix).thenReturn(matrix)
+
+ // set ids for recommendation containers
+ whenever(coverContainer1.id).thenReturn(1)
+ whenever(coverContainer2.id).thenReturn(2)
+ whenever(coverContainer3.id).thenReturn(3)
+ }
+
+ private fun getColorIcon(color: Int): Icon {
+ val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bmp)
+ canvas.drawColor(color)
+ return Icon.createWithBitmap(bmp)
+ }
+
private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
withArgCaptor {
verify(seekBarViewModel).setScrubbingChangeListener(capture())