Merge "Merge "Compute background color." into qt-dev am: a3fb334c6f" into qt-dev-plus-aosp

This commit is contained in:
Android Build Merger (Role)
2019-05-03 19:35:26 +00:00
committed by Android (Google) Code Review
4 changed files with 197 additions and 50 deletions

View File

@@ -16,7 +16,6 @@
package com.android.systemui.statusbar
import android.annotation.ColorInt
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
@@ -28,6 +27,7 @@ import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import android.util.MathUtils
import com.android.internal.graphics.ColorUtils
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
import javax.inject.Inject
import javax.inject.Singleton
@@ -42,7 +42,7 @@ class MediaArtworkProcessor @Inject constructor() {
private val mTmpSize = Point()
private var mArtworkCache: Bitmap? = null
fun processArtwork(context: Context, artwork: Bitmap, @ColorInt color: Int): Bitmap {
fun processArtwork(context: Context, artwork: Bitmap): Bitmap {
if (mArtworkCache != null) {
return mArtworkCache!!
}
@@ -71,13 +71,15 @@ class MediaArtworkProcessor @Inject constructor() {
blur.forEach(output)
output.copyTo(outBitmap)
val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork)
input.destroy()
output.destroy()
inBitmap.recycle()
blur.destroy()
val canvas = Canvas(outBitmap)
canvas.drawColor(ColorUtils.setAlphaComponent(color, COLOR_ALPHA))
canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA))
return outBitmap
}

View File

@@ -21,11 +21,11 @@ import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_AR
import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -35,11 +35,13 @@ import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.util.ArraySet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
@@ -64,8 +66,10 @@ import com.android.systemui.statusbar.policy.KeyguardMonitor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -108,6 +112,7 @@ public class NotificationMediaManager implements Dumpable {
private final MediaSessionManager mMediaSessionManager;
private final ArrayList<MediaListener> mMediaListeners;
private final MediaArtworkProcessor mMediaArtworkProcessor;
private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
protected NotificationPresenter mPresenter;
private MediaController mMediaController;
@@ -449,28 +454,37 @@ public class NotificationMediaManager implements Dumpable {
+ " state=" + mStatusBarStateController.getState());
}
Drawable artworkDrawable = null;
Bitmap artworkBitmap = null;
if (mediaMetadata != null) {
Bitmap artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
if (artworkBitmap == null) {
artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
// might still be null
}
if (artworkBitmap != null) {
int notificationColor;
synchronized (mEntryManager.getNotificationData()) {
NotificationEntry entry = mEntryManager.getNotificationData()
.get(mMediaNotificationKey);
if (entry == null || entry.getRow() == null) {
notificationColor = Color.TRANSPARENT;
} else {
notificationColor = entry.getRow().calculateBgColor();
}
}
Bitmap bmp = mMediaArtworkProcessor.processArtwork(mContext, artworkBitmap,
notificationColor);
artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
}
// Process artwork on a background thread and send the resulting bitmap to
// finishUpdateMediaMetaData.
if (metaDataChanged) {
for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {
task.cancel(true);
}
mProcessArtworkTasks.clear();
}
if (artworkBitmap != null) {
mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
allowEnterAnimation).execute(artworkBitmap));
} else {
finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);
}
Trace.endSection();
}
private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,
@Nullable Bitmap bmp) {
Drawable artworkDrawable = null;
if (bmp != null) {
artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
}
boolean allowWhenShade = false;
if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
@@ -598,7 +612,6 @@ public class NotificationMediaManager implements Dumpable {
}
}
}
Trace.endSection();
}
public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
@@ -629,6 +642,61 @@ public class NotificationMediaManager implements Dumpable {
}
};
private Bitmap processArtwork(Bitmap artwork) {
return mMediaArtworkProcessor.processArtwork(mContext, artwork);
}
@MainThread
private void removeTask(AsyncTask<?, ?, ?> task) {
mProcessArtworkTasks.remove(task);
}
/**
* {@link AsyncTask} to prepare album art for use as backdrop on lock screen.
*/
private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> {
private final WeakReference<NotificationMediaManager> mManagerRef;
private final boolean mMetaDataChanged;
private final boolean mAllowEnterAnimation;
ProcessArtworkTask(NotificationMediaManager manager, boolean changed,
boolean allowAnimation) {
mManagerRef = new WeakReference<>(manager);
mMetaDataChanged = changed;
mAllowEnterAnimation = allowAnimation;
}
@Override
protected Bitmap doInBackground(Bitmap... bitmaps) {
NotificationMediaManager manager = mManagerRef.get();
if (manager == null || bitmaps.length == 0 || isCancelled()) {
return null;
}
return manager.processArtwork(bitmaps[0]);
}
@Override
protected void onPostExecute(@Nullable Bitmap result) {
NotificationMediaManager manager = mManagerRef.get();
if (manager != null && !isCancelled()) {
manager.removeTask(this);
manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result);
}
}
@Override
protected void onCancelled(Bitmap result) {
if (result != null) {
result.recycle();
}
NotificationMediaManager manager = mManagerRef.get();
if (manager != null) {
manager.removeTask(this);
}
}
}
public interface MediaListener {
void onMetadataChanged(MediaMetadata metadata);
}

