Intensify recommendation gradient and scale the cover.

Improve the view of new recommendation card by using a new gradient and
scale the cover to 125%. Also, fix the change in gradient color in media
players and recommendation items by using drawable mutations in order
not to change the original state that is shared among drawables of the
same resource.

Bug: 271163892
Fixes: 268525377
Test: atest MediaControlPanelTest
Test: checked the new view of recommendation card.
Change-Id: Ie3619ea4ebbfcea082eb87d3e461347b7bcc4f8d
This commit is contained in:
Michael Mikhail
2023-02-28 23:18:18 +00:00
parent 7f650e2680
commit e0df7bc634
4 changed files with 161 additions and 17 deletions

View File

@@ -0,0 +1,25 @@
<?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
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- gradient from 25% in the center to 100% at edges -->
<gradient
android:type="radial"
android:gradientRadius="40%p"
android:startColor="#AE000000"
android:endColor="#00000000" />
</shape>

View File

@@ -22,9 +22,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:translationZ="0dp"
android:scaleType="centerCrop"
android:scaleType="matrix"
android:adjustViewBounds="true"
android:clipToOutline="true"
android:layerType="hardware"
android:background="@drawable/bg_smartspace_media_item"/>
<!-- App icon -->

View File

@@ -31,12 +31,15 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -62,7 +65,6 @@ 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;
@@ -145,6 +147,12 @@ public class MediaControlPanel {
private static final int SMARTSPACE_CARD_CLICK_EVENT = 760;
protected static final int SMARTSPACE_CARD_DISMISS_EVENT = 761;
private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
private static final float MEDIA_SCRIM_START_ALPHA = 0.25f;
private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 0.9f;
private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
// Buttons to show in small player when using semantic actions
@@ -776,7 +784,7 @@ public class MediaControlPanel {
WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
artwork = addGradientToPlayerAlbum(artworkIcon, mutableColorScheme, width, height);
isArtworkBound = true;
} else {
// If there's no artwork, use colors from the app icon
@@ -866,8 +874,9 @@ public class MediaControlPanel {
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();
int width = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_rec_album_width);
int height = mContext.getResources().getDimensionPixelSize(
R.dimen.qs_media_rec_album_height_expanded);
mBackgroundExecutor.execute(() -> {
// Album art
@@ -877,7 +886,8 @@ public class MediaControlPanel {
WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
artwork = addGradientToRecommendationAlbum(artworkIcon, mutableColorScheme, width,
height);
} else {
artwork = new ColorDrawable(Color.TRANSPARENT);
}
@@ -886,6 +896,11 @@ public class MediaControlPanel {
// Bind the artwork drawable to media cover.
ImageView mediaCover =
mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
// Rescale media cover
Matrix coverMatrix = new Matrix(mediaCover.getImageMatrix());
coverMatrix.postScale(REC_MEDIA_COVER_SCALE_FACTOR, REC_MEDIA_COVER_SCALE_FACTOR,
0.5f * width, 0.5f * height);
mediaCover.setImageMatrix(coverMatrix);
mediaCover.setImageDrawable(artwork);
// Set up the app icon.
@@ -907,7 +922,8 @@ public class MediaControlPanel {
// 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) {
@VisibleForTesting
protected WallpaperColors getWallpaperColor(Icon artworkIcon) {
if (artworkIcon != null) {
if (artworkIcon.getType() == Icon.TYPE_BITMAP
|| artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
@@ -925,22 +941,40 @@ public class MediaControlPanel {
return null;
}
private LayerDrawable addGradientToIcon(
Icon artworkIcon,
ColorScheme mutableColorScheme,
int width,
int height
) {
@VisibleForTesting
protected LayerDrawable addGradientToPlayerAlbum(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);
GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
R.drawable.qs_media_scrim).mutate();
return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
MEDIA_SCRIM_START_ALPHA, MEDIA_PLAYER_SCRIM_END_ALPHA);
}
@VisibleForTesting
protected LayerDrawable addGradientToRecommendationAlbum(Icon artworkIcon,
ColorScheme mutableColorScheme, int width, int height) {
// First try scaling rec card using bitmap drawable.
// If returns null, set drawable bounds.
Drawable albumArt = getScaledRecommendationCover(artworkIcon, width, height);
if (albumArt == null) {
albumArt = getScaledBackground(artworkIcon, width, height);
}
GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
R.drawable.qs_media_rec_scrim).mutate();
return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
MEDIA_REC_SCRIM_START_ALPHA, MEDIA_REC_SCRIM_END_ALPHA);
}
private LayerDrawable setupGradientColorOnDrawable(Drawable albumArt, GradientDrawable gradient,
ColorScheme mutableColorScheme, float startAlpha, float endAlpha) {
gradient.setColors(new int[] {
ColorUtilKt.getColorWithAlpha(
MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
0.25f),
startAlpha),
ColorUtilKt.getColorWithAlpha(
MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
0.9f),
endAlpha),
});
return new LayerDrawable(new Drawable[] { albumArt, gradient });
}
@@ -1585,6 +1619,29 @@ public class MediaControlPanel {
return drawable;
}
/**
* Scale artwork to fill the background of media covers in recommendation card.
*/
@UiThread
private Drawable getScaledRecommendationCover(Icon artworkIcon, int width, int height) {
if (width == 0 || height == 0) {
return null;
}
if (artworkIcon != null) {
Bitmap bitmap;
if (artworkIcon.getType() == Icon.TYPE_BITMAP
|| artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
Bitmap artworkBitmap = artworkIcon.getBitmap();
if (artworkBitmap != null) {
bitmap = Bitmap.createScaledBitmap(artworkIcon.getBitmap(), width,
height, false);
return new BitmapDrawable(mContext.getResources(), bitmap);
}
}
}
return null;
}
/**
* Get the current media controller
*

View File

@@ -27,6 +27,7 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
@@ -78,6 +79,8 @@ 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.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -214,6 +217,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@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
private lateinit var coverItem2: ImageView
private lateinit var coverItem3: ImageView
@@ -699,6 +703,33 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(albumView, times(3)).setImageDrawable(any(Drawable::class.java))
}
@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 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 greenWallpaperColor = player.getWallpaperColor(greenArt)
val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
// Add gradient to both icons.
val redArtwork = player.addGradientToPlayerAlbum(redArt, redColorScheme, 10, 10)
val greenArtwork = player.addGradientToPlayerAlbum(greenArt, greenColorScheme, 10, 10)
// They should have different constant states as they have different gradient color.
assertThat(redArtwork.getDrawable(1).constantState)
.isNotEqualTo(greenArtwork.getDrawable(1).constantState)
}
@Test
fun bind_seekBarDisabled_hasActions_seekBarVisibilityIsSetToInvisible() {
useRealConstraintSets()
@@ -2092,6 +2123,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.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)
@@ -2127,6 +2159,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(recCardTitle).setTextColor(any<Int>())
verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
verify(coverItem, times(3)).imageMatrix = any()
}
@Test
@@ -2188,6 +2221,34 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(recSubtitleMock3).visibility = View.VISIBLE
}
@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 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 greenWallpaperColor = player.getWallpaperColor(greenArt)
val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
// Add gradient to both icons.
val redArtwork = player.addGradientToRecommendationAlbum(redArt, redColorScheme, 10, 10)
val greenArtwork =
player.addGradientToRecommendationAlbum(greenArt, greenColorScheme, 10, 10)
// They should have different constant states as they have different gradient color.
assertThat(redArtwork.getDrawable(1).constantState)
.isNotEqualTo(greenArtwork.getDrawable(1).constantState)
}
@Test
fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)