From 65c796644b54c936dd453face51686af27135c13 Mon Sep 17 00:00:00 2001 From: Michael Mikhail Date: Thu, 2 Mar 2023 23:54:07 +0000 Subject: [PATCH] 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 --- packages/SystemUI/res/values/config.xml | 4 + packages/SystemUI/res/values/dimens.xml | 1 + .../controls/ui/MediaCarouselController.kt | 5 +- .../media/controls/ui/MediaControlPanel.java | 66 ++++++- .../media/controls/ui/MediaViewController.kt | 5 + .../controls/ui/MediaControlPanelTest.kt | 166 ++++++++++-------- 6 files changed, 170 insertions(+), 77 deletions(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 082f3855affc7..1602189297734 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -64,6 +64,10 @@ 4 + + 380 + diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ff86c595b19b1..412e70f0df060 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1094,6 +1094,7 @@ 144dp + 380dp 16dp 16dp 88dp 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 9c7b48d2514d5..f08b97695202f 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 @@ constructor( 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 15d999ad2fe82..cb1f12cf412fa 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.Intent; 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.Process; 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { // 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 @@ public class MediaControlPanel { 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 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 7a1302c5baa2b..cd51d92062bc2 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 @@ constructor( /** 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 @@ constructor( ) } } + 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 55b57f1707742..543875dc31cf8 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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { .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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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())