View File

@@ -69,8 +69,7 @@ public class MediaNotificationProcessor {
private static final int RESIZE_BITMAP_AREA = 150 * 150;
private final ImageGradientColorizer mColorizer;
private final Context mContext;
private float[] mFilteredBackgroundHsl = null;
private Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl);
private final Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl);
/**
* The context of the notification. This is the app context of the package posting the
@@ -121,23 +120,21 @@ public class MediaNotificationProcessor {
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
// for the background we only take the left side of the image to ensure
// a smooth transition
Palette.Builder paletteBuilder = Palette.from(bitmap)
.setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight())
.clearFilters() // we want all colors, red / white / black ones too!
.resizeBitmapArea(RESIZE_BITMAP_AREA);
Palette.Builder paletteBuilder = generateArtworkPaletteBuilder(bitmap);
Palette palette = paletteBuilder.generate();
backgroundColor = findBackgroundColorAndFilter(palette);
Palette.Swatch backgroundSwatch = findBackgroundSwatch(palette);
backgroundColor = backgroundSwatch.getRgb();
// we want most of the full region again, slightly shifted to the right
float textColorStartWidthFraction = 0.4f;
paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0,
bitmap.getWidth(),
bitmap.getHeight());
if (mFilteredBackgroundHsl != null) {
// We're not filtering on white or black
if (!isWhiteOrBlack(backgroundSwatch.getHsl())) {
final float backgroundHue = backgroundSwatch.getHsl()[0];
paletteBuilder.addFilter((rgb, hsl) -> {
// at least 10 degrees hue difference
float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]);
float diff = Math.abs(hsl[0] - backgroundHue);
return diff > 10 && diff < 350;
});
}
@@ -244,18 +241,31 @@ public class MediaNotificationProcessor {
&& (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
}
private int findBackgroundColorAndFilter(Palette palette) {
/**
* Finds an appropriate background swatch from media artwork.
*
* @param artwork Media artwork
* @return Swatch that should be used as the background of the media notification.
*/
public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) {
return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate());
}
/**
* Finds an appropriate background swatch from the palette of media artwork.
*
* @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
* @return Swatch that should be used as the background of the media notification.
*/
private static Palette.Swatch findBackgroundSwatch(Palette palette) {
// by default we use the dominant palette
Palette.Swatch dominantSwatch = palette.getDominantSwatch();
if (dominantSwatch == null) {
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return Color.WHITE;
return new Palette.Swatch(Color.WHITE, 100);
}
if (!isWhiteOrBlack(dominantSwatch.getHsl())) {
mFilteredBackgroundHsl = dominantSwatch.getHsl();
return dominantSwatch.getRgb();
return dominantSwatch;
}
// Oh well, we selected black or white. Lets look at the second color!
List<Palette.Swatch> swatches = palette.getSwatches();
@@ -270,38 +280,51 @@ public class MediaNotificationProcessor {
}
}
if (second == null) {
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return dominantSwatch.getRgb();
return dominantSwatch;
}
if (dominantSwatch.getPopulation() / highestNonWhitePopulation
> POPULATION_FRACTION_FOR_WHITE_OR_BLACK) {
// The dominant swatch is very dominant, lets take it!
// We're not filtering on white or black
mFilteredBackgroundHsl = null;
return dominantSwatch.getRgb();
return dominantSwatch;
} else {
mFilteredBackgroundHsl = second.getHsl();
return second.getRgb();
return second;
}
}
private boolean isWhiteOrBlack(float[] hsl) {
return isBlack(hsl) || isWhite(hsl);
/**
* Generate a palette builder for media artwork.
*
* For producing a smooth background transition, the palette is extracted from only the left
* side of the artwork.
*
* @param artwork Media artwork
* @return Builder that generates the {@link Palette} for the media artwork.
*/
private static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) {
// for the background we only take the left side of the image to ensure
// a smooth transition
return Palette.from(artwork)
.setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight())
.clearFilters() // we want all colors, red / white / black ones too!
.resizeBitmapArea(RESIZE_BITMAP_AREA);
}
private static boolean isWhiteOrBlack(float[] hsl) {
return isBlack(hsl) || isWhite(hsl);
}
/**
* @return true if the color represents a color which is close to black.
*/
private boolean isBlack(float[] hslColor) {
private static boolean isBlack(float[] hslColor) {
return hslColor[2] <= BLACK_MAX_LIGHTNESS;
}
/**
* @return true if the color represents a color which is close to white.
*/
private boolean isWhite(float[] hslColor) {
private static boolean isWhite(float[] hslColor) {
return hslColor[2] >= WHITE_MIN_LIGHTNESS;
}
}

View File

@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNotSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -24,17 +26,22 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import android.annotation.Nullable;
import android.app.Notification;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.test.suitebuilder.annotation.SmallTest;
import android.widget.RemoteViews;
import androidx.palette.graphics.Palette;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,9 +50,18 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class MediaNotificationProcessorTest extends SysuiTestCase {
private static final int BITMAP_WIDTH = 10;
private static final int BITMAP_HEIGHT = 10;
/**
* Color tolerance is borrowed from the AndroidX test utilities for Palette.
*/
private static final int COLOR_TOLERANCE = 8;
private MediaNotificationProcessor mProcessor;
private Bitmap mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
private ImageGradientColorizer mColorizer;
@Nullable private Bitmap mArtwork;
@Before
public void setUp() {
@@ -53,6 +69,14 @@ public class MediaNotificationProcessorTest extends SysuiTestCase {
mProcessor = new MediaNotificationProcessor(getContext(), getContext(), mColorizer);
}
@After
public void tearDown() {
if (mArtwork != null) {
mArtwork.recycle();
mArtwork = null;
}
}
@Test
public void testColorizedWithLargeIcon() {
Notification.Builder builder = new Notification.Builder(getContext()).setSmallIcon(
@@ -100,6 +124,36 @@ public class MediaNotificationProcessorTest extends SysuiTestCase {
assertNotSame(contentView, remoteViews);
}
@Test
public void findBackgroundSwatch_white() {
// Given artwork that is completely white.
mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mArtwork);
canvas.drawColor(Color.WHITE);
// WHEN the background swatch is computed
Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
// THEN the swatch color is white
assertCloseColors(swatch.getRgb(), Color.WHITE);
}
@Test
public void findBackgroundSwatch_red() {
// Given artwork that is completely red.
mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mArtwork);
canvas.drawColor(Color.RED);
// WHEN the background swatch is computed
Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
// THEN the swatch color is red
assertCloseColors(swatch.getRgb(), Color.RED);
}
static void assertCloseColors(int expected, int actual) {
assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual));
assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
}
public static class TestableColorizer extends ImageGradientColorizer {
private final Bitmap mBitmap;