Add monet to AOSP

Test: manual
Test: atest ColorSchemeTest
Test: atest SystemPalette
Fixes: 199401230
Change-Id: I0695257765335f12ad47d5d8f40d0885c652bff6
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 1f323c3..ae3a9e6 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -74,6 +74,11 @@
         return false;
     }
 
+    private static int unsupportedInt() {
+        if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception());
+        return -1;
+    }
+
     @Override
     public Drawable getDrawable() {
         return unsupported();
@@ -189,12 +194,12 @@
 
     @Override
     public int getWallpaperId(int which) {
-        return unsupported();
+        return unsupportedInt();
     }
 
     @Override
     public int getWallpaperIdForUser(int which, int userId) {
-        return unsupported();
+        return unsupportedInt();
     }
 
     @Override
@@ -209,7 +214,8 @@
 
     @Override
     public int setResource(int resid, int which) throws IOException {
-        return unsupported();
+        unsupported();
+        return 0;
     }
 
     @Override
@@ -220,19 +226,22 @@
     @Override
     public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup)
             throws IOException {
-        return unsupported();
+        unsupported();
+        return 0;
     }
 
     @Override
     public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which)
             throws IOException {
-        return unsupported();
+        unsupported();
+        return 0;
     }
 
     @Override
     public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which,
             int userId) throws IOException {
-        return unsupported();
+        unsupported();
+        return 0;
     }
 
     @Override
@@ -243,13 +252,15 @@
     @Override
     public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup)
             throws IOException {
-        return unsupported();
+        unsupported();
+        return 0;
     }
 
     @Override
     public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup,
             int which) throws IOException {
-        return unsupported();
+        unsupported();
+        return 0;
     }
 
     @Override
@@ -259,12 +270,12 @@
 
     @Override
     public int getDesiredMinimumWidth() {
-        return unsupported();
+        return unsupportedInt();
     }
 
     @Override
     public int getDesiredMinimumHeight() {
-        return unsupported();
+        return unsupportedInt();
     }
 
     @Override
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d051290..6671308 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -92,6 +92,7 @@
         "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
+        "monet",
         "dagger2",
         "jsr330",
         "lottie",
@@ -179,6 +180,7 @@
         "mockito-target-extended-minus-junit4",
         "testables",
         "truth-prebuilt",
+        "monet",
         "dagger2",
         "jsr330",
         "WindowManager-Shell",
