Cleanup for SystemUI's ColorScheme

Bug: 327338148
Test: atest SystemPaletteTest
Test: atest ThemeOverlayControllerTest
Flag: NONE
Change-Id: Ic185d31d1206ea7eb5463ac80d09f27e02f0b35a
diff --git a/packages/SystemUI/monet/Android.bp b/packages/SystemUI/monet/Android.bp
index 98f7ace..c54fdab 100644
--- a/packages/SystemUI/monet/Android.bp
+++ b/packages/SystemUI/monet/Android.bp
@@ -24,6 +24,7 @@
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.core_core",
+        "libmonet",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index 46a90f6..47a00f4 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -20,11 +20,17 @@
 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
+import com.google.ux.material.libmonet.hct.Hct
+import com.google.ux.material.libmonet.scheme.DynamicScheme
+import com.google.ux.material.libmonet.scheme.SchemeContent
+import com.google.ux.material.libmonet.scheme.SchemeExpressive
+import com.google.ux.material.libmonet.scheme.SchemeFruitSalad
+import com.google.ux.material.libmonet.scheme.SchemeMonochrome
+import com.google.ux.material.libmonet.scheme.SchemeNeutral
+import com.google.ux.material.libmonet.scheme.SchemeRainbow
+import com.google.ux.material.libmonet.scheme.SchemeTonalSpot
+import com.google.ux.material.libmonet.scheme.SchemeVibrant
 import kotlin.math.absoluteValue
-import kotlin.math.max
-import kotlin.math.min
 import kotlin.math.roundToInt
 
 const val TAG = "ColorScheme"
@@ -33,347 +39,65 @@
 const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
 const val MIN_CHROMA = 5
 
