Revert "[framework] Integrate new quantizers"

Revert "[sysuig] Integrate Monet color extraction to theme algorithm"

Revert submission 13554372-kahuna

Reason for revert: Colors on some wallpapers shifted, and Lucas found a couple big reasons why, would rather revert a revert tomorrow than fix forward today
Reverted Changes:
I2c3df9f71:[sysuig] Integrate Monet color extraction to theme...
I0fc60a134:[framework] Integrate new quantizers

Change-Id: I7eb8e4fde243bf62e8c750f88028db0a427560b5
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 3abba43..2d203f57 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -30,17 +30,14 @@
 import android.util.Size;
 
 import com.android.internal.graphics.ColorUtils;
-import com.android.internal.graphics.palette.CelebiQuantizer;
 import com.android.internal.graphics.palette.Palette;
+import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
 import com.android.internal.util.ContrastColorUtil;
 
 import java.io.FileOutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * Provides information about the colors of a wallpaper.
@@ -97,21 +94,16 @@
     private static final float DARK_PIXEL_CONTRAST = 6f;
     private static final float MAX_DARK_AREA = 0.025f;
 
-    private final List<Color> mMainColors;
-    private final Map<Integer, Integer> mAllColors;
+    private final ArrayList<Color> mMainColors;
     private int mColorHints;
 
     public WallpaperColors(Parcel parcel) {
         mMainColors = new ArrayList<>();
-        mAllColors = new HashMap<>();
         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);
-
-            final int population = parcel.readInt();
-            mAllColors.put(colorInt, population);
         }
         mColorHints = parcel.readInt();
     }
@@ -174,22 +166,39 @@
         }
 
         final Palette palette = Palette
-                .from(bitmap, new CelebiQuantizer())
-                .maximumColorCount(256)
+                .from(bitmap)
+                .setQuantizer(new VariationalKMeansQuantizer())
+                .maximumColorCount(5)
+                .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;
 
-        final Map<Integer, Integer> populationByColor = new HashMap<>();
+        swatchLoop:
         for (int i = 0; i < swatchesSize; i++) {
-            Palette.Swatch swatch = swatches.get(i);
-            int colorInt = swatch.getInt();
-            populationByColor.put(colorInt, swatch.getPopulation());
-
+            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 = calculateDarkHints(bitmap);
@@ -198,7 +207,7 @@
             bitmap.recycle();
         }
 
-        return new WallpaperColors(populationByColor, HINT_FROM_BITMAP | hints);
+        return new WallpaperColors(primary, secondary, tertiary, HINT_FROM_BITMAP | hints);
     }
 
     /**
@@ -244,13 +253,9 @@
         }
 
         mMainColors = new ArrayList<>(3);
-        mAllColors = new HashMap<>();
-
         mMainColors.add(primaryColor);
-        mAllColors.put(primaryColor.toArgb(), 0);
         if (secondaryColor != null) {
             mMainColors.add(secondaryColor);
-            mAllColors.put(secondaryColor.toArgb(), 0);
         }
         if (tertiaryColor != null) {
             if (secondaryColor == null) {
@@ -258,32 +263,8 @@
                         + "secondaryColor is null");
             }
             mMainColors.add(tertiaryColor);
-            mAllColors.put(tertiaryColor.toArgb(), 0);
         }
-        mColorHints = colorHints;
-    }
 
-    /**
-     * Constructs a new object from a set of colors, where hints can be specified.
-     *
-     * @param populationByColor Map with keys of colors, and value representing the number of
-     *                          occurrences of color in the wallpaper.
-     * @param colorHints        A combination of WallpaperColor hints.
-     * @hide
-     * @see WallpaperColors#HINT_SUPPORTS_DARK_TEXT
-     * @see WallpaperColors#fromBitmap(Bitmap)
-     * @see WallpaperColors#fromDrawable(Drawable)
-     */
-    public WallpaperColors(@NonNull Map<Integer, Integer> populationByColor, int colorHints) {
-        mAllColors = populationByColor;
-
-        ArrayList<Map.Entry<Integer, Integer>> mapEntries = new ArrayList(
-                populationByColor.entrySet());
-        mapEntries.sort((a, b) ->
-                a.getValue().compareTo(b.getValue())
-        );
-        mMainColors = mapEntries.stream().map(entry -> Color.valueOf(entry.getKey())).collect(
-                Collectors.toList());
         mColorHints = colorHints;
     }
 
@@ -312,9 +293,6 @@
         for (int i = 0; i < count; i++) {
             Color color = mainColors.get(i);
             dest.writeInt(color.toArgb());
-            Integer population = mAllColors.get(color.toArgb());
-            int populationInt = (population != null) ? population : 0;
-            dest.writeInt(populationInt);
         }
         dest.writeInt(mColorHints);
     }
@@ -358,17 +336,6 @@
         return Collections.unmodifiableList(mMainColors);
     }
 