diff --git a/packages/SystemUI/monet/Android.bp b/packages/SystemUI/monet/Android.bp
new file mode 100644
index 0000000..507ea25
--- /dev/null
+++ b/packages/SystemUI/monet/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "monet",
+    platform_apis: true,
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.core_core",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+}
diff --git a/packages/SystemUI/monet/AndroidManifest.xml b/packages/SystemUI/monet/AndroidManifest.xml
new file mode 100644
index 0000000..1fab528
--- /dev/null
+++ b/packages/SystemUI/monet/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.monet">
+</manifest>
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
new file mode 100644
index 0000000..b8039e1
--- /dev/null
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -0,0 +1,246 @@
+/*
+ * 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.systemui.monet
+
+import android.annotation.ColorInt
+import android.app.WallpaperColors
+import android.graphics.Color
+import com.android.internal.graphics.ColorUtils
+import com.android.internal.graphics.cam.Cam
+import com.android.internal.graphics.cam.CamUtils.lstarFromInt
+import kotlin.math.absoluteValue
+import kotlin.math.roundToInt
+
+const val TAG = "ColorScheme"
+
+const val ACCENT1_CHROMA = 48.0f
+const val ACCENT2_CHROMA = 16.0f
+const val ACCENT3_CHROMA = 32.0f
+const val ACCENT3_HUE_SHIFT = 60.0f
+
+const val NEUTRAL1_CHROMA = 4.0f
+const val NEUTRAL2_CHROMA = 8.0f
+
+const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
+
+const val MIN_CHROMA = 15
+const val MIN_LSTAR = 10
+
+public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) {
+
+    val accent1: List<Int>
+    val accent2: List<Int>
+    val accent3: List<Int>
+    val neutral1: List<Int>
+    val neutral2: List<Int>
+
+    constructor(wallpaperColors: WallpaperColors, darkTheme: Boolean):
+            this(getSeedColor(wallpaperColors), darkTheme)
+
+    val allAccentColors: List<Int>
+        get() {
+            val allColors = mutableListOf<Int>()
+            allColors.addAll(accent1)
+            allColors.addAll(accent2)
+            allColors.addAll(accent3)
+            return allColors
+        }
+
+    val allNeutralColors: List<Int>
+        get() {
+            val allColors = mutableListOf<Int>()
+            allColors.addAll(neutral1)
+            allColors.addAll(neutral2)
+            return allColors
+        }
+
+    val backgroundColor
+        get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1[8] else neutral1[0], 0xFF)
+
+    val accentColor
+        get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF)
+
+    init {
+        val seedArgb = if (seed == Color.TRANSPARENT) GOOGLE_BLUE else seed
+        val camSeed = Cam.fromInt(seedArgb)
+        val hue = camSeed.hue
+        val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA)
+        accent1 = Shades.of(hue, chroma).toList()
+        accent2 = Shades.of(hue, ACCENT2_CHROMA).toList()
+        accent3 = Shades.of(hue + ACCENT3_HUE_SHIFT, ACCENT3_CHROMA).toList()
+        neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList()
+        neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList()
+    }
+
+    override fun toString(): String {
+        return "ColorScheme {\n" +
+                "  neutral1: ${humanReadable(neutral1)}\n" +
+                "  neutral2: ${humanReadable(neutral2)}\n" +
+                "  accent1: ${humanReadable(accent1)}\n" +
+                "  accent2: ${humanReadable(accent2)}\n" +
+                "  accent3: ${humanReadable(accent3)}\n" +
+                "}"
+    }
+
+    companion object {
+        /**
+         * Identifies a color to create a color scheme from.
+         *
+         * @param wallpaperColors Colors extracted from an image via quantization.
+         * @return ARGB int representing the color
+         */
+        @JvmStatic
+        @ColorInt
+        fun getSeedColor(wallpaperColors: WallpaperColors): Int {
+            return getSeedColors(wallpaperColors).first()
+        }
+
+        /**
+         * Filters and ranks colors from WallpaperColors.
+         *
+         * @param wallpaperColors Colors extracted from an image via quantization.
+         * @return List of ARGB ints, ordered from highest scoring to lowest.
+         */
+        @JvmStatic
+        fun getSeedColors(wallpaperColors: WallpaperColors): List<Int> {
+            val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b }
+                    .toDouble()
+            val totalPopulationMeaningless = (totalPopulation == 0.0)
+            if (totalPopulationMeaningless) {
+                // WallpaperColors with a population of 0 indicate the colors didn't come from
+                // quantization. Instead of scoring, trust the ordering of the provided primary
+                // secondary/tertiary colors.
+                //
+                // In this case, the colors are usually from a Live Wallpaper.
+                val distinctColors = wallpaperColors.mainColors.map {
+                    it.toArgb()
+                }.distinct().filter {
+                    val cam = Cam.fromInt(it)
+                    val lstar = lstarFromInt(it)
+                    cam.chroma >= MIN_CHROMA && lstar >= MIN_LSTAR
+                }.toList()
+
+                if (distinctColors.isEmpty()) {
+                    return listOf(GOOGLE_BLUE)
+                }
+                return distinctColors
+            }
+
+            val intToProportion = wallpaperColors.allColors.mapValues {
+                it.value.toDouble() / totalPopulation
+            }
+            val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) }
+
+            // Get an array with 360 slots. A slot contains the percentage of colors with that hue.
+            val hueProportions = huePopulations(intToCam, intToProportion)
+            // Map each color to the percentage of the image with its hue.
+            val intToHueProportion = wallpaperColors.allColors.mapValues {
+                val cam = intToCam[it.key]!!
+                val hue = cam.hue.roundToInt()
+                var proportion = 0.0
+                for (i in hue - 15..hue + 15) {
+                    proportion += hueProportions[wrapDegrees(i)]
+                }
+                proportion
+            }
+            // Remove any inappropriate seed colors. For example, low chroma colors look grayscale
+            // raising their chroma will turn them to a much louder color that may not have been
+            // in the image.
+            val filteredIntToCam = intToCam.filter {
+                val cam = it.value
+                val lstar = lstarFromInt(it.key)
+                val proportion = intToHueProportion[it.key]!!
+                cam.chroma >= MIN_CHROMA && lstar >= MIN_LSTAR &&
+                        (totalPopulationMeaningless || proportion > 0.01)
+            }
+            // Sort the colors by score, from high to low.
+            val seeds = mutableListOf<Int>()
+            val intToScoreIntermediate = filteredIntToCam.mapValues {
+                score(it.value, intToHueProportion[it.key]!!)
+            }
+            val intToScore = intToScoreIntermediate.entries.toMutableList()
+            intToScore.sortByDescending { it.value }
+
+            // Go through the colors, from high score to low score. If there isn't already a seed
+            // color with a hue close to color being examined, add the color being examined to the
+            // seed colors.
+            for (entry in intToScore) {
+                val int = entry.key
+                val existingSeedNearby = seeds.find {
+                    val hueA = intToCam[int]!!.hue
+                    val hueB = intToCam[it]!!.hue
+                    hueDiff(hueA, hueB) < 15 } != null
+                if (existingSeedNearby) {
+                    continue
+                }
+                seeds.add(int)
+            }
+
+            if (seeds.isEmpty()) {
+                // Use gBlue 500 if there are 0 colors
+                seeds.add(GOOGLE_BLUE)
+            }
+
+            return seeds
+        }
+
+        private fun wrapDegrees(degrees: Int): Int {
+            return when {
+                degrees < 0 -> {
+                    (degrees % 360) + 360
+                }
+                degrees >= 360 -> {
+                    degrees % 360
+                }
+                else -> {
+                    degrees
+                }
+            }
+        }
+
+        private fun hueDiff(a: Float, b: Float): Float {
+            return 180f - ((a - b).absoluteValue - 180f).absoluteValue
+        }
+
+        private fun humanReadable(colors: List<Int>): String {
+            return colors.joinToString { "#" + Integer.toHexString(it) }
+        }
+
+        private fun score(cam: Cam, proportion: Double): Double {
+            val proportionScore = 0.7 * 100.0 * proportion
+            val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA)
+            else 0.3 * (cam.chroma - ACCENT1_CHROMA)
+            return chromaScore + proportionScore
+        }
+
+        private fun huePopulations(
+            camByColor: Map<Int, Cam>,
+            populationByColor: Map<Int, Double>
+        ): List<Double> {
+            val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
+
+            for (entry in populationByColor.entries) {
+                val population = populationByColor[entry.key]!!
+                val cam = camByColor[entry.key]!!
+                val hue = cam.hue.roundToInt() % 360
+                huePopulation[hue] = huePopulation[hue] + population
+            }
+
+            return huePopulation
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
new file mode 100644
index 0000000..498b7dd
--- /dev/null
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java
@@ -0,0 +1,64 @@
+/*
+ * 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.systemui.monet;
+
+
+import androidx.annotation.ColorInt;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
+
+
+/**
+ * Generate sets of colors that are shades of the same color
+ */
+@VisibleForTesting
+public class Shades {
+    /**
+     *  Combining the ability to convert between relative luminance and perceptual luminance with
+     *  contrast leads to a design system that can be based on a linear value to determine contrast,
+     *  rather than a ratio.
+     *
+     *  This codebase implements a design system that has that property, and as a result, we can
+     *  guarantee that any shades 5 steps from each other have a contrast ratio of at least 4.5.
+     *  4.5 is the requirement for smaller text contrast in WCAG 2.1 and earlier.
+     *
+     *  However, lstar 50 does _not_ have a contrast ratio >= 4.5 with lstar 100.
+     *  lstar 49.6 is the smallest lstar that will lead to a contrast ratio >= 4.5 with lstar 100,
+     *  and it also contrasts >= 4.5 with lstar 100.
+     */
+    public static final float MIDDLE_LSTAR = 49.6f;
+
+    /**
+     * Generate shades of a color. Ordered in lightness _descending_.
+     * <p>
+     * The first shade will be at 95% lightness, the next at 90, 80, etc. through 0.
+     *
+     * @param hue    hue in CAM16 color space
+     * @param chroma chroma in CAM16 color space
+     * @return shades of a color, as argb integers. Ordered by lightness descending.
+     */
+    public static @ColorInt int[] of(float hue, float chroma) {
+        int[] shades = new int[12];
+        shades[0] = ColorUtils.CAMToColor(hue, chroma, 99);
+        shades[1] = ColorUtils.CAMToColor(hue, chroma, 95);
+        for (int i = 2; i < 12; i++) {
+            float lStar = (i == 6) ? MIDDLE_LSTAR : 100 - 10 * (i - 1);
+            shades[i] = ColorUtils.CAMToColor(hue, chroma, lStar);
+        }
+        return shades;
+    }
+}
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index c2b87a5..d442cc5 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -22,7 +22,7 @@
     <bool name="flag_notification_pipeline2_rendering">false</bool>
     <bool name="flag_notif_updates">true</bool>
 
-    <bool name="flag_monet">false</bool>
+    <bool name="flag_monet">true</bool>
 
     <!-- AOD/Lockscreen alternate layout -->
     <bool name="flag_keyguard_layout">true</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 0130cb2..a288d999 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -37,6 +37,7 @@
 import android.content.om.FabricatedOverlay;
 import android.content.om.OverlayIdentifier;
 import android.content.pm.UserInfo;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.Color;
 import android.net.Uri;
@@ -47,9 +48,11 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.TypedValue;
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -59,6 +62,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -71,6 +75,7 @@
 import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -107,6 +112,7 @@
     private DeviceProvisionedController mDeviceProvisionedController;
     private WallpaperColors mCurrentColors;
     private WallpaperManager mWallpaperManager;
+    private ColorScheme mColorScheme;
     // If fabricated overlays were already created for the current theme.
     private boolean mNeedsOverlayCreation;
     // Dominant color extracted from wallpaper, NOT the color used on the overlay
@@ -403,25 +409,47 @@
      * Return the main theme color from a given {@link WallpaperColors} instance.
      */
     protected int getNeutralColor(@NonNull WallpaperColors wallpaperColors) {
-        return wallpaperColors.getPrimaryColor().toArgb();
+        return ColorScheme.getSeedColor(wallpaperColors);
     }
 
     protected int getAccentColor(@NonNull WallpaperColors wallpaperColors) {
-        Color accentCandidate = wallpaperColors.getSecondaryColor();
-        if (accentCandidate == null) {
-            accentCandidate = wallpaperColors.getTertiaryColor();
-        }
-        if (accentCandidate == null) {
-            accentCandidate = wallpaperColors.getPrimaryColor();
-        }
-        return accentCandidate.toArgb();
+        return ColorScheme.getSeedColor(wallpaperColors);
     }
 
     /**
      * Given a color candidate, return an overlay definition.
      */
     protected @Nullable FabricatedOverlay getOverlay(int color, int type) {
-        return null;
+        boolean nightMode = (mContext.getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+
+        mColorScheme = new ColorScheme(color, nightMode);
+        List<Integer> colorShades = type == ACCENT
+                ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors();
+        String name = type == ACCENT ? "accent" : "neutral";
+        int paletteSize = mColorScheme.getAccent1().size();
+        FabricatedOverlay.Builder overlay =
+                new FabricatedOverlay.Builder("com.android.systemui", name, "android");
+        for (int i = 0; i < colorShades.size(); i++) {
+            int luminosity = i % paletteSize;
+            int paletteIndex = i / paletteSize + 1;
+            String resourceName;
+            switch (luminosity) {
+                case 0:
+                    resourceName = "android:color/system_" + name + paletteIndex + "_10";
+                    break;
+                case 1:
+                    resourceName = "android:color/system_" + name + paletteIndex + "_50";
+                    break;
+                default:
+                    int l = luminosity - 1;
+                    resourceName = "android:color/system_" + name + paletteIndex + "_" + l + "00";
+            }
+            overlay.setResourceValue(resourceName, TypedValue.TYPE_INT_COLOR_ARGB8,
+                    ColorUtils.setAlphaComponent(colorShades.get(i), 0xFF));
+        }
+
+        return overlay.build();
     }
 
     private void updateThemeOverlays() {
@@ -521,7 +549,7 @@
                     .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
                             Collectors.joining(", ")));
         }
