diff options
6 files changed, 170 insertions, 77 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 082f3855affc..160218929773 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 ff86c595b19b..412e70f0df06 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 9c7b48d2514d..f08b97695202 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 15d999ad2fe8..cb1f12cf412f 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<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 7a1302c5baa2..cd51d92062bc 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 55b57f170774..543875dc31cf 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()) |