Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fcee629a authored by Android Build Merger (Role)'s avatar Android Build Merger (Role) Committed by Android (Google) Code Review
Browse files

Merge "Merge "WallpaperColors refactor" into oc-dr1-dev am: 04d2ec59" into oc-dr1-dev-plus-aosp

parents 0520d984 eeecc43e
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -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 {
+8 −4
Original line number Diff line number Diff line
@@ -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 {
+8 −4
Original line number Diff line number Diff line
@@ -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 {
+281 −56
Original line number Diff line number Diff line
@@ -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();
        mMainColors = new ArrayList<>();
        final 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));
            final int colorInt = parcel.readInt();
            Color color = Color.valueOf(colorInt);
            mMainColors.add(color);
        }
        mSupportsDarkText = parcel.readBoolean();
        mColorHints = parcel.readInt();
    }

    /**
     * Wallpaper color details containing a list of colors and their weights,
     * as if it were an histogram.
     * This list can be extracted from a bitmap by the Palette API.
     * Constructs {@link WallpaperColors} from a drawable.
     * <p>
     * Main colors will be extracted from the drawable and hints will be calculated.
     *
     * Dark text support will be calculated internally based on the histogram.
     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
     * @param drawable Source where to extract from.
     */
    public static WallpaperColors fromDrawable(Drawable drawable) {
        int width = drawable.getIntrinsicWidth();
        int height = drawable.getIntrinsicHeight();

        // Some drawables do not have intrinsic dimensions
        if (width <= 0 || height <= 0) {
            width = MAX_BITMAP_SIZE;
            height = MAX_BITMAP_SIZE;
        }

        Size optimalSize = calculateOptimalSize(width, height);
        Bitmap bitmap = Bitmap.createBitmap(optimalSize.getWidth(), optimalSize.getHeight(),
                Bitmap.Config.ARGB_8888);
        final Canvas bmpCanvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
        drawable.draw(bmpCanvas);

        final WallpaperColors colors = WallpaperColors.fromBitmap(bitmap);
        bitmap.recycle();

        return colors;
    }

    /**
     * Constructs {@link WallpaperColors} from a bitmap.
     * <p>
     * Main colors will be extracted from the bitmap and hints will be calculated.
     *
     * @param colors list of pairs where each pair contains a color
     *               and number of occurrences/influence.
     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
     * @param bitmap Source where to extract from.
     */
    public WallpaperColors(List<Pair<Color, Integer>> colors) {
        this(colors, calculateDarkTextSupport(colors));
    public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) {
        if (bitmap == null) {
            throw new IllegalArgumentException("Bitmap can't be null");
        }

        final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
        if (bitmapArea > MAX_WALLPAPER_EXTRACTION_AREA) {
            Size optimalSize = calculateOptimalSize(bitmap.getWidth(), bitmap.getHeight());
            Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, optimalSize.getWidth(),
                    optimalSize.getHeight(), true /* filter */);
            bitmap.recycle();
            bitmap = scaledBitmap;
        }

        final Palette palette = Palette
                .from(bitmap)
                .clearFilters()
                .resizeBitmapArea(MAX_WALLPAPER_EXTRACTION_AREA)
                .generate();

        // Remove insignificant colors and sort swatches by population
        final ArrayList<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches());
        final float minColorArea = bitmap.getWidth() * bitmap.getHeight() * MIN_COLOR_OCCURRENCE;
        swatches.removeIf(s -> s.getPopulation() < minColorArea);
        swatches.sort((a, b) -> b.getPopulation() - a.getPopulation());

        final int swatchesSize = swatches.size();
        Color primary = null, secondary = null, tertiary = null;

        swatchLoop:
        for (int i = 0; i < swatchesSize; i++) {
            Color color = Color.valueOf(swatches.get(i).getRgb());
            switch (i) {
                case 0:
                    primary = color;
                    break;
                case 1:
                    secondary = color;
                    break;
                case 2:
                    tertiary = color;
                    break;
                default:
                    // out of bounds
                    break swatchLoop;
            }
        }

        int hints = 0;
        if (calculateDarkTextSupport(bitmap)) {
            hints |= HINT_SUPPORTS_DARK_TEXT;
        }
        return new WallpaperColors(primary, secondary, tertiary, hints);
    }

    /**
     * Wallpaper color details containing a list of colors and their weights,
     * as if it were an histogram.
     * Explicit dark text support.
     * Constructs a new object from three colors, where hints can be specified.
     *
     * @param colors list of pairs where each pair contains a color
     *               and number of occurrences/influence.
     * @param supportsDarkText can have dark text on top or not
     * @param primaryColor Primary color.
     * @param secondaryColor Secondary color.
     * @param tertiaryColor Tertiary color.
     * @param colorHints A combination of WallpaperColor hints.
     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
     * @see WallpaperColors#fromBitmap(Bitmap)
     * @see WallpaperColors#fromDrawable(Drawable)
     */
    public WallpaperColors(List<Pair<Color, Integer>> colors, boolean supportsDarkText) {
        if (colors == null)
            colors = new ArrayList<>();
        mColors = colors;
        mSupportsDarkText = supportsDarkText;
    public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor,
            @Nullable Color tertiaryColor, int colorHints) {

        if (primaryColor == null) {
            throw new IllegalArgumentException("Primary color should never be null.");
        }

        mMainColors = new ArrayList<>(3);
        mMainColors.add(primaryColor);
        if (secondaryColor != null) {
            mMainColors.add(secondaryColor);
        }
        if (tertiaryColor != null) {
            if (secondaryColor == null) {
                throw new IllegalArgumentException("tertiaryColor can't be specified when "
                        + "secondaryColor is null");
            }
            mMainColors.add(tertiaryColor);
        }

        mColorHints = colorHints;
    }

    public static final Creator<WallpaperColors> CREATOR = new Creator<WallpaperColors>() {
@@ -94,21 +234,53 @@ public final class WallpaperColors implements Parcelable {

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        int count = mColors.size();
        List<Color> mainColors = getMainColors();
        int count = mainColors.size();
        dest.writeInt(count);
        for (Pair<Color, Integer> color : mColors) {
            dest.writeInt(color.first.toArgb());
            dest.writeInt(color.second);
        for (int i = 0; i < count; i++) {
            Color color = mainColors.get(i);
            dest.writeInt(color.toArgb());
        }
        dest.writeBoolean(mSupportsDarkText);
        dest.writeInt(mColorHints);
    }

    /**
     * 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 @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() {
        return mColors;
    public @NonNull List<Color> getMainColors() {
        return Collections.unmodifiableList(mMainColors);
    }

    @Override
@@ -118,38 +290,91 @@ public final class WallpaperColors implements Parcelable {
        }

        WallpaperColors other = (WallpaperColors) o;
        return mColors.equals(other.mColors) && mSupportsDarkText == other.mSupportsDarkText;
        return mMainColors.equals(other.mMainColors)
                && mColorHints == other.mColorHints;
    }

    @Override
    public int hashCode() {
        return 31 * mColors.hashCode() + (mSupportsDarkText ? 1 : 0);
        return 31 * mMainColors.hashCode() + mColorHints;
    }

    /**
     * Whether or not dark text is legible on top of this wallpaper.
     * Combination of WallpaperColor hints.
     *
     * @return true if dark text is supported
     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
     * @return True if dark text is supported.
     */
    public 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;
                }
            }
        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);
    }
}
+17 −12
Original line number Diff line number Diff line
@@ -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() {
Loading