-internal interface Hue {
-    fun get(sourceColor: Cam): Double
-
-    /**
-     * Given a hue, and a mapping of hues to hue rotations, find which hues in the mapping the hue
-     * fall betweens, and use the hue rotation of the lower hue.
-     *
-     * @param sourceHue hue of source color
-     * @param hueAndRotations list of pairs, where the first item in a pair is a hue, and the second
-     *   item in the pair is a hue rotation that should be applied
-     */
-    fun getHueRotation(sourceHue: Float, hueAndRotations: List<Pair<Int, Int>>): Double {
-        val sanitizedSourceHue = (if (sourceHue < 0 || sourceHue >= 360) 0 else sourceHue).toFloat()
-        for (i in 0..hueAndRotations.size - 2) {
-            val thisHue = hueAndRotations[i].first.toFloat()
-            val nextHue = hueAndRotations[i + 1].first.toFloat()
-            if (thisHue <= sanitizedSourceHue && sanitizedSourceHue < nextHue) {
-                return ColorScheme.wrapDegreesDouble(
-                    sanitizedSourceHue.toDouble() + hueAndRotations[i].second
-                )
-            }
-        }
-
-        // If this statement executes, something is wrong, there should have been a rotation
-        // found using the arrays.
-        return sourceHue.toDouble()
-    }
-}
-
-internal class HueSource : Hue {
-    override fun get(sourceColor: Cam): Double {
-        return sourceColor.hue.toDouble()
-    }
-}
-
-internal class HueAdd(val amountDegrees: Double) : Hue {
-    override fun get(sourceColor: Cam): Double {
-        return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() + amountDegrees)
-    }
-}
-
-internal class HueSubtract(val amountDegrees: Double) : Hue {
-    override fun get(sourceColor: Cam): Double {
-        return ColorScheme.wrapDegreesDouble(sourceColor.hue.toDouble() - amountDegrees)
-    }
-}
-
-internal class HueVibrantSecondary() : Hue {
-    val hueToRotations =
-        listOf(
-            Pair(0, 18),
-            Pair(41, 15),
-            Pair(61, 10),
-            Pair(101, 12),
-            Pair(131, 15),
-            Pair(181, 18),
-            Pair(251, 15),
-            Pair(301, 12),
-            Pair(360, 12)
-        )
-
-    override fun get(sourceColor: Cam): Double {
-        return getHueRotation(sourceColor.hue, hueToRotations)
-    }
-}
-
-internal class HueVibrantTertiary() : Hue {
-    val hueToRotations =
-        listOf(
-            Pair(0, 35),
-            Pair(41, 30),
-            Pair(61, 20),
-            Pair(101, 25),
-            Pair(131, 30),
-            Pair(181, 35),
-            Pair(251, 30),
-            Pair(301, 25),
-            Pair(360, 25)
-        )
-
-    override fun get(sourceColor: Cam): Double {
-        return getHueRotation(sourceColor.hue, hueToRotations)
-    }
-}
-
-internal class HueExpressiveSecondary() : Hue {
-    val hueToRotations =
-        listOf(
-            Pair(0, 45),
-            Pair(21, 95),
-            Pair(51, 45),
-            Pair(121, 20),
-            Pair(151, 45),
-            Pair(191, 90),
-            Pair(271, 45),
-            Pair(321, 45),
-            Pair(360, 45)
-        )
-
-    override fun get(sourceColor: Cam): Double {
-        return getHueRotation(sourceColor.hue, hueToRotations)
-    }
-}
-
-internal class HueExpressiveTertiary() : Hue {
-    val hueToRotations =
-        listOf(
-            Pair(0, 120),
-            Pair(21, 120),
-            Pair(51, 20),
-            Pair(121, 45),
-            Pair(151, 20),
-            Pair(191, 15),
-            Pair(271, 20),
-            Pair(321, 120),
-            Pair(360, 120)
-        )
-
-    override fun get(sourceColor: Cam): Double {
-        return getHueRotation(sourceColor.hue, hueToRotations)
-    }
-}
-
-internal interface Chroma {
-    fun get(sourceColor: Cam): Double
-
-    companion object {
-        val MAX_VALUE = 120.0
-        val MIN_VALUE = 0.0
-    }
-}
-
-internal class ChromaMaxOut : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        // Intentionally high. Gamut mapping from impossible HCT to sRGB will ensure that
-        // the maximum chroma is reached, even if lower than this constant.
-        return Chroma.MAX_VALUE + 10.0
-    }
-}
-
-internal class ChromaMultiple(val multiple: Double) : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        return sourceColor.chroma * multiple
-    }
-}
-
-internal class ChromaAdd(val amount: Double) : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        return sourceColor.chroma + amount
-    }
-}
-
-internal class ChromaBound(
-    val baseChroma: Chroma,
-    val minVal: Double,
-    val maxVal: Double,
-) : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        val result = baseChroma.get(sourceColor)
-        return min(max(result, minVal), maxVal)
-    }
-}
-
-internal class ChromaConstant(val chroma: Double) : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        return chroma
-    }
-}
-
-internal class ChromaSource : Chroma {
-    override fun get(sourceColor: Cam): Double {
-        return sourceColor.chroma.toDouble()
-    }
-}
-
-internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) {
-    fun shades(sourceColor: Cam): List<Int> {
-        val hue = hue.get(sourceColor)
-        val chroma = chroma.get(sourceColor)
-        return Shades.of(hue.toFloat(), chroma.toFloat()).toList()
-    }
-
-    fun getAtTone(sourceColor: Cam, tone: Float): Int {
-        val hue = hue.get(sourceColor)
-        val chroma = chroma.get(sourceColor)
-        return ColorUtils.CAMToColor(hue.toFloat(), chroma.toFloat(), (1000f - tone) / 10f)
-    }
-}
-
-internal class CoreSpec(
-    val a1: TonalSpec,
-    val a2: TonalSpec,
-    val a3: TonalSpec,
-    val n1: TonalSpec,
-    val n2: TonalSpec
-)
-
-enum class Style(internal val coreSpec: CoreSpec) {
-    SPRITZ(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaConstant(12.0)),
-            a2 = TonalSpec(HueSource(), ChromaConstant(8.0)),
-            a3 = TonalSpec(HueSource(), ChromaConstant(16.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(2.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(2.0))
-        )
-    ),
-    TONAL_SPOT(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaConstant(36.0)),
-            a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
-            a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(6.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(8.0))
-        )
-    ),
-    VIBRANT(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaMaxOut()),
-            a2 = TonalSpec(HueVibrantSecondary(), ChromaConstant(24.0)),
-            a3 = TonalSpec(HueVibrantTertiary(), ChromaConstant(32.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(12.0))
-        )
-    ),
-    EXPRESSIVE(
-        CoreSpec(
-            a1 = TonalSpec(HueAdd(240.0), ChromaConstant(40.0)),
-            a2 = TonalSpec(HueExpressiveSecondary(), ChromaConstant(24.0)),
-            a3 = TonalSpec(HueExpressiveTertiary(), ChromaConstant(32.0)),
-            n1 = TonalSpec(HueAdd(15.0), ChromaConstant(8.0)),
-            n2 = TonalSpec(HueAdd(15.0), ChromaConstant(12.0))
-        )
-    ),
-    RAINBOW(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaConstant(48.0)),
-            a2 = TonalSpec(HueSource(), ChromaConstant(16.0)),
-            a3 = TonalSpec(HueAdd(60.0), ChromaConstant(24.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
-        )
-    ),
-    FRUIT_SALAD(
-        CoreSpec(
-            a1 = TonalSpec(HueSubtract(50.0), ChromaConstant(48.0)),
-            a2 = TonalSpec(HueSubtract(50.0), ChromaConstant(36.0)),
-            a3 = TonalSpec(HueSource(), ChromaConstant(36.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(10.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(16.0))
-        )
-    ),
-    CONTENT(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaSource()),
-            a2 = TonalSpec(HueSource(), ChromaMultiple(0.33)),
-            a3 = TonalSpec(HueSource(), ChromaMultiple(0.66)),
-            n1 = TonalSpec(HueSource(), ChromaMultiple(0.0833)),
-            n2 = TonalSpec(HueSource(), ChromaMultiple(0.1666))
-        )
-    ),
-    MONOCHROMATIC(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaConstant(.0)),
-            a2 = TonalSpec(HueSource(), ChromaConstant(.0)),
-            a3 = TonalSpec(HueSource(), ChromaConstant(.0)),
-            n1 = TonalSpec(HueSource(), ChromaConstant(.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(.0))
-        )
-    ),
-    CLOCK(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 20.0, Chroma.MAX_VALUE)),
-            a2 = TonalSpec(HueAdd(10.0), ChromaBound(ChromaMultiple(0.85), 17.0, 40.0)),
-            a3 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaAdd(20.0), 50.0, Chroma.MAX_VALUE)),
-
-            // Not Used
-            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
-        )
-    ),
-    CLOCK_VIBRANT(
-        CoreSpec(
-            a1 = TonalSpec(HueSource(), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)),
-            a2 = TonalSpec(HueAdd(20.0), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)),
-            a3 = TonalSpec(HueAdd(60.0), ChromaBound(ChromaSource(), 70.0, Chroma.MAX_VALUE)),
-
-            // Not Used
-            n1 = TonalSpec(HueSource(), ChromaConstant(0.0)),
-            n2 = TonalSpec(HueSource(), ChromaConstant(0.0))
-        )
-    )
+enum class Style{
+    SPRITZ,
+    TONAL_SPOT,
+    VIBRANT,
+    EXPRESSIVE,
+    RAINBOW,
+    FRUIT_SALAD,
+    CONTENT,
+    MONOCHROMATIC,
+    CLOCK,
+    CLOCK_VIBRANT
 }
 
 class TonalPalette
 internal constructor(
-    private val spec: TonalSpec,
-    seedColor: Int,
+    private val materialTonalPalette: com.google.ux.material.libmonet.palettes.TonalPalette
 ) {
-    val seedCam: Cam = Cam.fromInt(seedColor)
-    val allShades: List<Int> = spec.shades(seedCam)
-    val allShadesMapped: Map<Int, Int> = SHADE_KEYS.zip(allShades).toMap()
-    val baseColor: Int
+    @Deprecated("Do not use. For color system only")
+    val allShades: List<Int>
+    val allShadesMapped: Map<Int, Int>
 
-    init {
-        val h = spec.hue.get(seedCam).toFloat()
-        val c = spec.chroma.get(seedCam).toFloat()
-        baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor))
+    init{
+        allShades = SHADE_KEYS.map {key -> getAtTone(key.toFloat()) }
+        allShadesMapped = SHADE_KEYS.zip(allShades).toMap()
     }
 
     // Dynamically computed tones across the full range from 0 to 1000
