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:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user