-    /**
-     * Map of all colors. Key is rgb integer, value is importance of color.
-     *
-     * @return List of colors.
-     * @hide
-     */
-    public @NonNull Map<Integer, Integer> getAllColors() {
-        return Collections.unmodifiableMap(mAllColors);
-    }
-
-
     @Override
     public boolean equals(@Nullable Object o) {
         if (o == null || getClass() != o.getClass()) {
diff --git a/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java b/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java
deleted file mode 100644
index de6bf20..0000000
--- a/core/java/com/android/internal/graphics/palette/CelebiQuantizer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-import java.util.List;
-
-/**
- * An implementation of Celebi's WSM quantizer, or, a Kmeans quantizer that starts with centroids
- * from a Wu quantizer to ensure 100% reproducible and quality results, and has some optimizations
- * to the Kmeans algorithm.
- *
- * See Celebi 2011, “Improving the Performance of K-Means for Color Quantization”
- */
-public class CelebiQuantizer implements Quantizer {
-    private List<Palette.Swatch> mSwatches;
-
-    public CelebiQuantizer() { }
-
-    @Override
-    public void quantize(int[] pixels, int maxColors) {
-        WuQuantizer wu = new WuQuantizer(pixels, maxColors);
-        wu.quantize(pixels, maxColors);
-        List<Palette.Swatch> wuSwatches = wu.getQuantizedColors();
-        LABCentroid labCentroidProvider = new LABCentroid();
-        WSMeansQuantizer kmeans =
-                new WSMeansQuantizer(WSMeansQuantizer.createStartingCentroids(labCentroidProvider,
-                        wuSwatches), labCentroidProvider, pixels, maxColors);
-        kmeans.quantize(pixels, maxColors);
-        mSwatches = kmeans.getQuantizedColors();
-    }
-
-    @Override
-    public List<Palette.Swatch> getQuantizedColors() {
-        return mSwatches;
-    }
-}
diff --git a/core/java/com/android/internal/graphics/palette/CentroidProvider.java b/core/java/com/android/internal/graphics/palette/CentroidProvider.java
deleted file mode 100644
index 5fcfcba..0000000
--- a/core/java/com/android/internal/graphics/palette/CentroidProvider.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-import android.annotation.ColorInt;
-
-interface CentroidProvider {
-    /**
-     * @return 3 dimensions representing the color
-     */
-    float[] getCentroid(@ColorInt int color);
-
-    /**
-     * @param centroid 3 dimensions representing the color
-     * @return 32-bit ARGB representation
-     */
-    @ColorInt
-    int getColor(float[] centroid);
-
-    /**
-     * Distance between two centroids.
-     */
-    float distance(float[] a, float[] b);
-}
diff --git a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
index 7779494..9ac753b 100644
--- a/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
+++ b/core/java/com/android/internal/graphics/palette/ColorCutQuantizer.java
@@ -35,8 +35,6 @@
 import android.graphics.Color;
 import android.util.TimingLogger;
 
-import com.android.internal.graphics.palette.Palette.Swatch;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -44,6 +42,9 @@
 import java.util.List;
 import java.util.PriorityQueue;
 
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.graphics.palette.Palette.Swatch;
+
 /**
  * Copied from: frameworks/support/v7/palette/src/main/java/android/support/v7/
  * graphics/ColorCutQuantizer.java
@@ -76,17 +77,20 @@
     int[] mHistogram;
     List<Swatch> mQuantizedColors;
     TimingLogger mTimingLogger;
+    Palette.Filter[] mFilters;
 
     private final float[] mTempHsl = new float[3];
 
     /**
      * Execute color quantization.
      *
-     * @param pixels    histogram representing an image's pixel data
+     * @param pixels histogram representing an image's pixel data
      * @param maxColors The maximum number of colors that should be in the result palette.
+     * @param filters Set of filters to use in the quantization stage
      */
-    public void quantize(final int[] pixels, final int maxColors) {
+    public void quantize(final int[] pixels, final int maxColors, final Palette.Filter[] filters) {
         mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null;
+        mFilters = filters;
 
         final int[] hist = mHistogram = new int[1 << (QUANTIZE_WORD_WIDTH * 3)];
         for (int i = 0; i < pixels.length; i++) {
@@ -104,6 +108,10 @@
         // Now let's count the number of distinct colors
         int distinctColorCount = 0;
         for (int color = 0; color < hist.length; color++) {
+            if (hist[color] > 0 && shouldIgnoreColor(color)) {
+                // If we should ignore the color, set the population to 0
+                hist[color] = 0;
+            }
             if (hist[color] > 0) {
                 // If the color has population, increase the distinct color count
                 distinctColorCount++;
@@ -178,7 +186,7 @@
      * and splitting them. Once split, the new box and the remaining box are offered back to the
      * queue.
      *
-     * @param queue   {@link java.util.PriorityQueue} to poll for boxes
+     * @param queue {@link java.util.PriorityQueue} to poll for boxes
      * @param maxSize Maximum amount of boxes to split
      */
     private void splitBoxes(final PriorityQueue<Vbox> queue, final int maxSize) {
@@ -208,7 +216,11 @@
         ArrayList<Swatch> colors = new ArrayList<>(vboxes.size());
         for (Vbox vbox : vboxes) {
             Swatch swatch = vbox.getAverageColor();
-            colors.add(swatch);
+            if (!shouldIgnoreColor(swatch)) {
+                // As we're averaging a color box, we can still get colors which we do not want, so
+                // we check again here
+                colors.add(swatch);
+            }
         }
         return colors;
     }
@@ -218,7 +230,7 @@
      */
     private class Vbox {
         // lower and upper index are inclusive
-        private final int mLowerIndex;
+        private int mLowerIndex;
         private int mUpperIndex;
         // Population of colors within this box
         private int mPopulation;
@@ -361,7 +373,7 @@
             modifySignificantOctet(colors, longestDimension, mLowerIndex, mUpperIndex);
 
             final int midPoint = mPopulation / 2;
-            for (int i = mLowerIndex, count = 0; i <= mUpperIndex; i++) {
+            for (int i = mLowerIndex, count = 0; i <= mUpperIndex; i++)  {
                 count += hist[colors[i]];
                 if (count >= midPoint) {
                     // we never want to split on the upperIndex, as this will result in the same
@@ -435,6 +447,27 @@
         }
     }
 
+    private boolean shouldIgnoreColor(int color565) {
+        final int rgb = approximateToRgb888(color565);
+        ColorUtils.colorToHSL(rgb, mTempHsl);
+        return shouldIgnoreColor(rgb, mTempHsl);
+    }
+
+    private boolean shouldIgnoreColor(Swatch color) {
+        return shouldIgnoreColor(color.getRgb(), color.getHsl());
+    }
+
+    private boolean shouldIgnoreColor(int rgb, float[] hsl) {
+        if (mFilters != null && mFilters.length > 0) {
+            for (int i = 0, count = mFilters.length; i < count; i++) {
+                if (!mFilters[i].isAllowed(rgb, hsl)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Comparator which sorts {@link Vbox} instances based on their volume, in descending order
      */
@@ -465,8 +498,7 @@
     }
 
     private static int approximateToRgb888(int color) {
-        return approximateToRgb888(quantizedRed(color), quantizedGreen(color),
-                quantizedBlue(color));
+        return approximateToRgb888(quantizedRed(color), quantizedGreen(color), quantizedBlue(color));
     }
 
     /**
diff --git a/core/java/com/android/internal/graphics/palette/Contrast.java b/core/java/com/android/internal/graphics/palette/Contrast.java
deleted file mode 100644
index 3dd1b8d..0000000
--- a/core/java/com/android/internal/graphics/palette/Contrast.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-/**
- * Helper methods for determining contrast between two colors, either via the colors themselves
- * or components in different color spaces.
- */
-public class Contrast {
-    /**
-     *
-     * @param y Y in XYZ that contrasts with the returned Y value
-     * @param contrast contrast ratio between color argument and returned Y value. Must be >= 1
-     *    or an exception will be thrown
-     * @return the lower Y coordinate in XYZ space that contrasts with color, or -1 if reaching
-     *    no Y coordinate reaches contrast with color.
-     */
-    public static float lighterY(float y, float contrast) {
-        assert (contrast >= 1);
-        float answer = -5 + contrast * (5 + y);
-        if (answer > 100.0) {
-            return -1;
-        }
-        return answer;
-    }
-
-
-    /**
-     * @param y Y in XYZ that contrasts with the returned Y value
-     * @param contrast contrast ratio between color argument and returned Y value. Must be >= 1
-     *    or an exception will be thrown
-     * @return the lower Y coordinate in XYZ space that contrasts with color, or -1 if reaching
-     *    no Y coordinate reaches contrast with color.
-     */
-    public static float darkerY(float y, float contrast) {
-        assert (contrast >= 1);
-        float answer = (5 - 5 * contrast + y) / contrast;
-        if (answer < 0.0) {
-            return -1;
-        }
-        return answer;
-    }
-
-    /**
-     * Convert L* in L*a*b* to Y in XYZ.
-     *
-     * @param lstar L* in L*a*b*
-     * @return Y in XYZ
-     */
-    public static float lstarToY(float lstar) {
-        // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
-        float ke = 8.0f;
-        if (lstar > ke) {
-            return (float) (Math.pow(((lstar + 16.0) / 116.0), 3) * 100.0);
-        } else {
-            return (float) (lstar / (24389 / 27) * 100.0);
-        }
-    }
-
-    /**
-     * Convert Y in XYZ to L* in L*a*b*.
-     *
-     * @param y Y in XYZ
-     * @return L* in L*a*b*
-     */
-    public static float yToLstar(float y) {
-        y = y / 100.0f;
-        float e = 216.0f / 24389.0f;
-        float y_intermediate;
-        if (y <= e) {
-            y_intermediate = (24389.f / 27.f) * y;
-            // If y < e, can skip consecutive steps of / 116 + 16 followed by * 116 - 16.
-            return y_intermediate;
-        } else {
-            y_intermediate = (float) Math.cbrt(y);
-        }
-        return 116.f * y_intermediate - 16.f;
-    }
-
-
-    /**
-     * @return Contrast ratio between two Y values in XYZ space.
-     */
-    public static float contrastYs(float y1, float y2) {
-        final float lighter = Math.max(y1, y2);
-        final float darker = (lighter == y1) ? y2 : y1;
-        return (lighter + 5) / (darker + 5);
-    }
-}
diff --git a/core/java/com/android/internal/graphics/palette/LABCentroid.java b/core/java/com/android/internal/graphics/palette/LABCentroid.java
deleted file mode 100644
index 98d5d26..0000000
--- a/core/java/com/android/internal/graphics/palette/LABCentroid.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-import android.graphics.Color;
-import android.graphics.ColorSpace;
-
-/**
- *  Allows quantizers to operate in the L*a*b* colorspace.
- *  L*a*b* is a good choice for measuring distance between colors.
- *  Better spaces, and better distance calculations even in L*a*b* exist, but measuring distance
- *  in L*a*b* space, also known as deltaE, is a universally accepted standard across industries
- *  and worldwide.
- */
-public class LABCentroid implements CentroidProvider {
-    final ColorSpace.Connector mRgbToLab;
-    final ColorSpace.Connector mLabToRgb;
-
-    public LABCentroid() {
-        mRgbToLab = ColorSpace.connect(
-                ColorSpace.get(ColorSpace.Named.SRGB),
-                ColorSpace.get(ColorSpace.Named.CIE_LAB));
-        mLabToRgb = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.CIE_LAB),
-                ColorSpace.get(ColorSpace.Named.SRGB));
-    }
-
-    @Override
-    public float[] getCentroid(int color) {
-        float r = Color.red(color) / 255.f;
-        float g =  Color.green(color) / 255.f;
-        float b = Color.blue(color) / 255.f;
-
-        float[] transform = mRgbToLab.transform(r, g, b);
-        return transform;
-    }
-
-    @Override
-    public int getColor(float[] centroid) {
-        float[] rgb = mLabToRgb.transform(centroid);
-        int color = Color.rgb(rgb[0], rgb[1], rgb[2]);
-        return color;
-    }
-
-    @Override
-    public float distance(float[] a, float[] b) {
-        // Standard v1 CIELAB deltaE formula, 1976 - easily improved upon, however,
-        // improvements do not significantly impact the Palette algorithm's results.
-        double dL = a[0] - b[0];
-        double dA = a[1] - b[1];
-        double dB = a[2] - b[2];
-        return (float) (Math.pow(dL, 2) + Math.pow(dA, 2) + Math.pow(dB, 2));
-    }
-}
diff --git a/core/java/com/android/internal/graphics/palette/Mean.java b/core/java/com/android/internal/graphics/palette/Mean.java
deleted file mode 100644
index 894f91b..0000000
--- a/core/java/com/android/internal/graphics/palette/Mean.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-import java.util.Random;
-
-/**
- * Represents a centroid in Kmeans algorithms.
- */
-public class Mean {
-    private static final Random RANDOM = new Random(0);
-
-    public float[] center;
-
-    /**
-     * Constructor.
-     *
-     * @param upperBound maximum value of a dimension in the space Kmeans is optimizing in
-     */
-    Mean(int upperBound) {
-        center =
-                new float[]{
-                        RANDOM.nextInt(upperBound + 1), RANDOM.nextInt(upperBound + 1),
-                        RANDOM.nextInt(upperBound + 1)
-                };
-    }
-
-    Mean(float[] center) {
-        this.center = center;
-    }
-}
diff --git a/core/java/com/android/internal/graphics/palette/MeanBucket.java b/core/java/com/android/internal/graphics/palette/MeanBucket.java
deleted file mode 100644
index ae8858a..0000000
--- a/core/java/com/android/internal/graphics/palette/MeanBucket.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-import java.util.HashSet;
-import java.util.Set;
-
-class MeanBucket {
-    float[] mTotal = {0.f, 0.f, 0.f};
-    int mCount = 0;
-    Set<Integer> mColors = new HashSet<>();
-
-    void add(float[] colorAsDoubles, int color, int colorCount) {
-        assert (colorAsDoubles.length == 3);
-        mColors.add(color);
-        mTotal[0] += (colorAsDoubles[0] * colorCount);
-        mTotal[1] += (colorAsDoubles[1] * colorCount);
-        mTotal[2] += (colorAsDoubles[2] * colorCount);
-        mCount += colorCount;
-    }
-
-    float[] getCentroid() {
-        if (mCount == 0) {
-            return null;
-        }
-        return new float[]{mTotal[0] / mCount, mTotal[1] / mCount, mTotal[2] / mCount};
-    }
-}
diff --git a/core/java/com/android/internal/graphics/palette/Palette.java b/core/java/com/android/internal/graphics/palette/Palette.java
index 8b1137d..a4f9a59 100644
--- a/core/java/com/android/internal/graphics/palette/Palette.java
+++ b/core/java/com/android/internal/graphics/palette/Palette.java
@@ -19,24 +19,48 @@
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.Px;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.util.TimingLogger;
 
+import com.android.internal.graphics.ColorUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 
 /**
+ * Copied from: /frameworks/support/v7/palette/src/main/java/android/support/v7/
+ * graphics/Palette.java
+ *
  * A helper class to extract prominent colors from an image.
+ * <p>
+ * A number of colors with different profiles are extracted from the image:
+ * <ul>
+ *     <li>Vibrant</li>
+ *     <li>Vibrant Dark</li>
+ *     <li>Vibrant Light</li>
+ *     <li>Muted</li>
+ *     <li>Muted Dark</li>
+ *     <li>Muted Light</li>
+ * </ul>
+ * These can be retrieved from the appropriate getter method.
  *
- * <p>Instances are created with a {@link Builder} which supports several options to tweak the
+ * <p>
+ * Instances are created with a {@link Palette.Builder} which supports several options to tweak the
  * generated Palette. See that class' documentation for more information.
- *
- * <p>Generation should always be completed on a background thread, ideally the one in which you
- * load your image on. {@link Builder} supports both synchronous and asynchronous generation:
+ * <p>
+ * Generation should always be completed on a background thread, ideally the one in
+ * which you load your image on. {@link Palette.Builder} supports both synchronous and asynchronous
+ * generation:
  *
  * <pre>
  * // Synchronous
@@ -61,59 +85,346 @@
         /**
          * Called when the {@link Palette} has been generated.
          */
-        void onGenerated(@Nullable Palette palette);
+        void onGenerated(Palette palette);
     }
 
     static final int DEFAULT_RESIZE_BITMAP_AREA = 112 * 112;
     static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
-    static final String LOG_TAG = "Palette";
 
-    /** Start generating a {@link Palette} with the returned {@link Builder} instance. */
-    @NonNull
-    public static Builder from(@NonNull Bitmap bitmap, @NonNull Quantizer quantizer) {
-        return new Builder(bitmap, quantizer);
+    static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
+    static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
+
+    static final String LOG_TAG = "Palette";
+    static final boolean LOG_TIMINGS = false;
+
+    /**
+     * Start generating a {@link Palette} with the returned {@link Palette.Builder} instance.
+     */
+    public static Palette.Builder from(Bitmap bitmap) {
+        return new Palette.Builder(bitmap);
     }
 
     /**
      * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches.
-     * This
-     * is useful for testing, or if you want to resurrect a {@link Palette} instance from a list of
-     * swatches. Will return null if the {@code swatches} is null.
+     * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
+     * list of swatches. Will return null if the {@code swatches} is null.
      */
-    @NonNull
-    public static Palette from(@NonNull List<Swatch> swatches) {
-        return new Builder(swatches).generate();
+    public static Palette from(List<Palette.Swatch> swatches) {
+        return new Palette.Builder(swatches).generate();
     }
 
-    private final List<Swatch> mSwatches;
+    /**
+     * @deprecated Use {@link Palette.Builder} to generate the Palette.
+     */
+    @Deprecated
+    public static Palette generate(Bitmap bitmap) {
+        return from(bitmap).generate();
+    }
 
+    /**
+     * @deprecated Use {@link Palette.Builder} to generate the Palette.
+     */
+    @Deprecated
+    public static Palette generate(Bitmap bitmap, int numColors) {
+        return from(bitmap).maximumColorCount(numColors).generate();
+    }
 
-    @Nullable
-    private final Swatch mDominantSwatch;
+    /**
+     * @deprecated Use {@link Palette.Builder} to generate the Palette.
+     */
+    @Deprecated
+    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
+            Bitmap bitmap, Palette.PaletteAsyncListener listener) {
+        return from(bitmap).generate(listener);
+    }
 
-    Palette(List<Swatch> swatches) {
+    /**
+     * @deprecated Use {@link Palette.Builder} to generate the Palette.
+     */
+    @Deprecated
+    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
+            final Bitmap bitmap, final int numColors, final Palette.PaletteAsyncListener listener) {
+        return from(bitmap).maximumColorCount(numColors).generate(listener);
+    }
+
+    private final List<Palette.Swatch> mSwatches;
+    private final List<Target> mTargets;
+
+    private final Map<Target, Palette.Swatch> mSelectedSwatches;
+    private final SparseBooleanArray mUsedColors;
+
+    private final Palette.Swatch mDominantSwatch;
+
+    Palette(List<Palette.Swatch> swatches, List<Target> targets) {
         mSwatches = swatches;
+        mTargets = targets;
+
+        mUsedColors = new SparseBooleanArray();
+        mSelectedSwatches = new ArrayMap<>();
+
         mDominantSwatch = findDominantSwatch();
     }
 
-    /** Returns all of the swatches which make up the palette. */
+    /**
+     * Returns all of the swatches which make up the palette.
+     */
     @NonNull
-    public List<Swatch> getSwatches() {
+    public List<Palette.Swatch> getSwatches() {
         return Collections.unmodifiableList(mSwatches);
     }
 
-    /** Returns the swatch with the highest population, or null if there are no swatches. */
+    /**
+     * Returns the targets used to generate this palette.
+     */
+    @NonNull
+    public List<Target> getTargets() {
+        return Collections.unmodifiableList(mTargets);
+    }
+
+    /**
+     * Returns the most vibrant swatch in the palette. Might be null.
+     *
+     * @see Target#VIBRANT
+     */
     @Nullable
-    public Swatch getDominantSwatch() {
+    public Palette.Swatch getVibrantSwatch() {
+        return getSwatchForTarget(Target.VIBRANT);
+    }
+
+    /**
+     * Returns a light and vibrant swatch from the palette. Might be null.
+     *
+     * @see Target#LIGHT_VIBRANT
+     */
+    @Nullable
+    public Palette.Swatch getLightVibrantSwatch() {
+        return getSwatchForTarget(Target.LIGHT_VIBRANT);
+    }
+
+    /**
+     * Returns a dark and vibrant swatch from the palette. Might be null.
+     *
+     * @see Target#DARK_VIBRANT
+     */
+    @Nullable
+    public Palette.Swatch getDarkVibrantSwatch() {
+        return getSwatchForTarget(Target.DARK_VIBRANT);
+    }
+
+    /**
+     * Returns a muted swatch from the palette. Might be null.
+     *
+     * @see Target#MUTED
+     */
+    @Nullable
+    public Palette.Swatch getMutedSwatch() {
+        return getSwatchForTarget(Target.MUTED);
+    }
+
+    /**
+     * Returns a muted and light swatch from the palette. Might be null.
+     *
+     * @see Target#LIGHT_MUTED
+     */
+    @Nullable
+    public Palette.Swatch getLightMutedSwatch() {
+        return getSwatchForTarget(Target.LIGHT_MUTED);
+    }
+
+    /**
+     * Returns a muted and dark swatch from the palette. Might be null.
+     *
+     * @see Target#DARK_MUTED
+     */
+    @Nullable
+    public Palette.Swatch getDarkMutedSwatch() {
+        return getSwatchForTarget(Target.DARK_MUTED);
+    }
+
+    /**
+     * Returns the most vibrant color in the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     * @see #getVibrantSwatch()
+     */
+    @ColorInt
+    public int getVibrantColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.VIBRANT, defaultColor);
+    }
+
+    /**
+     * Returns a light and vibrant color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     * @see #getLightVibrantSwatch()
+     */
+    @ColorInt
+    public int getLightVibrantColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.LIGHT_VIBRANT, defaultColor);
+    }
+
+    /**
+     * Returns a dark and vibrant color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     * @see #getDarkVibrantSwatch()
+     */
+    @ColorInt
+    public int getDarkVibrantColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.DARK_VIBRANT, defaultColor);
+    }
+
+    /**
+     * Returns a muted color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     * @see #getMutedSwatch()
+     */
+    @ColorInt
+    public int getMutedColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.MUTED, defaultColor);
+    }
+
+    /**
+     * Returns a muted and light color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     * @see #getLightMutedSwatch()
+     */
+    @ColorInt
+    public int getLightMutedColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.LIGHT_MUTED, defaultColor);
+    }
+
+    /**
+     * Returns a muted and dark color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     * @see #getDarkMutedSwatch()
+     */
+    @ColorInt
+    public int getDarkMutedColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.DARK_MUTED, defaultColor);
+    }
+
+    /**
+     * Returns the selected swatch for the given target from the palette, or {@code null} if one
+     * could not be found.
+     */
+    @Nullable
+    public Palette.Swatch getSwatchForTarget(@NonNull final Target target) {
+        return mSelectedSwatches.get(target);
+    }
+
+    /**
+     * Returns the selected color for the given target from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     */
+    @ColorInt
+    public int getColorForTarget(@NonNull final Target target, @ColorInt final int defaultColor) {
+        Palette.Swatch swatch = getSwatchForTarget(target);
+        return swatch != null ? swatch.getRgb() : defaultColor;
+    }
+
+    /**
+     * Returns the dominant swatch from the palette.
+     *
+     * <p>The dominant swatch is defined as the swatch with the greatest population (frequency)
+     * within the palette.</p>
+     */
+    @Nullable
+    public Palette.Swatch getDominantSwatch() {
         return mDominantSwatch;
     }
 