-    fun getAtTone(tone: Float) = spec.getAtTone(seedCam, tone)
+    fun getAtTone(shade: Float): Int = materialTonalPalette.tone(((1000.0f - shade) / 10f).toInt())
 
     // Predefined & precomputed tones
-    val s10: Int
+    val s0: Int
         get() = this.allShades[0]
-    val s50: Int
+    val s10: Int
         get() = this.allShades[1]
-    val s100: Int
+    val s50: Int
         get() = this.allShades[2]
-    val s200: Int
+    val s100: Int
         get() = this.allShades[3]
-    val s300: Int
+    val s200: Int
         get() = this.allShades[4]
-    val s400: Int
+    val s300: Int
         get() = this.allShades[5]
-    val s500: Int
+    val s400: Int
         get() = this.allShades[6]
-    val s600: Int
+    val s500: Int
         get() = this.allShades[7]
-    val s700: Int
+    val s600: Int
         get() = this.allShades[8]
-    val s800: Int
+    val s700: Int
         get() = this.allShades[9]
-    val s900: Int
+    val s800: Int
         get() = this.allShades[10]
-    val s1000: Int
+    val s900: Int
         get() = this.allShades[11]
+    val s1000: Int
+        get() = this.allShades[12]
 
     companion object {
-        val SHADE_KEYS = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
+        val SHADE_KEYS = listOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
     }
 }
 
@@ -381,9 +105,20 @@
         "instead")
 class ColorScheme(
     @ColorInt val seed: Int,
-    val darkTheme: Boolean,
-    val style: Style = Style.TONAL_SPOT
+    val isDark: Boolean,
+    val style: Style,
+    val contrastLevel: Double
 ) {
+    var materialScheme: DynamicScheme
+
+    private val proposedSeedHct: Hct = Hct.fromInt(seed)
+    private val seedHct: Hct = Hct.fromInt(if (seed == Color.TRANSPARENT) {
+        GOOGLE_BLUE
+    } else if (style != Style.CONTENT && proposedSeedHct.chroma < 5) {
+        GOOGLE_BLUE
+    } else {
+        seed
+    })
 
     val accent1: TonalPalette
     val accent2: TonalPalette
@@ -395,62 +130,49 @@
 
     @JvmOverloads
     constructor(
+        @ColorInt seed: Int,
+        darkTheme: Boolean,
+        style: Style
+    ) : this(seed, darkTheme, style, 0.5)
+
+    @JvmOverloads
+    constructor(
         wallpaperColors: WallpaperColors,
         darkTheme: Boolean,
         style: Style = Style.TONAL_SPOT
     ) : this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
 
-    val allHues: List<TonalPalette>
-        get() {
-            return listOf(accent1, accent2, accent3, neutral1, neutral2)
-        }
-
-    val allAccentColors: List<Int>
-        get() {
-            val allColors = mutableListOf<Int>()
-            allColors.addAll(accent1.allShades)
-            allColors.addAll(accent2.allShades)
-            allColors.addAll(accent3.allShades)
-            return allColors
-        }
-
-    val allNeutralColors: List<Int>
-        get() {
-            val allColors = mutableListOf<Int>()
-            allColors.addAll(neutral1.allShades)
-            allColors.addAll(neutral2.allShades)
-            return allColors
-        }
-
     val backgroundColor
-        get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1.s700 else neutral1.s10, 0xFF)
+        get() = ColorUtils.setAlphaComponent(if (isDark) neutral1.s700 else neutral1.s10, 0xFF)
 
     val accentColor
