Loading api/current.txt +8 −4 Original line number Original line Diff line number Diff line Loading @@ -6089,13 +6089,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); method public int describeContents(); method public int describeContents(); method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); method public boolean supportsDarkText(); 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); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; 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 { public final class WallpaperInfo implements android.os.Parcelable { api/system-current.txt +8 −4 Original line number Original line Diff line number Diff line Loading @@ -6299,13 +6299,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); method public int describeContents(); method public int describeContents(); method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); method public boolean supportsDarkText(); 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); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; 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 { public final class WallpaperInfo implements android.os.Parcelable { api/test-current.txt +8 −4 Original line number Original line Diff line number Diff line Loading @@ -6110,13 +6110,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); method public int describeContents(); method public int describeContents(); method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); method public boolean supportsDarkText(); 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); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; 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 { public final class WallpaperInfo implements android.os.Parcelable { core/java/android/app/WallpaperColors.java +281 −56 Original line number Original line Diff line number Diff line Loading @@ -16,63 +16,203 @@ package android.app; 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.Color; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; 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.ArrayList; import java.util.Collections; import java.util.List; 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 { public final class WallpaperColors implements Parcelable { private static final float BRIGHT_LUMINANCE = 0.9f; /** private final List<Pair<Color, Integer>> mColors; * Specifies that dark text is preferred over the current wallpaper for best presentation. private final boolean mSupportsDarkText; * <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) { public WallpaperColors(Parcel parcel) { mColors = new ArrayList<>(); mMainColors = new ArrayList<>(); int count = parcel.readInt(); final int count = parcel.readInt(); for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) { Color color = Color.valueOf(parcel.readInt()); final int colorInt = parcel.readInt(); int weight = parcel.readInt(); Color color = Color.valueOf(colorInt); mColors.add(new Pair<>(color, weight)); mMainColors.add(color); } } mSupportsDarkText = parcel.readBoolean(); mColorHints = parcel.readInt(); } } /** /** * Wallpaper color details containing a list of colors and their weights, * Constructs {@link WallpaperColors} from a drawable. * as if it were an histogram. * <p> * This list can be extracted from a bitmap by the Palette API. * 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 * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT * and number of occurrences/influence. * @param bitmap Source where to extract from. */ */ public WallpaperColors(List<Pair<Color, Integer>> colors) { public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) { this(colors, calculateDarkTextSupport(colors)); 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, * Constructs a new object from three colors, where hints can be specified. * as if it were an histogram. * Explicit dark text support. * * * @param colors list of pairs where each pair contains a color * @param primaryColor Primary color. * and number of occurrences/influence. * @param secondaryColor Secondary color. * @param supportsDarkText can have dark text on top or not * @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) { public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor, if (colors == null) @Nullable Color tertiaryColor, int colorHints) { colors = new ArrayList<>(); mColors = colors; if (primaryColor == null) { mSupportsDarkText = supportsDarkText; 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>() { public static final Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() { Loading @@ -94,21 +234,53 @@ public final class WallpaperColors implements Parcelable { @Override @Override public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) { int count = mColors.size(); List<Color> mainColors = getMainColors(); int count = mainColors.size(); dest.writeInt(count); dest.writeInt(count); for (Pair<Color, Integer> color : mColors) { for (int i = 0; i < count; i++) { dest.writeInt(color.first.toArgb()); Color color = mainColors.get(i); dest.writeInt(color.second); 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. * Gets the most visually representative color of the wallpaper. * @return list of colors paired with their weights. * "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 most preeminent colors, sorted by importance. * * @return List of colors. * @hide */ */ public List<Pair<Color, Integer>> getColors() { public @NonNull List<Color> getMainColors() { return mColors; return Collections.unmodifiableList(mMainColors); } } @Override @Override Loading @@ -118,38 +290,91 @@ public final class WallpaperColors implements Parcelable { } } WallpaperColors other = (WallpaperColors) o; WallpaperColors other = (WallpaperColors) o; return mColors.equals(other.mColors) && mSupportsDarkText == other.mSupportsDarkText; return mMainColors.equals(other.mMainColors) && mColorHints == other.mColorHints; } } @Override @Override public int hashCode() { 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() { public int getColorHints() { return mSupportsDarkText; 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; 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) { // This bitmap was already resized to fit the maximum allowed area. if (mainColor == null) { // Let's just loop through the pixels, no sweat! mainColor = color; for (int i = 0; i < pixels.length; i++) { } else if (color.second > mainColor.second) { final float luminance = Color.luminance(pixels[i]); mainColor = color; 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; } } } } return mainColor != null && mainColor.first.luminance() > BRIGHT_LUMINANCE; 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; } if (newHeight == 0) { newHeight = 1; } return new Size(newWidth, newHeight); } } } } core/java/android/service/wallpaper/WallpaperService.java +17 −12 Original line number Original line Diff line number Diff line Loading @@ -17,25 +17,19 @@ package android.service.wallpaper; package android.service.wallpaper; import android.annotation.Nullable; 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; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.app.Service; import android.app.WallpaperColors; import android.app.WallpaperManager; import android.app.WallpaperManager; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Bundle; import android.os.Bundle; Loading @@ -44,6 +38,7 @@ import android.os.Looper; import android.os.Message; import android.os.Message; import android.os.RemoteException; import android.os.RemoteException; import android.util.Log; import android.util.Log; import android.util.MergedConfiguration; import android.view.Display; import android.view.Display; import android.view.Gravity; import android.view.Gravity; import android.view.IWindowSession; import android.view.IWindowSession; Loading @@ -55,9 +50,14 @@ import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager; import android.view.WindowManagerGlobal; 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.FileDescriptor; import java.io.PrintWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.ArrayList; Loading Loading @@ -563,8 +563,13 @@ public abstract class WallpaperService extends Service { * Notifies the system about what colors the wallpaper is using. * 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 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. * 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 * @hide */ */ public @Nullable WallpaperColors onComputeWallpaperColors() { public @Nullable WallpaperColors onComputeWallpaperColors() { Loading Loading
api/current.txt +8 −4 Original line number Original line Diff line number Diff line Loading @@ -6089,13 +6089,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); method public int describeContents(); method public int describeContents(); method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); method public boolean supportsDarkText(); 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); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; 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 { public final class WallpaperInfo implements android.os.Parcelable {
api/system-current.txt +8 −4 Original line number Original line Diff line number Diff line Loading @@ -6299,13 +6299,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); method public int describeContents(); method public int describeContents(); method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); method public boolean supportsDarkText(); 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); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; 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 { public final class WallpaperInfo implements android.os.Parcelable {
api/test-current.txt +8 −4 Original line number Original line Diff line number Diff line Loading @@ -6110,13 +6110,17 @@ package android.app { public final class WallpaperColors implements android.os.Parcelable { public final class WallpaperColors implements android.os.Parcelable { ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(android.os.Parcel); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>); ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); ctor public WallpaperColors(java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>>, boolean); method public int describeContents(); method public int describeContents(); method public java.util.List<android.util.Pair<android.graphics.Color, java.lang.Integer>> getColors(); method public static android.app.WallpaperColors fromBitmap(android.graphics.Bitmap); method public boolean supportsDarkText(); 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); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR; 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 { public final class WallpaperInfo implements android.os.Parcelable {
core/java/android/app/WallpaperColors.java +281 −56 Original line number Original line Diff line number Diff line Loading @@ -16,63 +16,203 @@ package android.app; 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.Color; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; 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.ArrayList; import java.util.Collections; import java.util.List; 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 { public final class WallpaperColors implements Parcelable { private static final float BRIGHT_LUMINANCE = 0.9f; /** private final List<Pair<Color, Integer>> mColors; * Specifies that dark text is preferred over the current wallpaper for best presentation. private final boolean mSupportsDarkText; * <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) { public WallpaperColors(Parcel parcel) { mColors = new ArrayList<>(); mMainColors = new ArrayList<>(); int count = parcel.readInt(); final int count = parcel.readInt(); for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) { Color color = Color.valueOf(parcel.readInt()); final int colorInt = parcel.readInt(); int weight = parcel.readInt(); Color color = Color.valueOf(colorInt); mColors.add(new Pair<>(color, weight)); mMainColors.add(color); } } mSupportsDarkText = parcel.readBoolean(); mColorHints = parcel.readInt(); } } /** /** * Wallpaper color details containing a list of colors and their weights, * Constructs {@link WallpaperColors} from a drawable. * as if it were an histogram. * <p> * This list can be extracted from a bitmap by the Palette API. * 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 * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT * and number of occurrences/influence. * @param bitmap Source where to extract from. */ */ public WallpaperColors(List<Pair<Color, Integer>> colors) { public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) { this(colors, calculateDarkTextSupport(colors)); 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, * Constructs a new object from three colors, where hints can be specified. * as if it were an histogram. * Explicit dark text support. * * * @param colors list of pairs where each pair contains a color * @param primaryColor Primary color. * and number of occurrences/influence. * @param secondaryColor Secondary color. * @param supportsDarkText can have dark text on top or not * @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) { public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor, if (colors == null) @Nullable Color tertiaryColor, int colorHints) { colors = new ArrayList<>(); mColors = colors; if (primaryColor == null) { mSupportsDarkText = supportsDarkText; 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>() { public static final Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() { Loading @@ -94,21 +234,53 @@ public final class WallpaperColors implements Parcelable { @Override @Override public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) { int count = mColors.size(); List<Color> mainColors = getMainColors(); int count = mainColors.size(); dest.writeInt(count); dest.writeInt(count); for (Pair<Color, Integer> color : mColors) { for (int i = 0; i < count; i++) { dest.writeInt(color.first.toArgb()); Color color = mainColors.get(i); dest.writeInt(color.second); 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. * Gets the most visually representative color of the wallpaper. * @return list of colors paired with their weights. * "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 most preeminent colors, sorted by importance. * * @return List of colors. * @hide */ */ public List<Pair<Color, Integer>> getColors() { public @NonNull List<Color> getMainColors() { return mColors; return Collections.unmodifiableList(mMainColors); } } @Override @Override Loading @@ -118,38 +290,91 @@ public final class WallpaperColors implements Parcelable { } } WallpaperColors other = (WallpaperColors) o; WallpaperColors other = (WallpaperColors) o; return mColors.equals(other.mColors) && mSupportsDarkText == other.mSupportsDarkText; return mMainColors.equals(other.mMainColors) && mColorHints == other.mColorHints; } } @Override @Override public int hashCode() { 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() { public int getColorHints() { return mSupportsDarkText; 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; 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) { // This bitmap was already resized to fit the maximum allowed area. if (mainColor == null) { // Let's just loop through the pixels, no sweat! mainColor = color; for (int i = 0; i < pixels.length; i++) { } else if (color.second > mainColor.second) { final float luminance = Color.luminance(pixels[i]); mainColor = color; 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; } } } } return mainColor != null && mainColor.first.luminance() > BRIGHT_LUMINANCE; 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; } if (newHeight == 0) { newHeight = 1; } return new Size(newWidth, newHeight); } } } }
core/java/android/service/wallpaper/WallpaperService.java +17 −12 Original line number Original line Diff line number Diff line Loading @@ -17,25 +17,19 @@ package android.service.wallpaper; package android.service.wallpaper; import android.annotation.Nullable; 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; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.app.Service; import android.app.WallpaperColors; import android.app.WallpaperManager; import android.app.WallpaperManager; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Bundle; import android.os.Bundle; Loading @@ -44,6 +38,7 @@ import android.os.Looper; import android.os.Message; import android.os.Message; import android.os.RemoteException; import android.os.RemoteException; import android.util.Log; import android.util.Log; import android.util.MergedConfiguration; import android.view.Display; import android.view.Display; import android.view.Gravity; import android.view.Gravity; import android.view.IWindowSession; import android.view.IWindowSession; Loading @@ -55,9 +50,14 @@ import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager; import android.view.WindowManagerGlobal; 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.FileDescriptor; import java.io.PrintWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.ArrayList; Loading Loading @@ -563,8 +563,13 @@ public abstract class WallpaperService extends Service { * Notifies the system about what colors the wallpaper is using. * 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 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. * 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 * @hide */ */ public @Nullable WallpaperColors onComputeWallpaperColors() { public @Nullable WallpaperColors onComputeWallpaperColors() { Loading