-    @Nullable
-    private Swatch findDominantSwatch() {
-        int maxPop = Integer.MIN_VALUE;
-        Swatch maxSwatch = null;
+    /**
+     * Returns the color of the dominant swatch from the palette, as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     * @see #getDominantSwatch()
+     */
+    @ColorInt
+    public int getDominantColor(@ColorInt int defaultColor) {
+        return mDominantSwatch != null ? mDominantSwatch.getRgb() : defaultColor;
+    }
+
+    void generate() {
+        // We need to make sure that the scored targets are generated first. This is so that
+        // inherited targets have something to inherit from
+        for (int i = 0, count = mTargets.size(); i < count; i++) {
+            final Target target = mTargets.get(i);
+            target.normalizeWeights();
+            mSelectedSwatches.put(target, generateScoredTarget(target));
+        }
+        // We now clear out the used colors
+        mUsedColors.clear();
+    }
+
+    private Palette.Swatch generateScoredTarget(final Target target) {
+        final Palette.Swatch maxScoreSwatch = getMaxScoredSwatchForTarget(target);
+        if (maxScoreSwatch != null && target.isExclusive()) {
+            // If we have a swatch, and the target is exclusive, add the color to the used list
+            mUsedColors.append(maxScoreSwatch.getRgb(), true);
+        }
+        return maxScoreSwatch;
+    }
+
+    private Palette.Swatch getMaxScoredSwatchForTarget(final Target target) {
+        float maxScore = 0;
+        Palette.Swatch maxScoreSwatch = null;
         for (int i = 0, count = mSwatches.size(); i < count; i++) {
-            Swatch swatch = mSwatches.get(i);
+            final Palette.Swatch swatch = mSwatches.get(i);
+            if (shouldBeScoredForTarget(swatch, target)) {
+                final float score = generateScore(swatch, target);
+                if (maxScoreSwatch == null || score > maxScore) {
+                    maxScoreSwatch = swatch;
+                    maxScore = score;
+                }
+            }
+        }
+        return maxScoreSwatch;
+    }
+
+    private boolean shouldBeScoredForTarget(final Palette.Swatch swatch, final Target target) {
+        // Check whether the HSL values are within the correct ranges, and this color hasn't
+        // been used yet.
+        final float hsl[] = swatch.getHsl();
+        return hsl[1] >= target.getMinimumSaturation() && hsl[1] <= target.getMaximumSaturation()
+                && hsl[2] >= target.getMinimumLightness() && hsl[2] <= target.getMaximumLightness()
+                && !mUsedColors.get(swatch.getRgb());
+    }
+
+    private float generateScore(Palette.Swatch swatch, Target target) {
+        final float[] hsl = swatch.getHsl();
+
+        float saturationScore = 0;
+        float luminanceScore = 0;
+        float populationScore = 0;
+
+        final int maxPopulation = mDominantSwatch != null ? mDominantSwatch.getPopulation() : 1;
+
+        if (target.getSaturationWeight() > 0) {
+            saturationScore = target.getSaturationWeight()
+                    * (1f - Math.abs(hsl[1] - target.getTargetSaturation()));
+        }
+        if (target.getLightnessWeight() > 0) {
+            luminanceScore = target.getLightnessWeight()
+                    * (1f - Math.abs(hsl[2] - target.getTargetLightness()));
+        }
+        if (target.getPopulationWeight() > 0) {
+            populationScore = target.getPopulationWeight()
+                    * (swatch.getPopulation() / (float) maxPopulation);
+        }
+
+        return saturationScore + luminanceScore + populationScore;
+    }
+
+    private Palette.Swatch findDominantSwatch() {
+        int maxPop = Integer.MIN_VALUE;
+        Palette.Swatch maxSwatch = null;
+        for (int i = 0, count = mSwatches.size(); i < count; i++) {
+            Palette.Swatch swatch = mSwatches.get(i);
             if (swatch.getPopulation() > maxPop) {
                 maxSwatch = swatch;
                 maxPop = swatch.getPopulation();
@@ -122,42 +433,148 @@
         return maxSwatch;
     }
 
+    private static float[] copyHslValues(Palette.Swatch color) {
+        final float[] newHsl = new float[3];
+        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
+        return newHsl;
+    }
+
     /**
      * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
-     * by
-     * calling {@link #getInt()}.
+     * by calling {@link #getRgb()}.
      */
-    public static class Swatch {
-        private final Color mColor;
+    public static final class Swatch {
+        private final int mRed, mGreen, mBlue;
+        private final int mRgb;
         private final int mPopulation;
 
+        private boolean mGeneratedTextColors;
+        private int mTitleTextColor;
+        private int mBodyTextColor;
 
-        public Swatch(@ColorInt int colorInt, int population) {
-            mColor = Color.valueOf(colorInt);
+        private float[] mHsl;
+
+        public Swatch(@ColorInt int color, int population) {
+            mRed = Color.red(color);
+            mGreen = Color.green(color);
+            mBlue = Color.blue(color);
+            mRgb = color;
             mPopulation = population;
         }
 
-        /** @return this swatch's RGB color value */
-        @ColorInt
-        public int getInt() {
-            return mColor.toArgb();
+        Swatch(int red, int green, int blue, int population) {
+            mRed = red;
+            mGreen = green;
+            mBlue = blue;
+            mRgb = Color.rgb(red, green, blue);
+            mPopulation = population;
         }
 
-        /** @return the number of pixels represented by this swatch */
+        Swatch(float[] hsl, int population) {
+            this(ColorUtils.HSLToColor(hsl), population);
+            mHsl = hsl;
+        }
+
+        /**
+         * @return this swatch's RGB color value
+         */
+        @ColorInt
+        public int getRgb() {
+            return mRgb;
+        }
+
+        /**
+         * Return this swatch's HSL values.
+         *     hsv[0] is Hue [0 .. 360)
+         *     hsv[1] is Saturation [0...1]
+         *     hsv[2] is Lightness [0...1]
+         */
+        public float[] getHsl() {
+            if (mHsl == null) {
+                mHsl = new float[3];
+            }
+            ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl);
+            return mHsl;
+        }
+
+        /**
+         * @return the number of pixels represented by this swatch
+         */
         public int getPopulation() {
             return mPopulation;
         }
 
+        /**
+         * Returns an appropriate color to use for any 'title' text which is displayed over this
+         * {@link Palette.Swatch}'s color. This color is guaranteed to have sufficient contrast.
+         */
+        @ColorInt
+        public int getTitleTextColor() {
+            ensureTextColorsGenerated();
+            return mTitleTextColor;
+        }
+
+        /**
+         * Returns an appropriate color to use for any 'body' text which is displayed over this
+         * {@link Palette.Swatch}'s color. This color is guaranteed to have sufficient contrast.
+         */
+        @ColorInt
+        public int getBodyTextColor() {
+            ensureTextColorsGenerated();
+            return mBodyTextColor;
+        }
+
+        private void ensureTextColorsGenerated() {
+            if (!mGeneratedTextColors) {
+                // First check white, as most colors will be dark
+                final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha(
+                        Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT);
+                final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha(
+                        Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT);
+
+                if (lightBodyAlpha != -1 && lightTitleAlpha != -1) {
+                    // If we found valid light values, use them and return
+                    mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha);
+                    mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha);
+                    mGeneratedTextColors = true;
+                    return;
+                }
+
+                final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha(
+                        Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT);
+                final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha(
+                        Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT);
+
+                if (darkBodyAlpha != -1 && darkTitleAlpha != -1) {
+                    // If we found valid dark values, use them and return
+                    mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
+                    mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
+                    mGeneratedTextColors = true;
+                    return;
+                }
+
+                // If we reach here then we can not find title and body values which use the same
+                // lightness, we need to use mismatched values
+                mBodyTextColor = lightBodyAlpha != -1
+                        ? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha)
+                        : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
+                mTitleTextColor = lightTitleAlpha != -1
+                        ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha)
+                        : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
+                mGeneratedTextColors = true;
+            }
+        }
+
         @Override
         public String toString() {
             return new StringBuilder(getClass().getSimpleName())
-                    .append(" [")
-                    .append(mColor)
+                    .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
+                    .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
+                    .append(" [Population: ").append(mPopulation).append(']')
+                    .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor()))
                     .append(']')
-                    .append(" [Population: ")
-                    .append(mPopulation)
-                    .append(']')
-                    .toString();
+                    .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor()))
+                    .append(']').toString();
         }
 
         @Override
@@ -169,168 +586,243 @@
                 return false;
             }
 
-            Swatch swatch = (Swatch) o;
-            return mPopulation == swatch.mPopulation && mColor.toArgb() == swatch.mColor.toArgb();
+            Palette.Swatch
+                    swatch = (Palette.Swatch) o;
+            return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb;
         }
 
         @Override
         public int hashCode() {
-            return 31 * mColor.toArgb() + mPopulation;
+            return 31 * mRgb + mPopulation;
         }
     }
 