-        get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1.s100 else accent1.s500, 0xFF)
+        get() = ColorUtils.setAlphaComponent(if (isDark) accent1.s100 else accent1.s500, 0xFF)
 
     init {
-        val proposedSeedCam = Cam.fromInt(seed)
-        val seedArgb =
-            if (seed == Color.TRANSPARENT) {
-                GOOGLE_BLUE
-            } else if (style != Style.CONTENT && proposedSeedCam.chroma < 5) {
-                GOOGLE_BLUE
-            } else {
-                seed
-            }
+        materialScheme = when (style) {
+            Style.SPRITZ -> SchemeNeutral(seedHct, isDark, contrastLevel)
+            Style.TONAL_SPOT -> SchemeTonalSpot(seedHct, isDark, contrastLevel)
+            Style.VIBRANT -> SchemeVibrant(seedHct, isDark, contrastLevel)
+            Style.EXPRESSIVE -> SchemeExpressive(seedHct, isDark, contrastLevel)
+            Style.RAINBOW -> SchemeRainbow(seedHct, isDark, contrastLevel)
+            Style.FRUIT_SALAD -> SchemeFruitSalad(seedHct, isDark, contrastLevel)
+            Style.CONTENT -> SchemeContent(seedHct, isDark, contrastLevel)
+            Style.MONOCHROMATIC -> SchemeMonochrome(seedHct, isDark, contrastLevel)
 
-        accent1 = TonalPalette(style.coreSpec.a1, seedArgb)
-        accent2 = TonalPalette(style.coreSpec.a2, seedArgb)
-        accent3 = TonalPalette(style.coreSpec.a3, seedArgb)
-        neutral1 = TonalPalette(style.coreSpec.n1, seedArgb)
-        neutral2 = TonalPalette(style.coreSpec.n2, seedArgb)
+            // SystemUI Schemes
+            Style.CLOCK -> SchemeClock(seedHct, isDark, contrastLevel)
+            Style.CLOCK_VIBRANT -> SchemeClockVibrant(seedHct, isDark, contrastLevel)
+        }
+
+        accent1 = TonalPalette(materialScheme.primaryPalette)
+        accent2 = TonalPalette(materialScheme.secondaryPalette)
+        accent3 = TonalPalette(materialScheme.tertiaryPalette)
+        neutral1 = TonalPalette(materialScheme.neutralPalette)
+        neutral2 = TonalPalette(materialScheme.neutralVariantPalette)
     }
 
-    val shadeCount
-        get() = this.accent1.allShades.size
-
     val seedTone: Float
-        get() = 1000f - CamUtils.lstarFromInt(seed) * 10f
+        get() = 1000f - proposedSeedHct.tone.toFloat() * 10f
 
     override fun toString(): String {
         return "ColorScheme {\n" +
@@ -507,7 +229,7 @@
                             if (!filter) {
                                 true
                             } else {
-                                Cam.fromInt(it).chroma >= MIN_CHROMA
+                                Hct.fromInt(it).chroma >= MIN_CHROMA
                             }
                         }
                         .toList()
@@ -519,15 +241,15 @@
 
             val intToProportion =
                 wallpaperColors.allColors.mapValues { it.value.toDouble() / totalPopulation }
-            val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) }
+            val intToHct = wallpaperColors.allColors.mapValues { Hct.fromInt(it.key) }
 
             // Get an array with 360 slots. A slot contains the percentage of colors with that hue.
-            val hueProportions = huePopulations(intToCam, intToProportion, filter)
+            val hueProportions = huePopulations(intToHct, intToProportion, filter)
             // 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()
