WallpaperColors refactor

Hiding color extraction into WallpaperColors.
This enables us to create WallpaperColors from a a Bitmap
or Drawable.

Fixes: 62197187
Fixes: 62490115
Test: runtest --path cts/tests/app/src/android/app/cts/WallpaperColorsTest.java
Change-Id: I614cfa205e02b551a141642eac6de21251c3bff6
This commit is contained in:
Lucas Dupin
2017-05-09 12:16:19 -07:00
parent 1e8c673dbf
commit 84b89d9d59
8 changed files with 376 additions and 156 deletions

View File

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

View File

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

View File

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

View File

@@ -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.
*
* @param colors list of pairs where each pair contains a color
* and number of occurrences/influence.
* @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
* @param drawable Source where to extract from.
*/
public WallpaperColors(List<Pair<Color, Integer>> colors) {
this(colors, calculateDarkTextSupport(colors));
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;
}
/**
* Wallpaper color details containing a list of colors and their weights,
* as if it were an histogram.
* Explicit dark text support.
* 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.
* @param supportsDarkText can have dark text on top or not
* @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
* @param bitmap Source where to extract from.
*/
public WallpaperColors(List<Pair<Color, Integer>> colors, boolean supportsDarkText) {
if (colors == null)
colors = new ArrayList<>();
mColors = colors;
mSupportsDarkText = supportsDarkText;
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);
}
/**
* Constructs a new object from three colors, where hints can be specified.
*
* @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(@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);
}
/**
* List of colors with their occurrences. The bigger the int, the more relevant the color.
* @return list of colors paired with their weights.
* 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 List<Pair<Color, Integer>> getColors() {
return mColors;
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 most preeminent colors, sorted by importance.
*
* @return List of colors.
* @hide
*/
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 boolean supportsDarkText() {
return mSupportsDarkText;
public int getColorHints() {
return mColorHints;
}
private static boolean calculateDarkTextSupport(List<Pair<Color, Integer>> colors) {
if (colors == null) {
/**
* @param colorHints Combination of WallpaperColors hints.
* @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
* @hide
*/
public void setColorHints(int colorHints) {
mColorHints = colorHints;
}
/**
* 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 mainColor != null &&
mainColor.first.luminance() > BRIGHT_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;
}
if (newHeight == 0) {
newHeight = 1;
}
return new Size(newWidth, newHeight);
}
}

View File

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

View File

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

View File

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

View File

@@ -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);
colors = WallpaperColors.fromDrawable(thumbnail);
}
if (bitmap == null) {
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"));