-    /** Builder class for generating {@link Palette} instances. */
-    public static class Builder {
-        @Nullable
-        private final List<Swatch> mSwatches;
-        @Nullable
+    /**
+     * Builder class for generating {@link Palette} instances.
+     */
+    public static final class Builder {
+        private final List<Palette.Swatch> mSwatches;
         private final Bitmap mBitmap;
-        @Nullable
-        private Quantizer mQuantizer = new ColorCutQuantizer();
 
+        private final List<Target> mTargets = new ArrayList<>();
 
         private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
         private int mResizeArea = DEFAULT_RESIZE_BITMAP_AREA;
         private int mResizeMaxDimension = -1;
 
-        @Nullable
+        private final List<Palette.Filter> mFilters = new ArrayList<>();
         private Rect mRegion;
 
-        /** Construct a new {@link Builder} using a source {@link Bitmap} */
-        public Builder(@NonNull Bitmap bitmap, @NonNull Quantizer quantizer) {
+        private Quantizer mQuantizer;
+
+        /**
+         * Construct a new {@link Palette.Builder} using a source {@link Bitmap}
+         */
+        public Builder(Bitmap bitmap) {
             if (bitmap == null || bitmap.isRecycled()) {
                 throw new IllegalArgumentException("Bitmap is not valid");
             }
-            mSwatches = null;
+            mFilters.add(DEFAULT_FILTER);
             mBitmap = bitmap;
-            mQuantizer = quantizer == null ? new ColorCutQuantizer() : quantizer;
+            mSwatches = null;
+
+            // Add the default targets
+            mTargets.add(Target.LIGHT_VIBRANT);
+            mTargets.add(Target.VIBRANT);
+            mTargets.add(Target.DARK_VIBRANT);
+            mTargets.add(Target.LIGHT_MUTED);
+            mTargets.add(Target.MUTED);
+            mTargets.add(Target.DARK_MUTED);
         }
 
         /**
-         * Construct a new {@link Builder} using a list of {@link Swatch} instances. Typically only
-         * used
-         * for testing.
+         * Construct a new {@link Palette.Builder} using a list of {@link Palette.Swatch} instances.
+         * Typically only used for testing.
          */
-        public Builder(@NonNull List<Swatch> swatches) {
+        public Builder(List<Palette.Swatch> swatches) {
             if (swatches == null || swatches.isEmpty()) {
                 throw new IllegalArgumentException("List of Swatches is not valid");
             }
+            mFilters.add(DEFAULT_FILTER);
             mSwatches = swatches;
             mBitmap = null;
-            mQuantizer = null;
         }
 
         /**
-         * Set the maximum number of colors to use in the quantization step when using a {@link
-         * android.graphics.Bitmap} as the source.
-         *
-         * <p>Good values for depend on the source image type. For landscapes, good values are in
-         * the
-         * range 10-16. For images which are largely made up of people's faces then this value
-         * should be
-         * increased to ~24.
+         * Set the maximum number of colors to use in the quantization step when using a
+         * {@link android.graphics.Bitmap} as the source.
+         * <p>
+         * Good values for depend on the source image type. For landscapes, good values are in
+         * the range 10-16. For images which are largely made up of people's faces then this
+         * value should be increased to ~24.
          */
         @NonNull
-        public Builder maximumColorCount(int colors) {
+        public Palette.Builder maximumColorCount(int colors) {
             mMaxColors = colors;
             return this;
         }
 
         /**
-         * Set the resize value when using a {@link android.graphics.Bitmap} as the source. If the
-         * bitmap's largest dimension is greater than the value specified, then the bitmap will be
-         * resized so that its largest dimension matches {@code maxDimension}. If the bitmap is
-         * smaller
-         * or equal, the original is used as-is.
+         * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
+         * If the bitmap's largest dimension is greater than the value specified, then the bitmap
+         * will be resized so that its largest dimension matches {@code maxDimension}. If the
+         * bitmap is smaller or equal, the original is used as-is.
+         *
+         * @deprecated Using {@link #resizeBitmapArea(int)} is preferred since it can handle
+         * abnormal aspect ratios more gracefully.
          *
          * @param maxDimension the number of pixels that the max dimension should be scaled down to,
-         *                     or
-         *                     any value <= 0 to disable resizing.
-         * @deprecated Using {@link #resizeBitmapArea(int)} is preferred since it can handle
-         * abnormal
-         * aspect ratios more gracefully.
+         *                     or any value <= 0 to disable resizing.
          */
         @NonNull
         @Deprecated
-        public Builder resizeBitmapSize(int maxDimension) {
+        public Palette.Builder resizeBitmapSize(final int maxDimension) {
             mResizeMaxDimension = maxDimension;
             mResizeArea = -1;
             return this;
         }
 
         /**
-         * Set the resize value when using a {@link android.graphics.Bitmap} as the source. If the
-         * bitmap's area is greater than the value specified, then the bitmap will be resized so
-         * that
-         * its area matches {@code area}. If the bitmap is smaller or equal, the original is used
-         * as-is.
-         *
-         * <p>This value has a large effect on the processing time. The larger the resized image is,
-         * the
-         * greater time it will take to generate the palette. The smaller the image is, the more
-         * detail
-         * is lost in the resulting image and thus less precision for color selection.
+         * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
+         * If the bitmap's area is greater than the value specified, then the bitmap
+         * will be resized so that its area matches {@code area}. If the
+         * bitmap is smaller or equal, the original is used as-is.
+         * <p>
+         * This value has a large effect on the processing time. The larger the resized image is,
+         * the greater time it will take to generate the palette. The smaller the image is, the
+         * more detail is lost in the resulting image and thus less precision for color selection.
          *
          * @param area the number of pixels that the intermediary scaled down Bitmap should cover,
-         *             or
-         *             any value <= 0 to disable resizing.
+         *             or any value <= 0 to disable resizing.
          */
         @NonNull
-        public Builder resizeBitmapArea(int area) {
+        public Palette.Builder resizeBitmapArea(final int area) {
             mResizeArea = area;
             mResizeMaxDimension = -1;
             return this;
         }
 
         /**
+         * Clear all added filters. This includes any default filters added automatically by
+         * {@link Palette}.
+         */
+        @NonNull
+        public Palette.Builder clearFilters() {
+            mFilters.clear();
+            return this;
+        }
+
+        /**
+         * Add a filter to be able to have fine grained control over which colors are
+         * allowed in the resulting palette.
+         *
+         * @param filter filter to add.
+         */
+        @NonNull
+        public Palette.Builder addFilter(
+                Palette.Filter filter) {
+            if (filter != null) {
+                mFilters.add(filter);
+            }
+            return this;
+        }
+
+        /**
+         * Set a specific quantization algorithm. {@link ColorCutQuantizer} will
+         * be used if unspecified.
+         *
+         * @param quantizer Quantizer implementation.
+         */
+        @NonNull
+        public Palette.Builder setQuantizer(Quantizer quantizer) {
+            mQuantizer = quantizer;
+            return this;
+        }
+
+        /**
          * Set a region of the bitmap to be used exclusively when calculating the palette.
+         * <p>This only works when the original input is a {@link Bitmap}.</p>
          *
-         * <p>This only works when the original input is a {@link Bitmap}.
-         *
-         * @param left   The left side of the rectangle used for the region.
-         * @param top    The top of the rectangle used for the region.
-         * @param right  The right side of the rectangle used for the region.
+         * @param left The left side of the rectangle used for the region.
+         * @param top The top of the rectangle used for the region.
+         * @param right The right side of the rectangle used for the region.
          * @param bottom The bottom of the rectangle used for the region.
          */
         @NonNull
-        public Builder setRegion(@Px int left, @Px int top, @Px int right, @Px int bottom) {
+        public Palette.Builder setRegion(int left, int top, int right, int bottom) {
             if (mBitmap != null) {
                 if (mRegion == null) mRegion = new Rect();
                 // Set the Rect to be initially the whole Bitmap
                 mRegion.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
                 // Now just get the intersection with the region
                 if (!mRegion.intersect(left, top, right, bottom)) {
-                    throw new IllegalArgumentException(
-                            "The given region must intersect with " + "the Bitmap's dimensions.");
+                    throw new IllegalArgumentException("The given region must intersect with "
+                            + "the Bitmap's dimensions.");
                 }
             }
             return this;
         }
 
-        /** Clear any previously region set via {@link #setRegion(int, int, int, int)}. */
+        /**
+         * Clear any previously region set via {@link #setRegion(int, int, int, int)}.
+         */
         @NonNull
-        public Builder clearRegion() {
+        public Palette.Builder clearRegion() {
             mRegion = null;
             return this;
         }
 
+        /**
+         * Add a target profile to be generated in the palette.
+         *
+         * <p>You can retrieve the result via {@link Palette#getSwatchForTarget(Target)}.</p>
+         */
+        @NonNull
+        public Palette.Builder addTarget(@NonNull final Target target) {
+            if (!mTargets.contains(target)) {
+                mTargets.add(target);
+            }
+            return this;
+        }
 
-        /** Generate and return the {@link Palette} synchronously. */
+        /**
+         * Clear all added targets. This includes any default targets added automatically by
+         * {@link Palette}.
+         */
+        @NonNull
+        public Palette.Builder clearTargets() {
+            if (mTargets != null) {
+                mTargets.clear();
+            }
+            return this;
+        }
+
+        /**
+         * Generate and return the {@link Palette} synchronously.
+         */
         @NonNull
         public Palette generate() {
-            List<Swatch> swatches;
+            final TimingLogger logger = LOG_TIMINGS
+                    ? new TimingLogger(LOG_TAG, "Generation")
+                    : null;
+
+            List<Palette.Swatch> swatches;
 
             if (mBitmap != null) {
                 // We have a Bitmap so we need to use quantization to reduce the number of colors
 
                 // First we'll scale down the bitmap if needed
-                Bitmap bitmap = scaleBitmapDown(mBitmap);
+                final Bitmap bitmap = scaleBitmapDown(mBitmap);
 
-                Rect region = mRegion;
+                if (logger != null) {
+                    logger.addSplit("Processed Bitmap");
+                }
+
+                final Rect region = mRegion;
                 if (bitmap != mBitmap && region != null) {
                     // If we have a scaled bitmap and a selected region, we need to scale down the
                     // region to match the new scale
-                    double scale = bitmap.getWidth() / (double) mBitmap.getWidth();
+                    final double scale = bitmap.getWidth() / (double) mBitmap.getWidth();
                     region.left = (int) Math.floor(region.left * scale);
                     region.top = (int) Math.floor(region.top * scale);
                     region.right = Math.min((int) Math.ceil(region.right * scale),
@@ -340,47 +832,54 @@
                 }
 
                 // Now generate a quantizer from the Bitmap
+                if (mQuantizer == null) {
+                    mQuantizer = new ColorCutQuantizer();
+                }
+                mQuantizer.quantize(getPixelsFromBitmap(bitmap),
+                            mMaxColors, mFilters.isEmpty() ? null :
+                            mFilters.toArray(new Palette.Filter[mFilters.size()]));
 
-                mQuantizer.quantize(
-                        getPixelsFromBitmap(bitmap),
-                        mMaxColors);
                 // If created a new bitmap, recycle it
                 if (bitmap != mBitmap) {
                     bitmap.recycle();
                 }
+
                 swatches = mQuantizer.getQuantizedColors();
-            } else if (mSwatches != null) {
+
+                if (logger != null) {
+                    logger.addSplit("Color quantization completed");
+                }
+            } else {
                 // Else we're using the provided swatches
                 swatches = mSwatches;
-            } else {
-                // The constructors enforce either a bitmap or swatches are present.
-                throw new AssertionError();
             }
 
             // Now create a Palette instance
-            Palette p = new Palette(swatches);
+            final Palette p = new Palette(swatches, mTargets);
             // And make it generate itself
+            p.generate();
+
+            if (logger != null) {
+                logger.addSplit("Created Palette");
+                logger.dumpToLog();
+            }
 
             return p;
         }
 
         /**
-         * Generate the {@link Palette} asynchronously. The provided listener's {@link
-         * PaletteAsyncListener#onGenerated} method will be called with the palette when generated.
-         *
-         * @deprecated Use the standard <code>java.util.concurrent</code> or <a
-         * href="https://developer.android.com/topic/libraries/architecture/coroutines">Kotlin
-         * concurrency utilities</a> to call {@link #generate()} instead.
+         * Generate the {@link Palette} asynchronously. The provided listener's
+         * {@link Palette.PaletteAsyncListener#onGenerated} method will be called with the palette when
+         * generated.
          */
         @NonNull
-        @Deprecated
-        public android.os.AsyncTask<Bitmap, Void, Palette> generate(
-                @NonNull PaletteAsyncListener listener) {
-            assert (listener != null);
+        public AsyncTask<Bitmap, Void, Palette> generate(final Palette.PaletteAsyncListener listener) {
+            if (listener == null) {
+                throw new IllegalArgumentException("listener can not be null");
+            }
 
-            return new android.os.AsyncTask<Bitmap, Void, Palette>() {
+            return new AsyncTask<Bitmap, Void, Palette>() {
                 @Override
-                @Nullable
                 protected Palette doInBackground(Bitmap... params) {
                     try {
                         return generate();
@@ -391,16 +890,16 @@
                 }
 
                 @Override
-                protected void onPostExecute(@Nullable Palette colorExtractor) {
+                protected void onPostExecute(Palette colorExtractor) {
                     listener.onGenerated(colorExtractor);
                 }
-            }.executeOnExecutor(android.os.AsyncTask.THREAD_POOL_EXECUTOR, mBitmap);
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mBitmap);
         }
 
         private int[] getPixelsFromBitmap(Bitmap bitmap) {
-            int bitmapWidth = bitmap.getWidth();
-            int bitmapHeight = bitmap.getHeight();
-            int[] pixels = new int[bitmapWidth * bitmapHeight];
+            final int bitmapWidth = bitmap.getWidth();
+            final int bitmapHeight = bitmap.getHeight();
+            final int[] pixels = new int[bitmapWidth * bitmapHeight];
             bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight);
 
             if (mRegion == null) {
@@ -409,34 +908,32 @@
             } else {
                 // If we do have a region, lets create a subset array containing only the region's
                 // pixels
-                int regionWidth = mRegion.width();
-                int regionHeight = mRegion.height();
+                final int regionWidth = mRegion.width();
+                final int regionHeight = mRegion.height();
                 // pixels contains all of the pixels, so we need to iterate through each row and
                 // copy the regions pixels into a new smaller array
-                int[] subsetPixels = new int[regionWidth * regionHeight];
+                final int[] subsetPixels = new int[regionWidth * regionHeight];
                 for (int row = 0; row < regionHeight; row++) {
-                    System.arraycopy(
-                            pixels,
-                            ((row + mRegion.top) * bitmapWidth) + mRegion.left,
-                            subsetPixels,
-                            row * regionWidth,
-                            regionWidth);
+                    System.arraycopy(pixels, ((row + mRegion.top) * bitmapWidth) + mRegion.left,
+                            subsetPixels, row * regionWidth, regionWidth);
                 }
                 return subsetPixels;
             }
         }
 
-        /** Scale the bitmap down as needed. */
-        private Bitmap scaleBitmapDown(Bitmap bitmap) {
+        /**
+         * Scale the bitmap down as needed.
+         */
+        private Bitmap scaleBitmapDown(final Bitmap bitmap) {
             double scaleRatio = -1;
 
             if (mResizeArea > 0) {
-                int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
+                final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
                 if (bitmapArea > mResizeArea) {
                     scaleRatio = Math.sqrt(mResizeArea / (double) bitmapArea);
                 }
             } else if (mResizeMaxDimension > 0) {
-                int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+                final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
                 if (maxDimension > mResizeMaxDimension) {
                     scaleRatio = mResizeMaxDimension / (double) maxDimension;
                 }
@@ -447,13 +944,11 @@
                 return bitmap;
             }
 
-            return Bitmap.createScaledBitmap(
-                    bitmap,
+            return Bitmap.createScaledBitmap(bitmap,
                     (int) Math.ceil(bitmap.getWidth() * scaleRatio),
                     (int) Math.ceil(bitmap.getHeight() * scaleRatio),
                     false);
         }
-
     }
 
     /**
@@ -466,7 +961,9 @@
          *
          * @param rgb the color in RGB888.
          * @param hsl HSL representation of the color.
+         *
          * @return true if the color is allowed, false if not.
+         *
          * @see Palette.Builder#addFilter(Palette.Filter)
          */
         boolean isAllowed(int rgb, float[] hsl);
@@ -507,4 +1004,3 @@
         }
     };
 }
-
diff --git a/core/java/com/android/internal/graphics/palette/Quantizer.java b/core/java/com/android/internal/graphics/palette/Quantizer.java
index a219ea3..db60f2e 100644
--- a/core/java/com/android/internal/graphics/palette/Quantizer.java
+++ b/core/java/com/android/internal/graphics/palette/Quantizer.java
@@ -22,15 +22,6 @@
  * Definition of an algorithm that receives pixels and outputs a list of colors.
  */
 public interface Quantizer {
-    /**
-     * Create colors representative of the colors present in pixels.
-     * @param pixels Set of ARGB representation of a color.
-     * @param maxColors number of colors to generate
-     */
-    void quantize(int[] pixels, int maxColors);
-
-    /**
-     * List of colors generated by previous call to quantize.
-     */
+    void quantize(final int[] pixels, final int maxColors, final Palette.Filter[] filters);
     List<Palette.Swatch> getQuantizedColors();
 }
diff --git a/core/java/com/android/internal/graphics/palette/Target.java b/core/java/com/android/internal/graphics/palette/Target.java
index 96e7faa..0540d80 100644
--- a/core/java/com/android/internal/graphics/palette/Target.java
+++ b/core/java/com/android/internal/graphics/palette/Target.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,234 +16,368 @@
 
 package com.android.internal.graphics.palette;
 
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-
-/**
- * A class which allows custom selection of colors in a {@link Palette}'s generation. Instances can
- * be created via the {@link Builder} class.
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
+import android.annotation.FloatRange;
+
+/**
+ * Copied from: frameworks/support/v7/palette/src/main/java/android/support/v7/graphics/Target.java
+ *
+ * A class which allows custom selection of colors in a {@link Palette}'s generation. Instances
+ * can be created via the {@link android.support.v7.graphics.Target.Builder} class.
+ *
+ * <p>To use the target, use the {@link Palette.Builder#addTarget(Target)} API when building a
+ * Palette.</p>
+ */
 public final class Target {
-    private static final float WEIGHT_CHROMA = 0.5f;
-    private static final float WEIGHT_RELATIVE_LUMINANCE = 0.5f;
-    private static final float WEIGHT_POPULATION = 0.3f;
-    private static final float WEIGHT_HUE = 0.2f;
 
-    // Arbitrarily chosen, except max - CAM16 chroma has a ceiling of 130, based on unit testing.
-    private static final float DEFAULT_CHROMA_MIN = 0.f;
-    private static final float DEFAULT_CHROMA_MAX = 130.f;
-    private static final float DEFAULT_CHROMA_TARGET = 30.f;
+    private static final float TARGET_DARK_LUMA = 0.26f;
+    private static final float MAX_DARK_LUMA = 0.45f;
 
-    private float mTargetRelativeLuminance = -1.0f;
-    private float mChromaWeight;
-    private float mChromaTarget;
-    private float mChromaMin;
-    private float mChromaMax;
-    private float mRelativeLuminanceWeight;
-    private float mPopulationWeight;
-    private float mHueWeight;
-    private float mTargetHue;
+    private static final float MIN_LIGHT_LUMA = 0.55f;
+    private static final float TARGET_LIGHT_LUMA = 0.74f;
+
+    private static final float MIN_NORMAL_LUMA = 0.3f;
+    private static final float TARGET_NORMAL_LUMA = 0.5f;
+    private static final float MAX_NORMAL_LUMA = 0.7f;
+
+    private static final float TARGET_MUTED_SATURATION = 0.3f;
+    private static final float MAX_MUTED_SATURATION = 0.4f;
+
+    private static final float TARGET_VIBRANT_SATURATION = 1f;
+    private static final float MIN_VIBRANT_SATURATION = 0.35f;
+
+    private static final float WEIGHT_SATURATION = 0.24f;
+    private static final float WEIGHT_LUMA = 0.52f;
+    private static final float WEIGHT_POPULATION = 0.24f;
+
+    static final int INDEX_MIN = 0;
+    static final int INDEX_TARGET = 1;
+    static final int INDEX_MAX = 2;
+
+    static final int INDEX_WEIGHT_SAT = 0;
+    static final int INDEX_WEIGHT_LUMA = 1;
+    static final int INDEX_WEIGHT_POP = 2;
+
+    /**
+     * A target which has the characteristics of a vibrant color which is light in luminance.
+     */
+    public static final Target LIGHT_VIBRANT;
+
+    /**
+     * A target which has the characteristics of a vibrant color which is neither light or dark.
+     */
+    public static final Target VIBRANT;
+
+    /**
+     * A target which has the characteristics of a vibrant color which is dark in luminance.
+     */
+    public static final Target DARK_VIBRANT;
+
+    /**
+     * A target which has the characteristics of a muted color which is light in luminance.
+     */
+    public static final Target LIGHT_MUTED;
+
+    /**
+     * A target which has the characteristics of a muted color which is neither light or dark.
+     */
+    public static final Target MUTED;
+
+    /**
+     * A target which has the characteristics of a muted color which is dark in luminance.
+     */
+    public static final Target DARK_MUTED;
+
+    static {
+        LIGHT_VIBRANT = new Target();
+        setDefaultLightLightnessValues(LIGHT_VIBRANT);
+        setDefaultVibrantSaturationValues(LIGHT_VIBRANT);
+
+        VIBRANT = new Target();
+        setDefaultNormalLightnessValues(VIBRANT);
+        setDefaultVibrantSaturationValues(VIBRANT);
+
+        DARK_VIBRANT = new Target();
+        setDefaultDarkLightnessValues(DARK_VIBRANT);
+        setDefaultVibrantSaturationValues(DARK_VIBRANT);
+
+        LIGHT_MUTED = new Target();
+        setDefaultLightLightnessValues(LIGHT_MUTED);
+        setDefaultMutedSaturationValues(LIGHT_MUTED);
+
+        MUTED = new Target();
+        setDefaultNormalLightnessValues(MUTED);
+        setDefaultMutedSaturationValues(MUTED);
+
+        DARK_MUTED = new Target();
+        setDefaultDarkLightnessValues(DARK_MUTED);
+        setDefaultMutedSaturationValues(DARK_MUTED);
+    }
+
+    final float[] mSaturationTargets = new float[3];
+    final float[] mLightnessTargets = new float[3];
+    final float[] mWeights = new float[3];
+    boolean mIsExclusive = true; // default to true
 
     Target() {
-        mChromaMax = DEFAULT_CHROMA_MAX;
-        mChromaMin = DEFAULT_CHROMA_MIN;
-        mChromaTarget = DEFAULT_CHROMA_TARGET;
-        mChromaWeight =   WEIGHT_CHROMA;
-        mRelativeLuminanceWeight = WEIGHT_RELATIVE_LUMINANCE;
-        mPopulationWeight = WEIGHT_POPULATION;
-        mHueWeight = WEIGHT_HUE;
+        setTargetDefaultValues(mSaturationTargets);
+        setTargetDefaultValues(mLightnessTargets);
+        setDefaultWeights();
     }
 
-    Target(@NonNull Target from) {
-        mTargetRelativeLuminance = from.mTargetRelativeLuminance;
-        mChromaWeight = from.mChromaWeight;
-        mRelativeLuminanceWeight = from.mRelativeLuminanceWeight;
-        mPopulationWeight = from.mPopulationWeight;
-        mHueWeight = from.mHueWeight;
-        mChromaTarget = from.mChromaTarget;
-        mChromaMin = from.mChromaMin;
-        mChromaMax = from.mChromaMax;
-    }
-
-    /** The relative luminance value for this target. */
-    @FloatRange(from = 0, to = 100)
-    public float getTargetRelativeLuminance() {
-        return mTargetRelativeLuminance;
-    }
-
-    /** The relative luminance value for this target. */
-    @FloatRange(from = 0, to = 100)
-    public float getTargetPerceptualLuminance() {
-        return Contrast.yToLstar(mTargetRelativeLuminance);
-    }
-
-    /** The minimum chroma value for this target. */
-    @FloatRange(from = 0, to = 100)
-    public float getMinimumChroma() {
-        return mChromaMin;
-    }
-
-    /** The target chroma value for this target. */
-    @FloatRange(from = 0, to = 100)
-    public float getTargetChroma() {
-        return mChromaTarget;
-    }
-
-    /** The maximum chroma value for this target. */
-    @FloatRange(from = 0, to = 130)
-    public float getMaximumChroma() {
-        return mChromaMax;
-    }
-
-    /** The target hue value for this target. */
-    @FloatRange(from = 0, to = 100)
-    public float getTargetHue() {
-        return mTargetHue;
+    Target(Target from) {
+        System.arraycopy(from.mSaturationTargets, 0, mSaturationTargets, 0,
+                mSaturationTargets.length);
+        System.arraycopy(from.mLightnessTargets, 0, mLightnessTargets, 0,
+                mLightnessTargets.length);
+        System.arraycopy(from.mWeights, 0, mWeights, 0, mWeights.length);
     }
 
     /**
-     * Returns the weight of importance that this target places on a color's chroma within the
-     * image.
-     *
-     * <p>The larger the weight, relative to the other weights, the more important that a color
-     * being
-     * close to the target value has on selection.
-     *
-     * @see #getTargetChroma()
+     * The minimum saturation value for this target.
      */
-    public float getChromaWeight() {
-        return mChromaWeight;
+    @FloatRange(from = 0, to = 1)
+    public float getMinimumSaturation() {
+        return mSaturationTargets[INDEX_MIN];
     }
 
     /**
-     * Returns the weight of importance that this target places on a color's lightness within the
-     * image.
+     * The target saturation value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getTargetSaturation() {
+        return mSaturationTargets[INDEX_TARGET];
+    }
+
+    /**
+     * The maximum saturation value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getMaximumSaturation() {
+        return mSaturationTargets[INDEX_MAX];
+    }
+
+    /**
+     * The minimum lightness value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getMinimumLightness() {
+        return mLightnessTargets[INDEX_MIN];
+    }
+
+    /**
+     * The target lightness value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getTargetLightness() {
+        return mLightnessTargets[INDEX_TARGET];
+    }
+
+    /**
+     * The maximum lightness value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getMaximumLightness() {
+        return mLightnessTargets[INDEX_MAX];
+    }
+
+    /**
+     * Returns the weight of importance that this target places on a color's saturation within
+     * the image.
      *
      * <p>The larger the weight, relative to the other weights, the more important that a color
-     * being
-     * close to the target value has on selection.
+     * being close to the target value has on selection.</p>
      *
-     * @see #getTargetRelativeLuminance()
+     * @see #getTargetSaturation()
+     */
+    public float getSaturationWeight() {
+        return mWeights[INDEX_WEIGHT_SAT];
+    }
+
+    /**
+     * Returns the weight of importance that this target places on a color's lightness within
+     * the image.
+     *
+     * <p>The larger the weight, relative to the other weights, the more important that a color
+     * being close to the target value has on selection.</p>
+     *
+     * @see #getTargetLightness()
      */
     public float getLightnessWeight() {
-        return mRelativeLuminanceWeight;
+        return mWeights[INDEX_WEIGHT_LUMA];
     }
 
     /**
-     * Returns the weight of importance that this target places on a color's population within the
-     * image.
+     * Returns the weight of importance that this target places on a color's population within
+     * the image.
      *
-     * <p>The larger the weight, relative to the other weights, the more important that a color's
-     * population being close to the most populous has on selection.
+     * <p>The larger the weight, relative to the other weights, the more important that a
+     * color's population being close to the most populous has on selection.</p>
      */
     public float getPopulationWeight() {
-        return mPopulationWeight;
+        return mWeights[INDEX_WEIGHT_POP];
     }
 
     /**
-     * Returns the weight of importance that this target places on a color's hue.
+     * Returns whether any color selected for this target is exclusive for this target only.
      *
-     * <p>The larger the weight, relative to the other weights, the more important that a color's
-     * hue being close to the desired hue has on selection.
+     * <p>If false, then the color can be selected for other targets.</p>
      */
-    public float getHueWeight() {
-        return mHueWeight;
+    public boolean isExclusive() {
+        return mIsExclusive;
     }
 
+    private static void setTargetDefaultValues(final float[] values) {
+        values[INDEX_MIN] = 0f;
+        values[INDEX_TARGET] = 0.5f;
+        values[INDEX_MAX] = 1f;
+    }
 
-    /** Builder class for generating custom {@link Target} instances. */
-    public static class Builder {
+    private void setDefaultWeights() {
+        mWeights[INDEX_WEIGHT_SAT] = WEIGHT_SATURATION;
+        mWeights[INDEX_WEIGHT_LUMA] = WEIGHT_LUMA;
+        mWeights[INDEX_WEIGHT_POP] = WEIGHT_POPULATION;
+    }
+
+    void normalizeWeights() {
+        float sum = 0;
+        for (int i = 0, z = mWeights.length; i < z; i++) {
+            float weight = mWeights[i];
+            if (weight > 0) {
+                sum += weight;
+            }
+        }
+        if (sum != 0) {
+            for (int i = 0, z = mWeights.length; i < z; i++) {
+                if (mWeights[i] > 0) {
+                    mWeights[i] /= sum;
+                }
+            }
+        }
+    }
+
+    private static void setDefaultDarkLightnessValues(Target target) {
+        target.mLightnessTargets[INDEX_TARGET] = TARGET_DARK_LUMA;
+        target.mLightnessTargets[INDEX_MAX] = MAX_DARK_LUMA;
+    }
+
+    private static void setDefaultNormalLightnessValues(Target target) {
+        target.mLightnessTargets[INDEX_MIN] = MIN_NORMAL_LUMA;
+        target.mLightnessTargets[INDEX_TARGET] = TARGET_NORMAL_LUMA;
+        target.mLightnessTargets[INDEX_MAX] = MAX_NORMAL_LUMA;
+    }
+
+    private static void setDefaultLightLightnessValues(Target target) {
+        target.mLightnessTargets[INDEX_MIN] = MIN_LIGHT_LUMA;
+        target.mLightnessTargets[INDEX_TARGET] = TARGET_LIGHT_LUMA;
+    }
+
+    private static void setDefaultVibrantSaturationValues(Target target) {
+        target.mSaturationTargets[INDEX_MIN] = MIN_VIBRANT_SATURATION;
+        target.mSaturationTargets[INDEX_TARGET] = TARGET_VIBRANT_SATURATION;
+    }
+
+    private static void setDefaultMutedSaturationValues(Target target) {
+        target.mSaturationTargets[INDEX_TARGET] = TARGET_MUTED_SATURATION;
+        target.mSaturationTargets[INDEX_MAX] = MAX_MUTED_SATURATION;
+    }
+
+    /**
+     * Builder class for generating custom {@link Target} instances.
+     */
+    public final static class Builder {
         private final Target mTarget;
 
-        /** Create a new {@link Target} builder from scratch. */
+        /**
+         * Create a new {@link Target} builder from scratch.
+         */
         public Builder() {
             mTarget = new Target();
         }
 
-        /** Create a new builder based on an existing {@link Target}. */
-        public Builder(@NonNull Target target) {
+        /**
+         * Create a new builder based on an existing {@link Target}.
+         */
+        public Builder(Target target) {
             mTarget = new Target(target);
         }
 
-        /** Set the minimum chroma value for this target. */
-        @NonNull
-        public Builder setMinimumChroma(@FloatRange(from = 0, to = 100) float value) {
-            mTarget.mChromaMin = value;
-            return this;
-        }
-
-        /** Set the target/ideal chroma value for this target. */
-        @NonNull
-        public Builder setTargetChroma(@FloatRange(from = 0, to = 100) float value) {
-            mTarget.mChromaTarget = value;
-            return this;
-        }
-
-        /** Set the maximum chroma value for this target. */
-        @NonNull
-        public Builder setMaximumChroma(@FloatRange(from = 0, to = 100) float value) {
-            mTarget.mChromaMax = value;
-            return this;
-        }
-
-        /** Set the minimum lightness value for this target, using Y in XYZ color space. */
-        @NonNull
-        public Builder setTargetRelativeLuminance(@FloatRange(from = 0, to = 100) float value) {
-            mTarget.mTargetRelativeLuminance = value;
-            return this;
-        }
-
-        /** Set the minimum lightness value for this target, using L* in LAB color space. */
-        @NonNull
-        public Builder setTargetPerceptualLuminance(@FloatRange(from = 0, to = 100) float value) {
-            mTarget.mTargetRelativeLuminance = Contrast.lstarToY(value);
-            return this;
-        }
-
         /**
-         * Set the hue desired from the target. This hue is not enforced, the only consequence
-         * is points will be awarded to seed colors the closer they are to this hue.
+         * Set the minimum saturation value for this target.
          */
-        @NonNull
-        public Builder setTargetHue(@IntRange(from = 0, to = 360) int hue) {
-            mTarget.mTargetHue = hue;
-            return this;
-        }
-
-        /** Sets lightness value for this target. */
-        @NonNull
-        public Builder setContrastRatio(
-                @FloatRange(from = 1, to = 21) float value,
-                @FloatRange(from = 0, to = 100) float relativeLuminance) {
-            float counterpartY = relativeLuminance;
-            float lstar = Contrast.yToLstar(counterpartY);
-
-            float targetY;
-            if (lstar < 50) {
-                targetY = Contrast.lighterY(counterpartY, value);
-            } else {
-                targetY = Contrast.darkerY(counterpartY, value);
-            }
-            mTarget.mTargetRelativeLuminance = targetY;
+        public Target.Builder setMinimumSaturation(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mSaturationTargets[INDEX_MIN] = value;
             return this;
         }
 
         /**
-         * Set the weight of importance that this target will place on chroma values.
+         * Set the target/ideal saturation value for this target.
+         */
+        public Target.Builder setTargetSaturation(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mSaturationTargets[INDEX_TARGET] = value;
+            return this;
+        }
+
+        /**
+         * Set the maximum saturation value for this target.
+         */
+        public Target.Builder setMaximumSaturation(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mSaturationTargets[INDEX_MAX] = value;
+            return this;
+        }
+
+        /**
+         * Set the minimum lightness value for this target.
+         */
+        public Target.Builder setMinimumLightness(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mLightnessTargets[INDEX_MIN] = value;
+            return this;
+        }
+
+        /**
+         * Set the target/ideal lightness value for this target.
+         */
+        public Target.Builder setTargetLightness(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mLightnessTargets[INDEX_TARGET] = value;
+            return this;
+        }
+
+        /**
+         * Set the maximum lightness value for this target.
+         */
+        public Target.Builder setMaximumLightness(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mLightnessTargets[INDEX_MAX] = value;
+            return this;
+        }
+
+        /**
+         * Set the weight of importance that this target will place on saturation values.
          *
          * <p>The larger the weight, relative to the other weights, the more important that a color
-         * being close to the target value has on selection.
+         * being close to the target value has on selection.</p>
          *
-         * <p>A weight of 0 means that it has no weight, and thus has no bearing on the selection.
+         * <p>A weight of 0 means that it has no weight, and thus has no
+         * bearing on the selection.</p>
          *
-         * @see #setTargetChroma(float)
+         * @see #setTargetSaturation(float)
          */
-        @NonNull
-        public Builder setChromaWeight(@FloatRange(from = 0) float weight) {
-            mTarget.mChromaWeight = weight;
+        public Target.Builder setSaturationWeight(@FloatRange(from = 0) float weight) {
+            mTarget.mWeights[INDEX_WEIGHT_SAT] = weight;
             return this;
         }
 
@@ -251,40 +385,51 @@
          * Set the weight of importance that this target will place on lightness values.
          *
          * <p>The larger the weight, relative to the other weights, the more important that a color
-         * being close to the target value has on selection.
+         * being close to the target value has on selection.</p>
          *
-         * <p>A weight of 0 means that it has no weight, and thus has no bearing on the selection.
+         * <p>A weight of 0 means that it has no weight, and thus has no
+         * bearing on the selection.</p>
          *
-         * @see #setTargetRelativeLuminance(float)
+         * @see #setTargetLightness(float)
          */
-        @NonNull
-        public Builder setLightnessWeight(@FloatRange(from = 0) float weight) {
-            mTarget.mRelativeLuminanceWeight = weight;
+        public Target.Builder setLightnessWeight(@FloatRange(from = 0) float weight) {
+            mTarget.mWeights[INDEX_WEIGHT_LUMA] = weight;
             return this;
         }
 
         /**
          * Set the weight of importance that this target will place on a color's population within
-         * the
-         * image.
+         * the image.
          *
          * <p>The larger the weight, relative to the other weights, the more important that a
-         * color's
-         * population being close to the most populous has on selection.
+         * color's population being close to the most populous has on selection.</p>
          *
-         * <p>A weight of 0 means that it has no weight, and thus has no bearing on the selection.
+         * <p>A weight of 0 means that it has no weight, and thus has no
+         * bearing on the selection.</p>
          */
-        @NonNull
-        public Builder setPopulationWeight(@FloatRange(from = 0) float weight) {
-            mTarget.mPopulationWeight = weight;
+        public Target.Builder setPopulationWeight(@FloatRange(from = 0) float weight) {
+            mTarget.mWeights[INDEX_WEIGHT_POP] = weight;
             return this;
         }
 
+        /**
+         * Set whether any color selected for this target is exclusive to this target only.
+         * Defaults to true.
+         *
+         * @param exclusive true if any the color is exclusive to this target, or false is the
+         *                  color can be selected for other targets.
+         */
+        public Target.Builder setExclusive(boolean exclusive) {
+            mTarget.mIsExclusive = exclusive;
+            return this;
+        }
 
-        /** Builds and returns the resulting {@link Target}. */
-        @NonNull
+        /**
+         * Builds and returns the resulting {@link Target}.
+         */
         public Target build() {
             return mTarget;
         }
     }
-}
+
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java
index d791f7b..b035535 100644
--- a/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java
+++ b/core/java/com/android/internal/graphics/palette/VariationalKMeansQuantizer.java
@@ -70,9 +70,10 @@
      *
      * @param pixels Pixels to quantize.
      * @param maxColors Maximum number of clusters to extract.
+     * @param filters Colors that should be ignored
      */
     @Override
-    public void quantize(int[] pixels, int maxColors) {
+    public void quantize(int[] pixels, int maxColors, Palette.Filter[] filters) {
         // Start by converting all colors to HSL.
         // HLS is way more meaningful for clustering than RGB.
         final float[] hsl = {0, 0, 0};
@@ -110,18 +111,16 @@
 
         // Convert data to final format, de-normalizing the hue.
         mQuantizedColors = new ArrayList<>();
-        float[] mHsl = new float[3];
         for (KMeans.Mean mean : optimalMeans) {
             if (mean.getItems().size() == 0) {
                 continue;
             }
             float[] centroid = mean.getCentroid();
-
-            mHsl[0] = centroid[0] * 360f;
-            mHsl[1] = centroid[1];
-            mHsl[2] = centroid[2];
-            int color = ColorUtils.HSLToColor(mHsl);
-            mQuantizedColors.add(new Palette.Swatch(color, mean.getItems().size()));
+            mQuantizedColors.add(new Palette.Swatch(new float[]{
+                    centroid[0] * 360f,
+                    centroid[1],
+                    centroid[2]
+            }, mean.getItems().size()));
         }
     }
 
diff --git a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java b/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java
deleted file mode 100644
index a87a34f..0000000
--- a/core/java/com/android/internal/graphics/palette/WSMeansQuantizer.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-
-/**
- * A color quantizer based on the Kmeans algorithm.
- *
- * This is an implementation of Kmeans based on Celebi's 2011 paper,
- * "Improving the Performance of K-Means for Color Quantization". In the paper, this algorithm is
- * referred to as "WSMeans", or, "Weighted Square Means" The main advantages of this Kmeans
- * implementation are taking advantage of triangle properties to avoid distance calculations, as
- * well as indexing colors by their count, thus minimizing the number of points to move around.
- *
- * Celebi's paper also stabilizes results and guarantees high quality by using starting centroids
- * from Wu's quantization algorithm. See CelebiQuantizer for more info.
- */
-public class WSMeansQuantizer implements Quantizer {
-    Mean[] mMeans;
-    private final Map<Integer, Integer> mCountByColor = new HashMap<>();
-    private final Map<Integer, Integer> mMeanIndexByColor = new HashMap<>();
-    private final Set<Integer> mUniqueColors = new HashSet<>();
-    private final List<Palette.Swatch> mSwatches = new ArrayList<>();
-    private final CentroidProvider mCentroidProvider;
-
-    public WSMeansQuantizer(
-            float[][] means, CentroidProvider centroidProvider, int[] pixels, int maxColors) {
-        if (pixels == null) {
-            pixels = new int[]{};
-        }
-        mCentroidProvider = centroidProvider;
-        mMeans = new Mean[maxColors];
-        for (int i = 0; i < means.length; i++) {
-            mMeans[i] = new Mean(means[i]);
-        }
-
-        if (maxColors > means.length) {
-            int randomMeansToCreate = maxColors - means.length;
-            for (int i = 0; i < randomMeansToCreate; i++) {
-                mMeans[means.length + i] = new Mean(100);
-            }
-        }
-
-        for (int pixel : pixels) {
-            Integer currentCount = mCountByColor.get(pixel);
-            if (currentCount == null) {
-                currentCount = 0;
-                mUniqueColors.add(pixel);
-            }
-            mCountByColor.put(pixel, currentCount + 1);
-        }
-        for (int color : mUniqueColors) {
-            int closestMeanIndex = -1;
-            double closestMeanDistance = -1;
-            float[] centroid = mCentroidProvider.getCentroid(color);
-            for (int i = 0; i < mMeans.length; i++) {
-                double distance = mCentroidProvider.distance(centroid, mMeans[i].center);
-                if (closestMeanIndex == -1 || distance < closestMeanDistance) {
-                    closestMeanIndex = i;
-                    closestMeanDistance = distance;
-                }
-            }
-            mMeanIndexByColor.put(color, closestMeanIndex);
-        }
-
-        if (pixels.length == 0) {
-            return;
-        }
-
-        predict(maxColors, 0);
-    }
-
-    /** Create starting centroids for K-means from a set of colors. */
-    public static float[][] createStartingCentroids(CentroidProvider centroidProvider,
-            List<Palette.Swatch> swatches) {
-        float[][] startingCentroids = new float[swatches.size()][];
-        for (int i = 0; i < swatches.size(); i++) {
-            startingCentroids[i] = centroidProvider.getCentroid(swatches.get(i).getInt());
-        }
-        return startingCentroids;
-    }
-
-    /** Create random starting centroids for K-means. */
-    public static float[][] randomMeans(int maxColors, int upperBound) {
-        float[][] means = new float[maxColors][];
-        for (int i = 0; i < maxColors; i++) {
-            means[i] = new Mean(upperBound).center;
-        }
-        return means;
-    }
-
-
-    @Override
-    public void quantize(int[] pixels, int maxColors) {
-
-    }
-
-    @Override
-    public List<Palette.Swatch> getQuantizedColors() {
-        return mSwatches;
-    }
-
-    private void predict(int maxColors, int iterationsCompleted) {
-        double[][] centroidDistance = new double[maxColors][maxColors];
-        for (int i = 0; i <= maxColors; i++) {
-            for (int j = i + 1; j < maxColors; j++) {
-                float[] meanI = mMeans[i].center;
-                float[] meanJ = mMeans[j].center;
-                double distance = mCentroidProvider.distance(meanI, meanJ);
-                centroidDistance[i][j] = distance;
-                centroidDistance[j][i] = distance;
-            }
-        }
-
-        // Construct a K×K matrix M in which row i is a permutation of
-        // 1,2,…,K that represents the clusters in increasing order of
-        // distance of their centers from ci;
-        int[][] distanceMatrix = new int[maxColors][maxColors];
-        for (int i = 0; i < maxColors; i++) {
-            double[] distancesFromIToAnotherMean = centroidDistance[i];
-            double[] sortedByDistanceAscending = distancesFromIToAnotherMean.clone();
-            Arrays.sort(sortedByDistanceAscending);
-            int[] outputRow = new int[maxColors];
-            for (int j = 0; j < maxColors; j++) {
-                outputRow[j] = findIndex(distancesFromIToAnotherMean, sortedByDistanceAscending[j]);
-            }
-            distanceMatrix[i] = outputRow;
-        }
-
-        //   for (i=1;i≤N′;i=i+ 1) do
-        //   Let Sp be the cluster that xi was assigned to in the previous
-        //   iteration;
-        //   p=m[i];
-        //   min_dist=prev_dist=jjxi−cpjj2;
-        boolean anyColorMoved = false;
-        for (int intColor : mUniqueColors) {
-            float[] color = mCentroidProvider.getCentroid(intColor);
-            int indexOfCurrentMean = mMeanIndexByColor.get(intColor);
-            Mean currentMean = mMeans[indexOfCurrentMean];
-            double minDistance = mCentroidProvider.distance(color, currentMean.center);
-            for (int j = 1; j < maxColors; j++) {
-                int indexOfClusterFromCurrentToJ = distanceMatrix[indexOfCurrentMean][j];
-                double distanceBetweenJAndCurrent =
-                        centroidDistance[indexOfCurrentMean][indexOfClusterFromCurrentToJ];
-                if (distanceBetweenJAndCurrent >= (4 * minDistance)) {
-                    break;
-                }
-                double distanceBetweenJAndColor = mCentroidProvider.distance(mMeans[j].center,
-                        color);
-                if (distanceBetweenJAndColor < minDistance) {
-                    minDistance = distanceBetweenJAndColor;
-                    mMeanIndexByColor.remove(intColor);
-                    mMeanIndexByColor.put(intColor, j);
-                    anyColorMoved = true;
-                }
-            }
-        }
-
-        List<MeanBucket> buckets = new ArrayList<>();
-        for (int i = 0; i < maxColors; i++) {
-            buckets.add(new MeanBucket());
-        }
-
-        for (int intColor : mUniqueColors) {
-            int meanIndex = mMeanIndexByColor.get(intColor);
-            MeanBucket meanBucket = buckets.get(meanIndex);
-            meanBucket.add(mCentroidProvider.getCentroid(intColor), intColor,
-                    mCountByColor.get(intColor));
-        }
-
-        List<Palette.Swatch> swatches = new ArrayList<>();
-        boolean done = !anyColorMoved && iterationsCompleted > 0 || iterationsCompleted >= 100;
-        if (done) {
-            for (int i = 0; i < buckets.size(); i++) {
-                MeanBucket a = buckets.get(i);
-                if (a.mCount <= 0) {
-                    continue;
-                }
-                List<MeanBucket> bucketsToMerge = new ArrayList<>();
-                for (int j = i + 1; j < buckets.size(); j++) {
-                    MeanBucket b = buckets.get(j);
-                    if (b.mCount == 0) {
-                        continue;
-                    }
-                    float[] bCentroid = b.getCentroid();
-                    assert (a.mCount > 0);
-                    assert (a.getCentroid() != null);
-
-                    assert (bCentroid != null);
-                    if (mCentroidProvider.distance(a.getCentroid(), b.getCentroid()) < 5) {
-                        bucketsToMerge.add(b);
-                    }
-                }
-
-                for (MeanBucket bucketToMerge : bucketsToMerge) {
-                    float[] centroid = bucketToMerge.getCentroid();
-                    a.add(centroid, mCentroidProvider.getColor(centroid), bucketToMerge.mCount);
-                    buckets.remove(bucketToMerge);
-                }
-            }
-
-            for (MeanBucket bucket : buckets) {
-                float[] centroid = bucket.getCentroid();
-                if (centroid == null) {
-                    continue;
-                }
-
-                int rgb = mCentroidProvider.getColor(centroid);
-                swatches.add(new Palette.Swatch(rgb, bucket.mCount));
-                mSwatches.clear();
-                mSwatches.addAll(swatches);
-            }
-        } else {
-            List<MeanBucket> emptyBuckets = new ArrayList<>();
-            for (int i = 0; i < buckets.size(); i++) {
-                MeanBucket bucket = buckets.get(i);
-                if ((bucket.getCentroid() == null) || (bucket.mCount == 0)) {
-                    emptyBuckets.add(bucket);
-                    for (Integer color : mUniqueColors) {
-                        int meanIndex = mMeanIndexByColor.get(color);
-                        if (meanIndex > i) {
-                            mMeanIndexByColor.put(color, meanIndex--);
-                        }
-                    }
-                }
-            }
-
-            Mean[] newMeans = new Mean[buckets.size()];
-            for (int i = 0; i < buckets.size(); i++) {
-                float[] centroid = buckets.get(i).getCentroid();
-                newMeans[i] = new Mean(centroid);
-            }
-
-            predict(buckets.size(), iterationsCompleted + 1);
-        }
-
-    }
-
-    private static int findIndex(double[] list, double element) {
-        for (int i = 0; i < list.length; i++) {
-            if (list[i] == element) {
-                return i;
-            }
-        }
-        throw new IllegalArgumentException("Element not in list");
-    }
-}
diff --git a/core/java/com/android/internal/graphics/palette/WuQuantizer.java b/core/java/com/android/internal/graphics/palette/WuQuantizer.java
deleted file mode 100644
index 01e45f6..0000000
--- a/core/java/com/android/internal/graphics/palette/WuQuantizer.java
+++ /dev/null
@@ -1,442 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics.palette;
-
-
-import java.util.ArrayList;
-import java.util.List;
-
-// All reference Wu implementations are based on the original C code by Wu.
-// Comments on methods are the same as in the original implementation, and the comment below
-// is the original class header.
-
-/**
- * Wu's Color Quantizer (v. 2) (see Graphics Gems vol. II, pp. 126-133) Author: Xiaolin Wu
- *
- * <p>Algorithm: Greedy orthogonal bipartition of RGB space for variance minimization aided by
- * inclusion-exclusion tricks. For speed no nearest neighbor search is done. Slightly better
- * performance can be expected by more sophisticated but more expensive versions.
- */
-public class WuQuantizer implements Quantizer {
-    private static final int MAX_COLORS = 256;
-    private static final int RED = 2;
-    private static final int GREEN = 1;
-    private static final int BLUE = 0;
-
-    private static final int QUANT_SIZE = 33;
-    private final List<Palette.Swatch> mSwatches = new ArrayList<>();
-
-    @Override
-    public List<Palette.Swatch> getQuantizedColors() {
-        return mSwatches;
-    }
-
-    private static final class Box {
-        int mR0; /* min value, exclusive */
-        int mR1; /* max value, inclusive */
-        int mG0;
-        int mG1;
-        int mB0;
-        int mB1;
-        int mVol;
-    }
-
-    private final int mSize; /* image size, in bytes. */
-    private int mMaxColors;
-    private int[] mQadd;
-    private final int[] mPixels;
-
-    private final double[][][] mM2 = new double[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
-    private final long[][][] mWt = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
-    private final long[][][] mMr = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
-    private final long[][][] mMg = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
-    private final long[][][] mMb = new long[QUANT_SIZE][QUANT_SIZE][QUANT_SIZE];
-
-    public WuQuantizer(int[] pixels, int maxColorCount) {
-        if (pixels == null) {
-            pixels = new int[]{};
-        }
-        this.mPixels = pixels;
-        this.mSize = pixels.length;
-    }
-
-    @Override
-    public void quantize(int[] colors, int maxColorCount) {
-        // All of the sample Wu implementations are reimplementations of a snippet of C code from
-        // the early 90s. They all cap the maximum # of colors at 256, and it is impossible to tell
-        // if this is a requirement, a consequence of QUANT_SIZE, or arbitrary.
-        this.mMaxColors = Math.min(MAX_COLORS, maxColorCount);
-        Box[] cube = new Box[mMaxColors];
-        int red, green, blue;
-
-        int next, i, k;
-        long weight;
-        double[] vv = new double[mMaxColors];
-        double temp;
-
-        compute3DHistogram(mWt, mMr, mMg, mMb, mM2);
-        computeMoments(mWt, mMr, mMg, mMb, mM2);
-
-        for (i = 0; i < mMaxColors; i++) {
-            cube[i] = new Box();
-        }
-
-        cube[0].mR0 = cube[0].mG0 = cube[0].mB0 = 0;
-        cube[0].mR1 = cube[0].mG1 = cube[0].mB1 = QUANT_SIZE - 1;
-        next = 0;
-
-        for (i = 1; i < mMaxColors; ++i) {
-            if (cut(cube[next], cube[i])) {
-                vv[next] = (cube[next].mVol > 1) ? getVariance(cube[next]) : 0.0f;
-                vv[i] = (cube[i].mVol > 1) ? getVariance(cube[i]) : 0.0f;
-            } else {
-                vv[next] = 0.0f;
-                i--;
-            }
-            next = 0;
-            temp = vv[0];
-            for (k = 1; k <= i; ++k) {
-                if (vv[k] > temp) {
-                    temp = vv[k];
-                    next = k;
-                }
-            }
-            if (temp <= 0.0f) {
-                break;
-            }
-        }
-
-        for (k = 0; k < mMaxColors; ++k) {
-            weight = getVolume(cube[k], mWt);
-            if (weight > 0) {
-                red = (int) (getVolume(cube[k], mMr) / weight);
-                green = (int) (getVolume(cube[k], mMg) / weight);
-                blue = (int) (getVolume(cube[k], mMb) / weight);
-                colors[k] = ((red & 0x0ff) << 16) | ((green & 0x0ff) << 8) | (blue & 0x0ff);
-            } else {
-                colors[k] = 0;
-            }
-        }
-
-        int bitsPerPixel = 0;
-        while ((1 << bitsPerPixel) < mMaxColors) {
-            bitsPerPixel++;
-        }
-
-        List<Palette.Swatch> swatches = new ArrayList<>();
-        for (int l = 0; l < k; l++) {
-            int pixel = colors[l];
-            if (pixel == 0) {
-                continue;
-            }
-            swatches.add(new Palette.Swatch(pixel, 0));
-        }
-        mSwatches.clear();
-        mSwatches.addAll(swatches);
-    }
-
-    /* Histogram is in elements 1..HISTSIZE along each axis,
-     * element 0 is for base or marginal value
-     * NB: these must start out 0!
-     */
-    private void compute3DHistogram(
-            long[][][] vwt, long[][][] vmr, long[][][] vmg, long[][][] vmb, double[][][] m2) {
-        // build 3-D color histogram of counts, r/g/b, and c^2
-        int r, g, b;
-        int i;
-        int inr;
-        int ing;
-        int inb;
-        int[] table = new int[256];
-
-        for (i = 0; i < 256; i++) {
-            table[i] = i * i;
-        }
-
-        mQadd = new int[mSize];
-
-        for (i = 0; i < mSize; ++i) {
-            int rgb = mPixels[i];
-            // Skip less than opaque pixels. They're not meaningful in the context of palette
-            // generation for UI schemes.
-            if ((rgb >>> 24) < 0xff) {
-                continue;
-            }
-            r = ((rgb >> 16) & 0xff);
-            g = ((rgb >> 8) & 0xff);
-            b = (rgb & 0xff);
-            inr = (r >> 3) + 1;
-            ing = (g >> 3) + 1;
-            inb = (b >> 3) + 1;
-            mQadd[i] = (inr << 10) + (inr << 6) + inr + (ing << 5) + ing + inb;
-            /*[inr][ing][inb]*/
-            ++vwt[inr][ing][inb];
-            vmr[inr][ing][inb] += r;
-            vmg[inr][ing][inb] += g;
-            vmb[inr][ing][inb] += b;
-            m2[inr][ing][inb] += table[r] + table[g] + table[b];
-        }
-    }
-
-    /* At conclusion of the histogram step, we can interpret
-     *   wt[r][g][b] = sum over voxel of P(c)
-     *   mr[r][g][b] = sum over voxel of r*P(c)  ,  similarly for mg, mb
-     *   m2[r][g][b] = sum over voxel of c^2*P(c)
-     * Actually each of these should be divided by 'size' to give the usual
-     * interpretation of P() as ranging from 0 to 1, but we needn't do that here.
-     *
-     * We now convert histogram into moments so that we can rapidly calculate
-     * the sums of the above quantities over any desired box.
-     */
-    private void computeMoments(
-            long[][][] vwt, long[][][] vmr, long[][][] vmg, long[][][] vmb, double[][][] m2) {
-        /* compute cumulative moments. */
-        int i, r, g, b;
-        int line, line_r, line_g, line_b;
-        int[] area = new int[QUANT_SIZE];
-        int[] area_r = new int[QUANT_SIZE];
-        int[] area_g = new int[QUANT_SIZE];
-        int[] area_b = new int[QUANT_SIZE];
-        double line2;
-        double[] area2 = new double[QUANT_SIZE];
-
-        for (r = 1; r < QUANT_SIZE; ++r) {
-            for (i = 0; i < QUANT_SIZE; ++i) {
-                area2[i] = area[i] = area_r[i] = area_g[i] = area_b[i] = 0;
-            }
-            for (g = 1; g < QUANT_SIZE; ++g) {
-                line2 = line = line_r = line_g = line_b = 0;
-                for (b = 1; b < QUANT_SIZE; ++b) {
-                    line += vwt[r][g][b];
-                    line_r += vmr[r][g][b];
-                    line_g += vmg[r][g][b];
-                    line_b += vmb[r][g][b];
-                    line2 += m2[r][g][b];
-
-                    area[b] += line;
-                    area_r[b] += line_r;
-                    area_g[b] += line_g;
-                    area_b[b] += line_b;
-                    area2[b] += line2;
-
-                    vwt[r][g][b] = vwt[r - 1][g][b] + area[b];
-                    vmr[r][g][b] = vmr[r - 1][g][b] + area_r[b];
-                    vmg[r][g][b] = vmg[r - 1][g][b] + area_g[b];
-                    vmb[r][g][b] = vmb[r - 1][g][b] + area_b[b];
-                    m2[r][g][b] = m2[r - 1][g][b] + area2[b];
-                }
-            }
-        }
-    }
-
-    private long getVolume(Box cube, long[][][] mmt) {
-        /* Compute sum over a box of any given statistic */
-        return (mmt[cube.mR1][cube.mG1][cube.mB1]
-                - mmt[cube.mR1][cube.mG1][cube.mB0]
-                - mmt[cube.mR1][cube.mG0][cube.mB1]
-                + mmt[cube.mR1][cube.mG0][cube.mB0]
-                - mmt[cube.mR0][cube.mG1][cube.mB1]
-                + mmt[cube.mR0][cube.mG1][cube.mB0]
-                + mmt[cube.mR0][cube.mG0][cube.mB1]
-                - mmt[cube.mR0][cube.mG0][cube.mB0]);
-    }
-
-    /* The next two routines allow a slightly more efficient calculation
-     * of Vol() for a proposed subbox of a given box.  The sum of Top()
-     * and Bottom() is the Vol() of a subbox split in the given direction
-     * and with the specified new upper bound.
-     */
-    private long getBottom(Box cube, int dir, long[][][] mmt) {
-        /* Compute part of Vol(cube, mmt) that doesn't depend on r1, g1, or b1 */
-        /* (depending on dir) */
-        switch (dir) {
-            case RED:
-                return (-mmt[cube.mR0][cube.mG1][cube.mB1]
-                        + mmt[cube.mR0][cube.mG1][cube.mB0]
-                        + mmt[cube.mR0][cube.mG0][cube.mB1]
-                        - mmt[cube.mR0][cube.mG0][cube.mB0]);
-            case GREEN:
-                return (-mmt[cube.mR1][cube.mG0][cube.mB1]
-                        + mmt[cube.mR1][cube.mG0][cube.mB0]
-                        + mmt[cube.mR0][cube.mG0][cube.mB1]
-                        - mmt[cube.mR0][cube.mG0][cube.mB0]);
-            case BLUE:
-                return (-mmt[cube.mR1][cube.mG1][cube.mB0]
-                        + mmt[cube.mR1][cube.mG0][cube.mB0]
-                        + mmt[cube.mR0][cube.mG1][cube.mB0]
-                        - mmt[cube.mR0][cube.mG0][cube.mB0]);
-            default:
-                return 0;
-        }
-    }
-
-    private long getTop(Box cube, int dir, int pos, long[][][] mmt) {
-        /* Compute remainder of Vol(cube, mmt), substituting pos for */
-        /* r1, g1, or b1 (depending on dir) */
-        switch (dir) {
-            case RED:
-                return (mmt[pos][cube.mG1][cube.mB1]
-                        - mmt[pos][cube.mG1][cube.mB0]
-                        - mmt[pos][cube.mG0][cube.mB1]
-                        + mmt[pos][cube.mG0][cube.mB0]);
-            case GREEN:
-                return (mmt[cube.mR1][pos][cube.mB1]
-                        - mmt[cube.mR1][pos][cube.mB0]
-                        - mmt[cube.mR0][pos][cube.mB1]
-                        + mmt[cube.mR0][pos][cube.mB0]);
-            case BLUE:
-                return (mmt[cube.mR1][cube.mG1][pos]
-                        - mmt[cube.mR1][cube.mG0][pos]
-                        - mmt[cube.mR0][cube.mG1][pos]
-                        + mmt[cube.mR0][cube.mG0][pos]);
-            default:
-                return 0;
-        }
-    }
-
-    private double getVariance(Box cube) {
-        /* Compute the weighted variance of a box */
-        /* NB: as with the raw statistics, this is really the variance * size */
-        double dr, dg, db, xx;
-        dr = getVolume(cube, mMr);
-        dg = getVolume(cube, mMg);
-        db = getVolume(cube, mMb);
-        xx =
-                mM2[cube.mR1][cube.mG1][cube.mB1]
-                        - mM2[cube.mR1][cube.mG1][cube.mB0]
-                        - mM2[cube.mR1][cube.mG0][cube.mB1]
-                        + mM2[cube.mR1][cube.mG0][cube.mB0]
-                        - mM2[cube.mR0][cube.mG1][cube.mB1]
-                        + mM2[cube.mR0][cube.mG1][cube.mB0]
-                        + mM2[cube.mR0][cube.mG0][cube.mB1]
-                        - mM2[cube.mR0][cube.mG0][cube.mB0];
-        return xx - (dr * dr + dg * dg + db * db) / getVolume(cube, mWt);
-    }
-
-    /* We want to minimize the sum of the variances of two subboxes.
-     * The sum(c^2) terms can be ignored since their sum over both subboxes
-     * is the same (the sum for the whole box) no matter where we split.
-     * The remaining terms have a minus sign in the variance formula,
-     * so we drop the minus sign and MAXIMIZE the sum of the two terms.
-     */
-    private double maximize(
-            Box cube,
-            int dir,
-            int first,
-            int last,
-            int[] cut,
-            long wholeR,
-            long wholeG,
-            long wholeB,
-            long wholeW) {
-        long half_r, half_g, half_b, half_w;
-        long base_r, base_g, base_b, base_w;
-        int i;
-        double temp, max;
-
-        base_r = getBottom(cube, dir, mMr);
-        base_g = getBottom(cube, dir, mMg);
-        base_b = getBottom(cube, dir, mMb);
-        base_w = getBottom(cube, dir, mWt);
-
-        max = 0.0f;
-        cut[0] = -1;
-
-        for (i = first; i < last; ++i) {
-            half_r = base_r + getTop(cube, dir, i, mMr);
-            half_g = base_g + getTop(cube, dir, i, mMg);
-            half_b = base_b + getTop(cube, dir, i, mMb);
-            half_w = base_w + getTop(cube, dir, i, mWt);
-            /* now half_x is sum over lower half of box, if split at i */
-            if (half_w == 0) /* subbox could be empty of pixels! */ {
-                continue; /* never split into an empty box */
-            }
-            temp = (half_r * half_r + half_g * half_g + half_b * half_b) / (double) half_w;
-            half_r = wholeR - half_r;
-            half_g = wholeG - half_g;
-            half_b = wholeB - half_b;
-            half_w = wholeW - half_w;
-            if (half_w == 0) /* subbox could be empty of pixels! */ {
-                continue; /* never split into an empty box */
-            }
-            temp += (half_r * half_r + half_g * half_g + half_b * half_b) / (double) half_w;
-
-            if (temp > max) {
-                max = temp;
-                cut[0] = i;
-            }
-        }
-
-        return max;
-    }
-
-    private boolean cut(Box set1, Box set2) {
-        int dir;
-        int[] cutr = new int[1];
-        int[] cutg = new int[1];
-        int[] cutb = new int[1];
-        double maxr, maxg, maxb;
-        long whole_r, whole_g, whole_b, whole_w;
-
-        whole_r = getVolume(set1, mMr);
-        whole_g = getVolume(set1, mMg);
-        whole_b = getVolume(set1, mMb);
-        whole_w = getVolume(set1, mWt);
-
-        maxr = maximize(set1, RED, set1.mR0 + 1, set1.mR1, cutr, whole_r, whole_g, whole_b,
-                whole_w);
-        maxg = maximize(set1, GREEN, set1.mG0 + 1, set1.mG1, cutg, whole_r, whole_g, whole_b,
-                whole_w);
-        maxb = maximize(set1, BLUE, set1.mB0 + 1, set1.mB1, cutb, whole_r, whole_g, whole_b,
-                whole_w);
-
-        if (maxr >= maxg && maxr >= maxb) {
-            dir = RED;
-            if (cutr[0] < 0) return false; /* can't split the box */
-        } else if (maxg >= maxr && maxg >= maxb) {
-            dir = GREEN;
-        } else {
-            dir = BLUE;
-        }
-
-        set2.mR1 = set1.mR1;
-        set2.mG1 = set1.mG1;
-        set2.mB1 = set1.mB1;
-
-        switch (dir) {
-            case RED:
-                set2.mR0 = set1.mR1 = cutr[0];
-                set2.mG0 = set1.mG0;
-                set2.mB0 = set1.mB0;
-                break;
-            case GREEN:
-                set2.mG0 = set1.mG1 = cutg[0];
-                set2.mR0 = set1.mR0;
-                set2.mB0 = set1.mB0;
-                break;
-            case BLUE:
-                set2.mB0 = set1.mB1 = cutb[0];
-                set2.mR0 = set1.mR0;
-                set2.mG0 = set1.mG0;
-                break;
-        }
-        set1.mVol = (set1.mR1 - set1.mR0) * (set1.mG1 - set1.mG0) * (set1.mB1 - set1.mB0);
-        set2.mVol = (set2.mR1 - set2.mR0) * (set2.mG1 - set2.mG0) * (set2.mB1 - set2.mB0);
-
-        return true;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 7649770..45d5515 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -43,7 +43,6 @@
 
 /**
  * Util class to create the view for a splash screen content.
- *
  * @hide
  */
 public class SplashscreenContentDrawer {
@@ -350,7 +349,7 @@
 
         // Calculate the difference between two colors based on the HSV dimensions.
         final float normalizeH = minAngle / 180f;
-        final double square = Math.pow(normalizeH, 2)
+        final double square =  Math.pow(normalizeH, 2)
                 + Math.pow(aHsv[1] - bHsv[1], 2)
                 + Math.pow(aHsv[2] - bHsv[2], 2);
         final double mean = square / 3;
@@ -434,11 +433,8 @@
          */
         private interface ColorTester {
             float nonTransparentRatio();
-
             boolean isComplexColor();
-
             int getDominantColor();
-
             boolean isGrayscale();
         }
 
@@ -515,17 +511,14 @@
                 // restore to original bounds
                 drawable.setBounds(initialBounds);
 
-                final Palette.Builder builder;
+                final Palette.Builder builder = new Palette.Builder(bitmap)
+                        .maximumColorCount(5).clearFilters();
                 // The Palette API will ignore Alpha, so it cannot handle transparent pixels, but
                 // sometimes we will need this information to know if this Drawable object is
                 // transparent.
                 mFilterTransparent = filterTransparent;
                 if (mFilterTransparent) {
-                    builder = new Palette.Builder(bitmap, TRANSPARENT_FILTER_QUANTIZER)
-                            .maximumColorCount(5);
-                } else {
-                    builder = new Palette.Builder(bitmap, null)
-                            .maximumColorCount(5);
+                    builder.setQuantizer(TRANSPARENT_FILTER_QUANTIZER);
                 }
                 mPalette = builder.generate();
                 bitmap.recycle();
@@ -545,7 +538,7 @@
             public int getDominantColor() {
                 final Palette.Swatch mainSwatch = mPalette.getDominantSwatch();
                 if (mainSwatch != null) {
-                    return mainSwatch.getInt();
+                    return mainSwatch.getRgb();
                 }
                 return Color.BLACK;
             }
@@ -556,7 +549,7 @@
                 if (swatches != null) {
                     for (int i = swatches.size() - 1; i >= 0; i--) {
                         Palette.Swatch swatch = swatches.get(i);
-                        if (!isGrayscaleColor(swatch.getInt())) {
+                        if (!isGrayscaleColor(swatch.getRgb())) {
                             return false;
                         }
                     }
@@ -568,9 +561,9 @@
                 private static final int NON_TRANSPARENT = 0xFF000000;
                 private final Quantizer mInnerQuantizer = new VariationalKMeansQuantizer();
                 private float mNonTransparentRatio;
-
                 @Override
-                public void quantize(final int[] pixels, final int maxColors) {
+                public void quantize(final int[] pixels, final int maxColors,
+                        final Palette.Filter[] filters) {
                     mNonTransparentRatio = 0;
                     int realSize = 0;
                     for (int i = pixels.length - 1; i > 0; i--) {
@@ -582,7 +575,7 @@
                         if (DEBUG) {
                             Slog.d(TAG, "quantize: this is pure transparent image");
                         }
-                        mInnerQuantizer.quantize(pixels, maxColors);
+                        mInnerQuantizer.quantize(pixels, maxColors, filters);
                         return;
                     }
                     mNonTransparentRatio = (float) realSize / pixels.length;
@@ -594,7 +587,7 @@
                             rowIndex++;
                         }
                     }
-                    mInnerQuantizer.quantize(samplePixels, maxColors);
+                    mInnerQuantizer.quantize(samplePixels, maxColors, filters);
                 }
 
                 @Override