+                    val hct = intToHct[it.key]!!
+                    val hue = hct.hue.roundToInt()
                     var proportion = 0.0
                     for (i in hue - 15..hue + 15) {
                         proportion += hueProportions[wrapDegrees(i)]
@@ -537,18 +259,18 @@
             // 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 =
-                if (!filter) intToCam
+            val filteredIntToHct =
+                if (!filter) intToHct
                 else
-                    (intToCam.filter {
-                        val cam = it.value
+                    (intToHct.filter {
+                        val hct = it.value
                         val proportion = intToHueProportion[it.key]!!
-                        cam.chroma >= MIN_CHROMA &&
+                        hct.chroma >= MIN_CHROMA &&
                             (totalPopulationMeaningless || proportion > 0.01)
                     })
             // Sort the colors by score, from high to low.
             val intToScoreIntermediate =
-                filteredIntToCam.mapValues { score(it.value, intToHueProportion[it.key]!!) }
+                filteredIntToHct.mapValues { score(it.value, intToHueProportion[it.key]!!) }
             val intToScore = intToScoreIntermediate.entries.toMutableList()
             intToScore.sortByDescending { it.value }
 
@@ -564,8 +286,8 @@
                     val int = entry.key
                     val existingSeedNearby =
                         seeds.find {
-                            val hueA = intToCam[int]!!.hue
-                            val hueB = intToCam[it]!!.hue
+                            val hueA = intToHct[int]!!.hue
+                            val hueB = intToHct[it]!!.hue
                             hueDiff(hueA, hueB) < i
                         } != null
                     if (existingSeedNearby) {
@@ -600,30 +322,16 @@
             }
         }
 
-        public fun wrapDegreesDouble(degrees: Double): Double {
-            return when {
-                degrees < 0 -> {
-                    (degrees % 360) + 360
-                }
-                degrees >= 360 -> {
-                    degrees % 360
-                }
-                else -> {
-                    degrees
-                }
-            }
-        }
-
-        private fun hueDiff(a: Float, b: Float): Float {
+        private fun hueDiff(a: Double, b: Double): Double {
             return 180f - ((a - b).absoluteValue - 180f).absoluteValue
         }
 
         private fun stringForColor(color: Int): String {
             val width = 4
-            val hct = Cam.fromInt(color)
+            val hct = Hct.fromInt(color)
             val h = "H${hct.hue.roundToInt().toString().padEnd(width)}"
             val c = "C${hct.chroma.roundToInt().toString().padEnd(width)}"
-            val t = "T${CamUtils.lstarFromInt(color).roundToInt().toString().padEnd(width)}"
+            val t = "T${hct.tone.roundToInt().toString().padEnd(width)}"
             val hex = Integer.toHexString(color and 0xffffff).padStart(6, '0').uppercase()
             return "$h$c$t = #$hex"
         }
@@ -633,16 +341,16 @@
                 colors.map { stringForColor(it) }.joinToString(separator = "\n") { it }
         }
 
-        private fun score(cam: Cam, proportion: Double): Double {
+        private fun score(hct: Hct, 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)
+                if (hct.chroma < ACCENT1_CHROMA) 0.1 * (hct.chroma - ACCENT1_CHROMA)
+                else 0.3 * (hct.chroma - ACCENT1_CHROMA)
             return chromaScore + proportionScore
         }
 
         private fun huePopulations(
-            camByColor: Map<Int, Cam>,
+            hctByColor: Map<Int, Hct>,
             populationByColor: Map<Int, Double>,
             filter: Boolean = true
         ): List<Double> {
@@ -650,9 +358,9 @@
 
             for (entry in populationByColor.entries) {
                 val population = populationByColor[entry.key]!!
-                val cam = camByColor[entry.key]!!
-                val hue = cam.hue.roundToInt() % 360
-                if (filter && cam.chroma <= MIN_CHROMA) {
+                val hct = hctByColor[entry.key]!!
+                val hue = hct.hue.roundToInt() % 360
+                if (filter && hct.chroma <= MIN_CHROMA) {
                     continue
                 }
                 huePopulation[hue] = huePopulation[hue] + population
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java
new file mode 100644
index 0000000..4747cc5
--- /dev/null
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClock.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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 static com.google.ux.material.libmonet.utils.MathUtils.clampDouble;
+
+import static java.lang.Double.max;
+
+import com.google.ux.material.libmonet.hct.Hct;
+import com.google.ux.material.libmonet.palettes.TonalPalette;
+import com.google.ux.material.libmonet.scheme.DynamicScheme;
+import com.google.ux.material.libmonet.scheme.Variant;
+
+public class SchemeClock extends DynamicScheme {
+    public SchemeClock(Hct sourceColorHct, boolean isDark, double contrastLevel) {
+        super(
+                sourceColorHct,
+                Variant.MONOCHROME,
+                isDark,
+                contrastLevel,
+                /*primary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue(),
+                        /*chroma*/ max(sourceColorHct.getChroma(), 20)
+                ),
+                /*secondary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue() + 10.0,
+                        /*chroma*/ clampDouble(17, 40, sourceColorHct.getChroma() * 0.85)
+                ),
+                /*tertiary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue() + 20.0,
+                        /*chroma*/ max(sourceColorHct.getChroma() + 20, 50)
+                ),
+
+                //not used
+                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
+                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
+    }
+}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java
new file mode 100644
index 0000000..fb5e972
--- /dev/null
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/SchemeClockVibrant.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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 static java.lang.Double.max;
+
+import com.google.ux.material.libmonet.hct.Hct;
+import com.google.ux.material.libmonet.palettes.TonalPalette;
+import com.google.ux.material.libmonet.scheme.DynamicScheme;
+import com.google.ux.material.libmonet.scheme.Variant;
+
+public class SchemeClockVibrant extends DynamicScheme {
+    public SchemeClockVibrant(Hct sourceColorHct, boolean isDark, double contrastLevel) {
+        super(
+                sourceColorHct,
+                Variant.MONOCHROME,
+                isDark,
+                contrastLevel,
+                /*primary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue(),
+                        /*chroma*/ max(sourceColorHct.getChroma(), 70)
+                ),
+                /*secondary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue() + 20.0,
+                        /*chroma*/ max(sourceColorHct.getChroma(), 70)
+                ),
+                /*tertiary*/
+                TonalPalette.fromHueAndChroma(
+                        /*hue*/ sourceColorHct.getHue() + 60.0,
+                        /*chroma*/ max(sourceColorHct.getChroma(), 70)
+                ),
+
+                //not used
+                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
+                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index b5efc44..aac18e3 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -86,15 +86,6 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
-import com.google.ux.material.libmonet.hct.Hct;
-import com.google.ux.material.libmonet.scheme.DynamicScheme;
-import com.google.ux.material.libmonet.scheme.SchemeExpressive;
-import com.google.ux.material.libmonet.scheme.SchemeFruitSalad;
-import com.google.ux.material.libmonet.scheme.SchemeMonochrome;
-import com.google.ux.material.libmonet.scheme.SchemeNeutral;
-import com.google.ux.material.libmonet.scheme.SchemeRainbow;
-import com.google.ux.material.libmonet.scheme.SchemeTonalSpot;
-import com.google.ux.material.libmonet.scheme.SchemeVibrant;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -150,7 +141,7 @@
     // Dominant color extracted from wallpaper, NOT the color used on the overlay
     protected int mMainWallpaperColor = Color.TRANSPARENT;
     // UI contrast as reported by UiModeManager
-    private float mContrast = 0;
+    private double mContrast = 0.0;
     // Theme variant: Vibrant, Tonal, Expressive, etc
     @VisibleForTesting
     protected Style mThemeStyle = Style.TONAL_SPOT;
@@ -170,8 +161,8 @@
     private final JavaAdapter mJavaAdapter;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final UiModeManager mUiModeManager;
-    private DynamicScheme mDynamicSchemeDark;
-    private DynamicScheme mDynamicSchemeLight;
+    private ColorScheme mDarkColorScheme;
+    private ColorScheme mLightColorScheme;
 
     // Defers changing themes until Setup Wizard is done.
     private boolean mDeferredThemeEvaluation;
@@ -210,7 +201,7 @@
             boolean currentUser = userId == mUserTracker.getUserId();
             boolean isAsleep = themeOverlayControllerWakefulnessDeprecation()
                     ? mKeyguardTransitionInteractor.isFinishedInStateWhereValue(
-                        state -> KeyguardState.Companion.deviceIsAsleepInState(state))
+                    KeyguardState.Companion::deviceIsAsleepInState)
                     : mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP;
 
             if (currentUser && !mAcceptColorEvents && isAsleep) {
@@ -483,19 +474,14 @@
             reevaluateSystemTheme(true /* forceReload */);
         });
 
+        // All wallpaper color and keyguard logic only applies when Monet is enabled.
         if (!mIsMonetEnabled) {
             return;
         }
 
         mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
-
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
-        // All wallpaper color and keyguard logic only applies when Monet is enabled.
-        if (!mIsMonetEnabled) {
-            return;
-        }
-
         // Upon boot, make sure we have the most up to date colors
         Runnable updateColors = () -> {
             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
@@ -589,29 +575,6 @@
         return ColorScheme.getSeedColor(wallpaperColors);
     }
 
-    private static DynamicScheme dynamicSchemeFromStyle(Style style, int color,
-            boolean isDark, double contrastLevel) {
-        Hct sourceColorHct = Hct.fromInt(color);
-        switch (style) {
-            case EXPRESSIVE:
-                return new SchemeExpressive(sourceColorHct, isDark, contrastLevel);
-            case SPRITZ:
-                return new SchemeNeutral(sourceColorHct, isDark, contrastLevel);
-            case TONAL_SPOT:
-                return new SchemeTonalSpot(sourceColorHct, isDark, contrastLevel);
-            case FRUIT_SALAD:
-                return new SchemeFruitSalad(sourceColorHct, isDark, contrastLevel);
-            case RAINBOW:
-                return new SchemeRainbow(sourceColorHct, isDark, contrastLevel);
-            case VIBRANT:
-                return new SchemeVibrant(sourceColorHct, isDark, contrastLevel);
-            case MONOCHROMATIC:
-                return new SchemeMonochrome(sourceColorHct, isDark, contrastLevel);
-            default:
-                return null;
-        }
-    }
-
     @VisibleForTesting
     protected boolean isNightMode() {
         return (mResources.getConfiguration().uiMode
@@ -626,22 +589,17 @@
     @VisibleForTesting
     protected boolean isPrivateProfile(UserHandle userHandle) {
         Context usercontext = mContext.createContextAsUser(userHandle,0);
-        if (usercontext.getSystemService(UserManager.class).isPrivateProfile()) {
-            return true;
-        }
-        return false;
+        return usercontext.getSystemService(UserManager.class).isPrivateProfile();
     }
 
     private void createOverlays(int color) {
-        boolean nightMode = isNightMode();
-        mColorScheme = new ColorScheme(color, nightMode, mThemeStyle);
+        mDarkColorScheme = new ColorScheme(color, true /* isDark */, mThemeStyle, mContrast);
+        mLightColorScheme = new ColorScheme(color, false /* isDark */, mThemeStyle, mContrast);
+        mColorScheme = isNightMode() ? mDarkColorScheme : mLightColorScheme;
+
         mNeutralOverlay = createNeutralOverlay();
         mSecondaryOverlay = createAccentOverlay();
 
-        mDynamicSchemeDark = dynamicSchemeFromStyle(
-                mThemeStyle, color, true /* isDark */, mContrast);
-        mDynamicSchemeLight = dynamicSchemeFromStyle(
-                mThemeStyle, color, false /* isDark */, mContrast);
         mDynamicOverlay = createDynamicOverlay();
     }
 
@@ -682,10 +640,10 @@
 
     private void assignDynamicPaletteToOverlay(FabricatedOverlay overlay, boolean isDark) {
         String suffix = isDark ? "dark" : "light";
-        DynamicScheme scheme = isDark ? mDynamicSchemeDark : mDynamicSchemeLight;
+        ColorScheme scheme = isDark ? mDarkColorScheme : mLightColorScheme;
         DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled).forEach(p -> {
             String resourceName = "android:color/system_" + p.first + "_" + suffix;
-            int colorValue = p.second.getArgb(scheme);
+            int colorValue = p.second.getArgb(scheme.getMaterialScheme());
             overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
                     null /* configuration */);
         });
@@ -694,7 +652,7 @@
     private void assignFixedColorsToOverlay(FabricatedOverlay overlay) {
         DynamicColors.getFixedColorsMapped(mIsFidelityEnabled).forEach(p -> {
             String resourceName = "android:color/system_" + p.first;
-            int colorValue = p.second.getArgb(mDynamicSchemeLight);
+            int colorValue = p.second.getArgb(mLightColorScheme.getMaterialScheme());
             overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
                     null /* configuration */);
         });
@@ -702,6 +660,7 @@
 
     /**
      * Checks if the color scheme in mColorScheme matches the current system palettes.
+     *
      * @param managedProfiles List of managed profiles for this user.
      */
     private boolean colorSchemeIsApplied(Set<UserHandle> managedProfiles) {
@@ -723,15 +682,18 @@
                     && res.getColor(android.R.color.system_neutral2_500, theme)
                     == mColorScheme.getNeutral2().getS500()
                     && res.getColor(android.R.color.system_outline_variant_dark, theme)
-                    == dynamicColors.outlineVariant().getArgb(mDynamicSchemeDark)
+                    == dynamicColors.outlineVariant().getArgb(mDarkColorScheme.getMaterialScheme())
                     && res.getColor(android.R.color.system_outline_variant_light, theme)
-                    == dynamicColors.outlineVariant().getArgb(mDynamicSchemeLight)
+                    == dynamicColors.outlineVariant().getArgb(mLightColorScheme.getMaterialScheme())
                     && res.getColor(android.R.color.system_primary_container_dark, theme)
-                    == dynamicColors.primaryContainer().getArgb(mDynamicSchemeDark)
+                    == dynamicColors.primaryContainer().getArgb(
+                    mDarkColorScheme.getMaterialScheme())
                     && res.getColor(android.R.color.system_primary_container_light, theme)
-                    == dynamicColors.primaryContainer().getArgb(mDynamicSchemeLight)
+                    == dynamicColors.primaryContainer().getArgb(
+                    mLightColorScheme.getMaterialScheme())
                     && res.getColor(android.R.color.system_primary_fixed, theme)
-                    == dynamicColors.primaryFixed().getArgb(mDynamicSchemeLight))) {
+                    == dynamicColors.primaryFixed().getArgb(
+                    mLightColorScheme.getMaterialScheme()))) {
                 return false;
             }
         }