-        Runnable overlaysAppliedRunnable = () -> onOverlaysApplied();
+        Runnable overlaysAppliedRunnable = this::onOverlaysApplied;
         if (mNeedsOverlayCreation) {
             mNeedsOverlayCreation = false;
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
@@ -544,6 +572,7 @@
         pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
         pw.println("mNeutralOverlay=" + mNeutralOverlay);
         pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+        pw.println("mColorScheme=" + mColorScheme);
         pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
         pw.println("mAcceptColorEvents=" + mAcceptColorEvents);
         pw.println("mDeferredThemeEvaluation=" + mDeferredThemeEvaluation);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
new file mode 100644
index 0000000..bc86ef9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.systemui.monet;
+
+import android.app.WallpaperColors;
+import android.graphics.Color;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ColorSchemeTest extends SysuiTestCase {
+    @Test
+    public void testFilterTransparency() {
+        ColorScheme colorScheme = new ColorScheme(Color.TRANSPARENT, false /* darkTheme */);
+        Assert.assertEquals(colorScheme.getAllAccentColors(),
+                new ColorScheme(0xFF1b6ef3, false).getAllAccentColors());
+    }
+
+    @Test
+    public void testDontFilterOpaque() {
+        ColorScheme colorScheme = new ColorScheme(0xFFFF0000, false /* darkTheme */);
+        Assert.assertNotEquals(colorScheme.getAllAccentColors(),
+                new ColorScheme(0xFF1b6ef3, false).getAllAccentColors());
+    }
+
+    @Test
+    public void testUniqueColors() {
+        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a),
+                Color.valueOf(0xffaec00a), Color.valueOf(0xffaec00a));
+
+        List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
+        Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a));
+    }
+
+
+    @Test
+    public void testFiltersInvalidColors() {
+        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xff5e7ea2),
+                Color.valueOf(0xff5e7ea2), Color.valueOf(0xff000000));
+
+        List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
+        Assert.assertEquals(rankedSeedColors, List.of(0xff5e7ea2));
+    }
+
+    @Test
+    public void testInvalidColorBecomesGBlue() {
+        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xff000000), null,
+                null);
+
+        List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
+        Assert.assertEquals(rankedSeedColors, List.of(0xFF1b6ef3));
+    }
+
+    @Test
+    public void testDontFilterRRGGBB() {
+        ColorScheme colorScheme = new ColorScheme(0xFF0000, false /* darkTheme */);
+        Assert.assertEquals(colorScheme.getAllAccentColors(),
+                new ColorScheme(0xFFFF0000, false).getAllAccentColors());
+    }
+
+    @Test
+    public void testNoPopulationSignal() {
+        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a),
+                Color.valueOf(0xffbe0000), Color.valueOf(0xffcc040f));
+
+        List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
+        Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a, 0xffbe0000, 0xffcc040f));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index cd911cc..4c282bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -167,7 +167,7 @@
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
                 .isEqualTo(new OverlayIdentifier("ffff0000"));
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
-                .isEqualTo(new OverlayIdentifier("ff0000ff"));
+                .isEqualTo(new OverlayIdentifier("ffff0000"));
 
         // Should not ask again if changed to same value
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);