summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt12
-rw-r--r--api/system-current.txt12
-rw-r--r--api/test-current.txt12
-rw-r--r--core/java/android/app/WallpaperColors.java337
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java29
-rw-r--r--packages/SystemUI/colorextraction/src/com/google/android/colorextraction/types/Tonal.java46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java4
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java82
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"));