diff --git a/packages/SystemUI/tests/AndroidTest.xml b/packages/SystemUI/tests/AndroidTest.xml
index 31e7bd2..cd2a62d 100644
--- a/packages/SystemUI/tests/AndroidTest.xml
+++ b/packages/SystemUI/tests/AndroidTest.xml
@@ -32,4 +32,11 @@
         <option name="test-filter-dir" value="/data/data/com.android.systemui.tests" />
         <option name="hidden-api-checks" value="false"/>
     </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys"
+            value="/data/user/0/com.android.systemui.tests/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
 </configuration>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
new file mode 100644
index 0000000..261e8c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.util.Log
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.theme.DynamicColors
+import com.google.ux.material.libmonet.hct.Hct
+import com.google.ux.material.libmonet.scheme.SchemeTonalSpot
+import java.io.File
+import java.io.FileWriter
+import java.io.StringWriter
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.transform.OutputKeys
+import javax.xml.transform.TransformerException
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+import kotlin.math.abs
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+
+private const val fileHeader =
+    """
+  ~ Copyright (C) 2022 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.
+"""
+
+private fun testName(name: String): String {
+    return "Auto generated by: atest ColorSchemeTest#$name"
+}
+
+private const val commentRoles =
+    "Colors used in Android system, from design system. These " +
+        "values can be overlaid at runtime by OverlayManager RROs."
+
+private const val commentOverlay = "This value can be overlaid at runtime by OverlayManager RROs."
+
+private fun commentWhite(paletteName: String): String {
+    return "Lightest shade of the $paletteName color used by the system. White. $commentOverlay"
+}
+
+private fun commentBlack(paletteName: String): String {
+    return "Darkest shade of the $paletteName color used by the system. Black. $commentOverlay"
+}
+
+private fun commentShade(paletteName: String, tone: Int): String {
+    return "Shade of the $paletteName system color at $tone% perceptual luminance (L* in L*a*b* " +
+        "color space). $commentOverlay"
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ColorSchemeTest : SysuiTestCase() {
+    @Test
+    fun generateThemeStyles() {
+        val document = buildDoc<Any>()
+
+        val themes = document.createElement("themes")
+        document.appendWithBreak(themes)
+
+        var hue = 0.0
+        while (hue < 360) {
+            val sourceColor = Hct.from(hue, 50.0, 50.0)
+            val sourceColorHex = sourceColor.toInt().toRGBHex()
+
+            val theme = document.createElement("theme")
+            theme.setAttribute("color", sourceColorHex)
+            themes.appendChild(theme)
+
+            for (styleValue in Style.entries) {
+                if (
+                    styleValue == Style.CLOCK ||
+                        styleValue == Style.CLOCK_VIBRANT ||
+                        styleValue == Style.CONTENT
+                ) {
+                    continue
+                }
+
+                val style = document.createElement(styleValue.name.lowercase())
+                val colorScheme = ColorScheme(sourceColor.toInt(), false, styleValue)
+
+                style.appendChild(
+                    document.createTextNode(
+                        listOf(
+                                colorScheme.accent1,
+                                colorScheme.accent2,
+                                colorScheme.accent3,
+                                colorScheme.neutral1,
+                                colorScheme.neutral2
+                            )
+                            .flatMap { a -> listOf(*a.allShades.toTypedArray()) }
+                            .joinToString(",", transform = Int::toRGBHex)
+                    )
+                )
+                theme.appendChild(style)
+            }
+
+            hue += 60
+        }
+
+        saveFile(document, "current_themes.xml")
+    }
+
+    @Test
+    fun generateDefaultValues() {
+        val document = buildDoc<Any>()
+
+        val resources = document.createElement("resources")
+        document.appendWithBreak(resources)
+
+        // shade colors
+        val colorScheme = ColorScheme(GOOGLE_BLUE, false)
+        arrayOf(
+                Triple("accent1", "Primary", colorScheme.accent1),
+                Triple("accent2", "Secondary", colorScheme.accent2),
+                Triple("accent3", "Tertiary", colorScheme.accent3),
+                Triple("neutral1", "Neutral", colorScheme.neutral1),
+                Triple("neutral2", "Secondary Neutral", colorScheme.neutral2)
+            )
+            .forEach {
+                val (paletteName, readable, palette) = it
+                palette.allShadesMapped.entries.forEachIndexed { index, (shade, colorValue) ->
+                    val comment =
+                        when (index) {
+                            0 -> commentWhite(readable)
+                            palette.allShadesMapped.entries.size - 1 -> commentBlack(readable)
+                            else -> commentShade(readable, abs(shade / 10 - 100))
+                        }
+                    resources.createColorEntry("system_${paletteName}_$shade", colorValue, comment)
+                }
+            }
+
+        resources.appendWithBreak(document.createComment(commentRoles), 2)
+
+        // dynamic colors
+        arrayOf(false, true).forEach { isDark ->
+            val suffix = if (isDark) "_dark" else "_light"
+            val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, 0.5)
+            DynamicColors.allDynamicColorsMapped(false).forEach {
+                resources.createColorEntry(
+                    "system_${it.first}$suffix",
+                    it.second.getArgb(dynamicScheme)
+                )
+            }
+        }
+
+        // fixed colors
+        val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), false, 0.5)
+        DynamicColors.getFixedColorsMapped(false).forEach {
+            resources.createColorEntry("system_${it.first}", it.second.getArgb(dynamicScheme))
+        }
+
+        saveFile(document, "role_values.xml")
+    }
+
+    // Helper Functions
+
+    private inline fun <reified T> buildDoc(): Document {
+        val functionName = T::class.simpleName + ""
+        val factory = DocumentBuilderFactory.newInstance()
+        val builder = factory.newDocumentBuilder()
+        val document = builder.newDocument()
+
+        document.appendWithBreak(document.createComment(fileHeader))
+        document.appendWithBreak(document.createComment(testName(functionName)))
+
+        return document
+    }
+
+    private fun documentToString(document: Document): String {
+        try {
+            val transformerFactory = TransformerFactory.newInstance()
+            val transformer = transformerFactory.newTransformer()
+            transformer.setOutputProperty(OutputKeys.MEDIA_TYPE, "application/xml")
+            transformer.setOutputProperty(OutputKeys.METHOD, "xml")
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes")
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")
+
+            val stringWriter = StringWriter()
+            transformer.transform(DOMSource(document), StreamResult(stringWriter))
+            return stringWriter.toString()
+        } catch (e: TransformerException) {
+            throw RuntimeException("Error transforming XML", e)
+        }
+    }
+
+    private fun saveFile(document: Document, fileName: String) {
+        val outPath = context.filesDir.path + "/" + fileName
+        Log.d("ColorSchemeXml", "Artifact $fileName created")
+        val writer = FileWriter(File(outPath))
+        writer.write(documentToString(document))
+        writer.close()
+    }
+}
+
+private fun Element.createColorEntry(name: String, value: Int, comment: String? = null) {
+    val doc = this.ownerDocument
+
+    if (comment != null) {
+        this.appendChild(doc.createComment(comment))
+    }
+
+    val color = doc.createElement("color")
+    this.appendChild(color)
+
+    color.setAttribute("name", name)
+    color.appendChild(doc.createTextNode("#" + value.toRGBHex()))
+}
+
+private fun Node.appendWithBreak(child: Node, lineBreaks: Int = 1): Node {
+    val doc = if (this is Document) this else this.ownerDocument
+    val node = doc.createTextNode("\n".repeat(lineBreaks))
+    this.appendChild(node)
+    return this.appendChild(child)
+}
+
+private fun Int.toRGBHex(): String {
+    return "%06X".format(0xFFFFFF and this)
+}
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 ab28a2f..ed7c956 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -985,7 +985,7 @@
         FabricatedOverlay neutrals = overlays[1];
         FabricatedOverlay dynamic = overlays[2];
 
-        final int colorsPerPalette = 12;
+        final int colorsPerPalette = 13;
 
         // Color resources were added for all 3 accent palettes
         verify(accents, times(colorsPerPalette * 3))