Merge "Adjust the number of recommendations according to display size" into udc-dev

This commit is contained in:
Michael Mikhail
2023-03-22 18:31:43 +00:00
committed by Android (Google) Code Review
6 changed files with 170 additions and 77 deletions

View File

@@ -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 -->

View File

@@ -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>

View File

@@ -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

View File

@@ -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(

View File

@@ -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()
}
}
}
}

View File

@@ -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())