diff options
8 files changed, 377 insertions, 157 deletions
diff --git a/api/current.txt b/api/current.txt index a533ff6784b7..d7f8ecd0f606 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6089,13 +6089,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); + ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); method public int describeContents(); - method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); - method public boolean supportsDarkText(); + method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); + method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable); + method public int getColorHints(); + method public android.graphics.Color getPrimaryColor(); + method public android.graphics.Color getSecondaryColor(); + method public android.graphics.Color getTertiaryColor(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; + field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 } public final class WallpaperInfo implements android.os.Parcelable { diff --git a/api/system-current.txt b/api/system-current.txt index fa1ef49c6162..85e917563602 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6299,13 +6299,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); + ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); method public int describeContents(); - method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); - method public boolean supportsDarkText(); + method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); + method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable); + method public int getColorHints(); + method public android.graphics.Color getPrimaryColor(); + method public android.graphics.Color getSecondaryColor(); + method public android.graphics.Color getTertiaryColor(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; + field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 } public final class WallpaperInfo implements android.os.Parcelable { diff --git a/api/test-current.txt b/api/test-current.txt index fe04b8b04b31..ed9071a2eec9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6110,13 +6110,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); - ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); + ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); method public int describeContents(); - method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); - method public boolean supportsDarkText(); + method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); + method public static android.app.WallpaperColors fromDrawable(android.graphics.drawable.Drawable); + method public int getColorHints(); + method public android.graphics.Color getPrimaryColor(); + method public android.graphics.Color getSecondaryColor(); + method public android.graphics.Color getTertiaryColor(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; + field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 } public final class WallpaperInfo implements android.os.Parcelable { diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java index b3c70a49f660..23e9ca5c32ae 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -16,63 +16,203 @@ package android.app; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; +import android.util.Size; -import android.util.Pair; +import com.android.internal.graphics.palette.Palette; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * A class containing information about the colors of a wallpaper. + * Provides information about the colors of a wallpaper. + * <p> + * This class contains two main components: + * <ul> + * <li>Named colors: Most visually representative colors of a wallpaper. Can be either + * {@link WallpaperColors#getPrimaryColor()}, {@link WallpaperColors#getSecondaryColor()} + * or {@link WallpaperColors#getTertiaryColor()}. + * </li> + * <li>Hints: How colors may affect other system components. Currently the only supported hint is + * {@link WallpaperColors#HINT_SUPPORTS_DARK_TEXT}, which specifies if dark text is preferred + * over the wallpaper.</li> + * </ul> */ public final class WallpaperColors implements Parcelable { - private static final float BRIGHT_LUMINANCE = 0.9f; - private final List<Pair<Color, Integer>> mColors; - private final boolean mSupportsDarkText; + /** + * Specifies that dark text is preferred over the current wallpaper for best presentation. + * <p> + * eg. A launcher may set its text color to black if this flag is specified. + */ + public static final int HINT_SUPPORTS_DARK_TEXT = 0x1; + + // Maximum size that a bitmap can have to keep our calculations sane + private static final int MAX_BITMAP_SIZE = 112; + + // Even though we have a maximum size, we'll mainly match bitmap sizes + // using the area instead. This way our comparisons are aspect ratio independent. + private static final int MAX_WALLPAPER_EXTRACTION_AREA = MAX_BITMAP_SIZE * MAX_BITMAP_SIZE; + + // When extracting the main colors, only consider colors + // present in at least MIN_COLOR_OCCURRENCE of the image + private static final float MIN_COLOR_OCCURRENCE = 0.05f; + + // Minimum mean luminosity that an image needs to have to support dark text + private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.9f; + // We also check if the image has dark pixels in it, + // to avoid bright images with some dark spots. + private static final float DARK_PIXEL_LUMINANCE = 0.45f; + private static final float MAX_DARK_AREA = 0.05f; + + private final ArrayList<Color> mMainColors; + private int mColorHints; public WallpaperColors(Parcel parcel) { - mColors = new ArrayList<>(); - int count = parcel.readInt(); - for (int i=0; i < count; i++) { - Color color = Color.valueOf(parcel.readInt()); - int weight = parcel.readInt(); - mColors.add(new Pair<>(color, weight)); + mMainColors = new ArrayList<>(); + final int count = parcel.readInt(); + for (int i = 0; i < count; i++) { + final int colorInt = parcel.readInt(); + Color color = Color.valueOf(colorInt); + mMainColors.add(color); } - mSupportsDarkText = parcel.readBoolean(); + mColorHints = parcel.readInt(); } /** - * Wallpaper color details containing a list of colors and their weights, - * as if it were an histogram. - * This list can be extracted from a bitmap by the Palette API. + * Constructs {@link WallpaperColors} from a drawable. + * <p> + * Main colors will be extracted from the drawable and hints will be calculated. * - * Dark text support will be calculated internally based on the histogram. + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @param drawable Source where to extract from. + */ + public static WallpaperColors fromDrawable(Drawable drawable) { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + + // Some drawables do not have intrinsic dimensions + if (width <= 0 || height <= 0) { + width = MAX_BITMAP_SIZE; + height = MAX_BITMAP_SIZE; + } + + Size optimalSize = calculateOptimalSize(width, height); + Bitmap bitmap = Bitmap.createBitmap(optimalSize.getWidth(), optimalSize.getHeight(), + Bitmap.Config.ARGB_8888); + final Canvas bmpCanvas = new Canvas(bitmap); + drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); + drawable.draw(bmpCanvas); + + final WallpaperColors colors = WallpaperColors.fromBitmap(bitmap); + bitmap.recycle(); + + return colors; + } + + /** + * Constructs {@link WallpaperColors} from a bitmap. + * <p> + * Main colors will be extracted from the bitmap and hints will be calculated. * - * @param colors list of pairs where each pair contains a color - * and number of occurrences/influence. + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @param bitmap Source where to extract from. */ - public WallpaperColors(List<Pair<Color, Integer>> colors) { - this(colors, calculateDarkTextSupport(colors)); + public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) { + if (bitmap == null) { + throw new IllegalArgumentException("Bitmap can't be null"); + } + + final int bitmapArea = bitmap.getWidth() * bitmap.getHeight(); + if (bitmapArea > MAX_WALLPAPER_EXTRACTION_AREA) { + Size optimalSize = calculateOptimalSize(bitmap.getWidth(), bitmap.getHeight()); + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, optimalSize.getWidth(), + optimalSize.getHeight(), true /* filter */); + bitmap.recycle(); + bitmap = scaledBitmap; + } + + final Palette palette = Palette + .from(bitmap) + .clearFilters() + .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA) + .generate(); + + // Remove insignificant colors and sort swatches by population + final ArrayList<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches()); + final float minColorArea = bitmap.getWidth() * bitmap.getHeight() * MIN_COLOR_OCCURRENCE; + swatches.removeIf(s -> s.getPopulation() < minColorArea); + swatches.sort((a, b) -> b.getPopulation() - a.getPopulation()); + + final int swatchesSize = swatches.size(); + Color primary = null, secondary = null, tertiary = null; + + swatchLoop: + for (int i = 0; i < swatchesSize; i++) { + Color color = Color.valueOf(swatches.get(i).getRgb()); + switch (i) { + case 0: + primary = color; + break; + case 1: + secondary = color; + break; + case 2: + tertiary = color; + break; + default: + // out of bounds + break swatchLoop; + } + } + + int hints = 0; + if (calculateDarkTextSupport(bitmap)) { + hints |= HINT_SUPPORTS_DARK_TEXT; + } + return new WallpaperColors(primary, secondary, tertiary, hints); } /** - * Wallpaper color details containing a list of colors and their weights, - * as if it were an histogram. - * Explicit dark text support. + * Constructs a new object from three colors, where hints can be specified. * - * @param colors list of pairs where each pair contains a color - * and number of occurrences/influence. - * @param supportsDarkText can have dark text on top or not + * @param primaryColor Primary color. + * @param secondaryColor Secondary color. + * @param tertiaryColor Tertiary color. + * @param colorHints A combination of WallpaperColor hints. + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @see WallpaperColors#fromBitmap(Bitmap) + * @see WallpaperColors#fromDrawable(Drawable) */ - public WallpaperColors(List<Pair<Color, Integer>> colors, boolean supportsDarkText) { - if (colors == null) - colors = new ArrayList<>(); - mColors = colors; - mSupportsDarkText = supportsDarkText; + public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor, + @Nullable Color tertiaryColor, int colorHints) { + + if (primaryColor == null) { + throw new IllegalArgumentException("Primary color should never be null."); + } + + mMainColors = new ArrayList<>(3); + mMainColors.add(primaryColor); + if (secondaryColor != null) { + mMainColors.add(secondaryColor); + } + if (tertiaryColor != null) { + if (secondaryColor == null) { + throw new IllegalArgumentException("tertiaryColor can't be specified when " + + "secondaryColor is null"); + } + mMainColors.add(tertiaryColor); + } + + mColorHints = colorHints; } public static final Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() { @@ -94,21 +234,53 @@ public final class WallpaperColors implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - int count = mColors.size(); + List<Color> mainColors = getMainColors(); + int count = mainColors.size(); dest.writeInt(count); - for (Pair<Color, Integer> color : mColors) { - dest.writeInt(color.first.toArgb()); - dest.writeInt(color.second); + for (int i = 0; i < count; i++) { + Color color = mainColors.get(i); + dest.writeInt(color.toArgb()); } - dest.writeBoolean(mSupportsDarkText); + dest.writeInt(mColorHints); + } + + /** + * Gets the most visually representative color of the wallpaper. + * "Visually representative" means easily noticeable in the image, + * probably happening at high frequency. + * + * @return A color. + */ + public @NonNull Color getPrimaryColor() { + return mMainColors.get(0); + } + + /** + * Gets the second most preeminent color of the wallpaper. Can be null. + * + * @return A color, may be null. + */ + public @Nullable Color getSecondaryColor() { + return mMainColors.size() < 2 ? null : mMainColors.get(1); + } + + /** + * Gets the third most preeminent color of the wallpaper. Can be null. + * + * @return A color, may be null. + */ + public @Nullable Color getTertiaryColor() { + return mMainColors.size() < 3 ? null : mMainColors.get(2); } /** - * List of colors with their occurrences. The bigger the int, the more relevant the color. - * @return list of colors paired with their weights. + * List of most preeminent colors, sorted by importance. + * + * @return List of colors. + * @hide */ - public List<Pair<Color, Integer>> getColors() { - return mColors; + public @NonNull List<Color> getMainColors() { + return Collections.unmodifiableList(mMainColors); } @Override @@ -118,38 +290,91 @@ public final class WallpaperColors implements Parcelable { } WallpaperColors other = (WallpaperColors) o; - return mColors.equals(other.mColors) && mSupportsDarkText == other.mSupportsDarkText; + return mMainColors.equals(other.mMainColors) + && mColorHints == other.mColorHints; } @Override public int hashCode() { - return 31 * mColors.hashCode() + (mSupportsDarkText ? 1 : 0); + return 31 * mMainColors.hashCode() + mColorHints; } /** - * Whether or not dark text is legible on top of this wallpaper. + * Combination of WallpaperColor hints. * - * @return true if dark text is supported + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @return True if dark text is supported. + */ + public int getColorHints() { + return mColorHints; + } + + /** + * @param colorHints Combination of WallpaperColors hints. + * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT + * @hide */ - public boolean supportsDarkText() { - return mSupportsDarkText; + public void setColorHints(int colorHints) { + mColorHints = colorHints; } - private static boolean calculateDarkTextSupport(List<Pair<Color, Integer>> colors) { - if (colors == null) { + /** + * Checks if image is bright and clean enough to support light text. + * + * @param source What to read. + * @return Whether image supports dark text or not. + */ + private static boolean calculateDarkTextSupport(Bitmap source) { + if (source == null) { return false; } - Pair<Color, Integer> mainColor = null; + int[] pixels = new int[source.getWidth() * source.getHeight()]; + double totalLuminance = 0; + final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA); + int darkPixels = 0; + source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */, + source.getWidth(), source.getHeight()); - for (Pair<Color, Integer> color : colors) { - if (mainColor == null) { - mainColor = color; - } else if (color.second > mainColor.second) { - mainColor = color; + // This bitmap was already resized to fit the maximum allowed area. + // Let's just loop through the pixels, no sweat! + for (int i = 0; i < pixels.length; i++) { + final float luminance = Color.luminance(pixels[i]); + final int alpha = Color.alpha(pixels[i]); + + // Make sure we don't have a dark pixel mass that will + // make text illegible. + if (luminance < DARK_PIXEL_LUMINANCE && alpha != 0) { + darkPixels++; + if (darkPixels > maxDarkPixels) { + return false; + } } + + totalLuminance += luminance; + } + return totalLuminance / pixels.length > BRIGHT_IMAGE_MEAN_LUMINANCE; + } + + private static Size calculateOptimalSize(int width, int height) { + // Calculate how big the bitmap needs to be. + // This avoids unnecessary processing and allocation inside Palette. + final int requestedArea = width * height; + double scale = 1; + if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) { + scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea); + } + int newWidth = (int) (width * scale); + int newHeight = (int) (height * scale); + // Dealing with edge cases of the drawable being too wide or too tall. + // Width or height would end up being 0, in this case we'll set it to 1. + if (newWidth == 0) { + newWidth = 1; } - return mainColor != null && - mainColor.first.luminance() > BRIGHT_LUMINANCE; + if (newHeight == 0) { + newHeight = 1; + } + + return new Size(newWidth, newHeight); } } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index da6b1f5f460e..e4d3142ccefe 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -17,25 +17,19 @@ package android.service.wallpaper; import android.annotation.Nullable; -import android.app.WallpaperColors; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.util.MergedConfiguration; -import android.view.WindowInsets; - -import com.android.internal.R; -import com.android.internal.os.HandlerCaller; -import com.android.internal.view.BaseIWindow; -import com.android.internal.view.BaseSurfaceHolder; - import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; +import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Bundle; @@ -44,6 +38,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.util.MergedConfiguration; import android.view.Display; import android.view.Gravity; import android.view.IWindowSession; @@ -55,9 +50,14 @@ import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import com.android.internal.os.HandlerCaller; +import com.android.internal.view.BaseIWindow; +import com.android.internal.view.BaseSurfaceHolder; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -563,8 +563,13 @@ public abstract class WallpaperService extends Service { * Notifies the system about what colors the wallpaper is using. * You might return null if no color information is available at the moment. In that case * you might want to call {@link #invalidateColors()} in a near future. + * <p> + * The simplest way of creating A {@link android.app.WallpaperColors} object is by using + * {@link android.app.WallpaperColors#fromBitmap(Bitmap)} or + * {@link android.app.WallpaperColors#fromDrawable(Drawable)}, but you can also specify + * your main colors and dark text support explicitly using one of the constructors. * - * @return List of wallpaper colors and their weights. + * @return Wallpaper colors. * @hide */ public @Nullable WallpaperColors onComputeWallpaperColors() { diff --git a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java index 5b4b3ed77f3a..81bc83134b21 100644 --- a/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java +++ b/packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java @@ -29,6 +29,8 @@ import android.util.Range; import com.google.android.colorextraction.ColorExtractor.GradientColors; +import java.util.List; + /** * Implementation of tonal color extraction */ @@ -40,9 +42,6 @@ public class Tonal implements ExtractionType { private static final float FIT_WEIGHT_S = 1.0f; private static final float FIT_WEIGHT_L = 10.0f; - // When extracting the main color, only consider colors - // present in at least MIN_COLOR_OCCURRENCE of the image - private static final float MIN_COLOR_OCCURRENCE = 0.1f; private static final boolean DEBUG = true; // Temporary variable to avoid allocations @@ -61,7 +60,10 @@ public class Tonal implements ExtractionType { @NonNull GradientColors outColorsNormal, @NonNull GradientColors outColorsDark, @NonNull GradientColors outColorsExtraDark) { - if (inWallpaperColors.getColors().size() == 0) { + final List<Color> mainColors = inWallpaperColors.getMainColors(); + final int mainColorsSize = mainColors.size(); + + if (mainColorsSize == 0) { return false; } // Tonal is not really a sort, it takes a color from the extracted @@ -69,30 +71,18 @@ public class Tonal implements ExtractionType { // palettes. The best fit is tweaked to be closer to the source color // and replaces the original palette - // First find the most representative color in the image - populationSort(inWallpaperColors); - // Calculate total - int total = 0; - for (Pair<Color, Integer> weightedColor : inWallpaperColors.getColors()) { - total += weightedColor.second; - } - - // Get bright colors that occur often enough in this image - Pair<Color, Integer> bestColor = null; - float[] hsl = new float[3]; - for (Pair<Color, Integer> weightedColor : inWallpaperColors.getColors()) { - float colorOccurrence = weightedColor.second / (float) total; - if (colorOccurrence < MIN_COLOR_OCCURRENCE) { - break; - } - - int colorValue = weightedColor.first.toArgb(); + // Get the most preeminent, non-blacklisted color. + Color bestColor = null; + final float[] hsl = new float[3]; + for (int i = 0; i < mainColorsSize; i++) { + final Color color = mainColors.get(i); + final int colorValue = color.toArgb(); ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue), hsl); // Stop when we find a color that meets our criteria if (!isBlacklisted(hsl)) { - bestColor = weightedColor; + bestColor = color; break; } } @@ -102,7 +92,7 @@ public class Tonal implements ExtractionType { return false; } - int colorValue = bestColor.first.toArgb(); + int colorValue = bestColor.toArgb(); ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue), hsl); @@ -212,10 +202,6 @@ public class Tonal implements ExtractionType { return false; } - private static void populationSort(@NonNull WallpaperColors wallpaperColors) { - wallpaperColors.getColors().sort((a, b) -> b.second - a.second); - } - /** * Offsets all colors by a delta, clamping values that go beyond what's * supported on the color space. @@ -269,7 +255,9 @@ public class Tonal implements ExtractionType { TonalPalette best = null; float error = Float.POSITIVE_INFINITY; - for (TonalPalette candidate : TONAL_PALETTES) { + for (int i = 0; i < TONAL_PALETTES.length; i++) { + final TonalPalette candidate = TONAL_PALETTES[i]; + if (h >= candidate.minHue && h <= candidate.maxHue) { best = candidate; break; diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java index 7ed1e2c966d3..1ed5f565117f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java +++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java @@ -96,9 +96,7 @@ public class SysuiColorExtractorTests extends SysuiTestCase { private void simulateEvent(SysuiColorExtractor extractor) { // Let's fake a color event - List<Pair<Color, Integer>> dummyColors = new ArrayList<>(); - dummyColors.add(new Pair<>(Color.valueOf(Color.BLACK), 1)); - extractor.onColorsChanged(new WallpaperColors(dummyColors), + extractor.onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), null, null, 0), WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK); } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 71c92b88947d..929f28da66f3 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -52,7 +52,6 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; @@ -92,7 +91,6 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.content.PackageMonitor; -import com.android.internal.graphics.palette.Palette; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; @@ -168,7 +166,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig"; static final String WALLPAPER_LOCK_CROP = "wallpaper_lock"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; - static final int MAX_WALLPAPER_EXTRACTION_AREA = 112 * 112; // All the various per-user state files we need to be aware of static final String[] sPerUserFiles = new String[] { @@ -397,8 +394,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { // It prevents color extraction on big bitmaps. int wallpaperId = -1; + boolean imageWallpaper = false; synchronized (mLock) { - final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent) + imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent) || wallpaper.wallpaperComponent == null; if (imageWallpaper) { if (wallpaper.cropFile != null && wallpaper.cropFile.exists()) { @@ -422,45 +420,33 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaperId = wallpaper.wallpaperId; } - Bitmap bitmap = null; + WallpaperColors colors = null; if (cropFile != null) { - bitmap = BitmapFactory.decodeFile(cropFile); + Bitmap bitmap = BitmapFactory.decodeFile(cropFile); + colors = WallpaperColors.fromBitmap(bitmap); + bitmap.recycle(); } else if (thumbnail != null) { - // Calculate how big the bitmap needs to be. - // This avoids unnecessary processing and allocation inside Palette. - final int requestedArea = thumbnail.getIntrinsicWidth() * - thumbnail.getIntrinsicHeight(); - double scale = 1; - if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) { - scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea); - } - bitmap = Bitmap.createBitmap((int) (thumbnail.getIntrinsicWidth() * scale), - (int) (thumbnail.getIntrinsicHeight() * scale), Bitmap.Config.ARGB_8888); - final Canvas bmpCanvas = new Canvas(bitmap); - thumbnail.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); - thumbnail.draw(bmpCanvas); - } - - if (bitmap == null) { + colors = WallpaperColors.fromDrawable(thumbnail); + } + + if (colors == null) { Slog.w(TAG, "Cannot extract colors because wallpaper could not be read."); return; } - Palette palette = Palette - .from(bitmap) - .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA) - .generate(); - bitmap.recycle(); - - final List<Pair<Color, Integer>> colors = new ArrayList<>(); - for (Palette.Swatch swatch : palette.getSwatches()) { - colors.add(new Pair<>(Color.valueOf(swatch.getRgb()), - swatch.getPopulation())); + // Even though we can extract colors from live wallpaper thumbnails, + // it's risky to assume that it might support dark text on top of it: + // • Thumbnail might not be accurate. + // • Colors might change over time. + if (!imageWallpaper) { + int colorHints = colors.getColorHints(); + colorHints &= ~WallpaperColors.HINT_SUPPORTS_DARK_TEXT; + colors.setColorHints(colorHints); } synchronized (mLock) { if (wallpaper.wallpaperId == wallpaperId) { - wallpaper.primaryColors = new WallpaperColors(colors); + wallpaper.primaryColors = colors; } else { Slog.w(TAG, "Not setting primary colors since wallpaper changed"); } @@ -2224,17 +2210,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } if (wallpaper.primaryColors != null) { - int colorsCount = wallpaper.primaryColors.getColors().size(); + int colorsCount = wallpaper.primaryColors.getMainColors().size(); out.attribute(null, "colorsCount", Integer.toString(colorsCount)); if (colorsCount > 0) { for (int i = 0; i < colorsCount; i++) { - Pair<Color, Integer> wc = wallpaper.primaryColors.getColors().get(i); - out.attribute(null, "colorValue"+i, Integer.toString(wc.first.toArgb())); - out.attribute(null, "colorWeight"+i, Integer.toString(wc.second)); + final Color wc = wallpaper.primaryColors.getMainColors().get(i); + out.attribute(null, "colorValue"+i, Integer.toString(wc.toArgb())); } } out.attribute(null, "supportsDarkText", - Integer.toString(wallpaper.primaryColors.supportsDarkText() ? 1 : 0)); + Integer.toString(wallpaper.primaryColors.getColorHints())); } out.attribute(null, "name", wallpaper.name); @@ -2469,15 +2454,22 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0); int colorsCount = getAttributeInt(parser, "colorsCount", 0); if (colorsCount > 0) { - List<Pair<Color, Integer>> colors = new ArrayList<>(); + Color primary = null, secondary = null, tertiary = null; + final List<Color> colors = new ArrayList<>(); for (int i = 0; i < colorsCount; i++) { - colors.add(new Pair<>( - Color.valueOf(getAttributeInt(parser, "colorValue"+i, 0)), - getAttributeInt(parser, "colorWeight"+i, 0) - )); + Color color = Color.valueOf(getAttributeInt(parser, "colorValue" + i, 0)); + if (i == 0) { + primary = color; + } else if (i == 1) { + secondary = color; + } else if (i == 2) { + tertiary = color; + } else { + break; + } } - boolean dark = getAttributeInt(parser, "supportsDarkText", 0) == 1; - wallpaper.primaryColors = new WallpaperColors(colors, dark); + int colorHints = getAttributeInt(parser, "colorHints", 0); + wallpaper.primaryColors = new WallpaperColors(primary, secondary, tertiary, colorHints); } wallpaper.name = parser.getAttributeValue(null, "name"); wallpaper.allowBackup = "true".equals(parser.getAttributeValue(null, "backup")); |