[TP] Clock color updates

Support the update of the clock color.
Clock color will change when a color option is selected.

Test: Manually tested that the clock color can be updated
Bug: 269203967
Change-Id: I5d7632e3d7ba14108ee9111f323edaa912c7bd6f
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
index 66793cd..3474858 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -16,6 +16,8 @@
  */
 package com.android.customization.picker.clock.data.repository
 
+import androidx.annotation.ColorInt
+import androidx.annotation.IntRange
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import kotlinx.coroutines.flow.Flow
@@ -33,7 +35,18 @@
 
     fun setSelectedClock(clockId: String)
 
-    fun setClockColor(color: Int?)
+    /**
+     * Set clock color to the settings.
+     *
+     * @param selectedColor selected color in the color option list.
+     * @param colorTone color tone from 0 to 100 to apply to the selected color
+     * @param seedColor the actual clock color after blending the selected color and color tone
+     */
+    fun setClockColor(
+        @ColorInt selectedColor: Int?,
+        @IntRange(from = 0, to = 100) colorTone: Int,
+        @ColorInt seedColor: Int?,
+    )
 
     suspend fun setClockSize(size: ClockSize)
 }
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
index a360b6c..e96649b 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -17,6 +17,8 @@
 package com.android.customization.picker.clock.data.repository
 
 import android.provider.Settings
+import androidx.annotation.ColorInt
+import androidx.annotation.IntRange
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import com.android.systemui.plugins.ClockMetadata
@@ -34,6 +36,7 @@
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.shareIn
+import org.json.JSONObject
 
 /** Implementation of [ClockPickerRepository], using [ClockRegistry]. */
 class ClockPickerRepositoryImpl(
@@ -50,7 +53,7 @@
                         registry
                             .getClocks()
                             .filter { "NOT_IN_USE" !in it.clockId }
-                            .map { it.toModel(null) }
+                            .map { it.toModel() }
                     trySend(allClocks)
                 }
 
@@ -72,18 +75,22 @@
                 allClocks
             }
 
-    /** The currently-selected clock. */
+    /** The currently-selected clock. This also emits the clock color information. */
     override val selectedClock: Flow<ClockMetadataModel> =
         callbackFlow {
                 fun send() {
                     val currentClockId = registry.currentClockId
-                    // It is possible that the model can be null since the full clock list is not
-                    // initiated.
+                    val metadata = registry.settings?.metadata
                     val model =
                         registry
                             .getClocks()
                             .find { clockMetadata -> clockMetadata.clockId == currentClockId }
-                            ?.toModel(registry.seedColor)
+                            ?.toModel(
+                                selectedColor = metadata?.getSelectedColor(),
+                                colorTone = metadata?.getColorTone()
+                                        ?: ClockMetadataModel.DEFAULT_COLOR_TONE,
+                                seedColor = registry.seedColor
+                            )
                     trySend(model)
                 }
 
@@ -104,11 +111,26 @@
             .mapNotNull { it }
 
     override fun setSelectedClock(clockId: String) {
-        registry.currentClockId = clockId
+        registry.mutateSetting { oldSettings ->
+            val newSettings = oldSettings.copy(clockId = clockId)
+            newSettings.metadata = oldSettings.metadata
+            newSettings
+        }
     }
 
-    override fun setClockColor(color: Int?) {
-        registry.seedColor = color
+    override fun setClockColor(
+        @ColorInt selectedColor: Int?,
+        @IntRange(from = 0, to = 100) colorTone: Int,
+        @ColorInt seedColor: Int?,
+    ) {
+        registry.mutateSetting { oldSettings ->
+            val newSettings = oldSettings.copy(seedColor = seedColor)
+            newSettings.metadata =
+                oldSettings.metadata
+                    .put(KEY_METADATA_SELECTED_COLOR, selectedColor)
+                    .put(KEY_METADATA_COLOR_TONE, colorTone)
+            newSettings
+        }
     }
 
     override val selectedClockSize: SharedFlow<ClockSize> =
@@ -131,7 +153,38 @@
         )
     }
 
-    private fun ClockMetadata.toModel(color: Int?): ClockMetadataModel {
-        return ClockMetadataModel(clockId = clockId, name = name, color = color)
+    private fun JSONObject.getSelectedColor(): Int? {
+        return if (this.isNull(KEY_METADATA_SELECTED_COLOR)) {
+            null
+        } else {
+            this.getInt(KEY_METADATA_SELECTED_COLOR)
+        }
+    }
+
+    private fun JSONObject.getColorTone(): Int {
+        return this.optInt(KEY_METADATA_COLOR_TONE, ClockMetadataModel.DEFAULT_COLOR_TONE)
+    }
+
+    /** By default, [ClockMetadataModel] has no color information unless specified. */
+    private fun ClockMetadata.toModel(
+        @ColorInt selectedColor: Int? = null,
+        @IntRange(from = 0, to = 100) colorTone: Int = 0,
+        @ColorInt seedColor: Int? = null,
+    ): ClockMetadataModel {
+        return ClockMetadataModel(
+            clockId = clockId,
+            name = name,
+            selectedColor = selectedColor,
+            colorTone = colorTone,
+            seedColor = seedColor,
+        )
+    }
+
+    companion object {
+        // The selected color in the color option list
+        private const val KEY_METADATA_SELECTED_COLOR = "metadataSelectedColor"
+
+        // The color tone to apply to the selected color
+        private const val KEY_METADATA_COLOR_TONE = "metadataColorTone"
     }
 }
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
index 677fcfa..794706f 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 
 /**
@@ -31,9 +32,15 @@
 
     val allClocks: Flow<List<ClockMetadataModel>> = repository.allClocks
 
-    val selectedClock: Flow<ClockMetadataModel> = repository.selectedClock
+    val selectedClockId: Flow<String> =
+        repository.selectedClock.map { clock -> clock.clockId }.distinctUntilChanged()
 
-    val selectedClockColor: Flow<Int?> = repository.selectedClock.map { clock -> clock.color }
+    val selectedColor: Flow<Int?> =
+        repository.selectedClock.map { clock -> clock.selectedColor }.distinctUntilChanged()
+
+    val colorTone: Flow<Int> = repository.selectedClock.map { clock -> clock.colorTone }
+
+    val seedColor: Flow<Int?> = repository.selectedClock.map { clock -> clock.seedColor }
 
     val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize
 
@@ -41,8 +48,8 @@
         repository.setSelectedClock(clockId)
     }
 
-    fun setClockColor(color: Int?) {
-        repository.setClockColor(color)
+    fun setClockColor(selectedColor: Int?, colorTone: Int, seedColor: Int?) {
+        repository.setClockColor(selectedColor, colorTone, seedColor)
     }
 
     suspend fun setClockSize(size: ClockSize) {
diff --git a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
index acbc792..72aeca6 100644
--- a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
+++ b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
@@ -17,9 +17,18 @@
 
 package com.android.customization.picker.clock.shared.model
 
+import androidx.annotation.ColorInt
+import androidx.annotation.IntRange
+
 /** Model for clock metadata. */
 data class ClockMetadataModel(
     val clockId: String,
     val name: String,
-    val color: Int?,
-)
+    @ColorInt val selectedColor: Int?,
+    @IntRange(from = 0, to = 100) val colorTone: Int,
+    @ColorInt val seedColor: Int?,
+) {
+    companion object {
+        const val DEFAULT_COLOR_TONE = 50
+    }
+}
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
index 7ea0210..62d7217 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
@@ -63,8 +63,10 @@
                     }
                 }
 
-                override fun onStartTrackingTouch(p0: SeekBar?) = Unit
-                override fun onStopTrackingTouch(p0: SeekBar?) = Unit
+                override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit
+                override fun onStopTrackingTouch(seekBar: SeekBar?) {
+                    seekBar?.progress?.let { viewModel.onSliderProgressStop(it) }
+                }
             }
         )
 
@@ -120,14 +122,12 @@
 
                 launch {
                     viewModel.sliderProgress.collect { progress ->
-                        progress?.let { slider.setProgress(progress, false) }
+                        slider.setProgress(progress, true)
                     }
                 }
 
                 launch {
-                    viewModel.isSliderEnabled.collect { isEnabled ->
-                        slider.isInvisible = !isEnabled
-                    }
+                    viewModel.isSliderEnabled.collect { isEnabled -> slider.isEnabled = isEnabled }
                 }
             }
         }
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
index 8d614e4..8fe9dca 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -55,8 +55,8 @@
     val selectedIndex: Flow<Int> =
         allClockIds
             .flatMapLatest { allClockIds ->
-                interactor.selectedClock.map { selectedClock ->
-                    val index = allClockIds.indexOf(selectedClock.clockId)
+                interactor.selectedClockId.map { selectedClockId ->
+                    val index = allClockIds.indexOf(selectedClockId)
                     if (index >= 0) {
                         index
                     } else {
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
index b36c8eb..06fd2af 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
@@ -24,12 +24,11 @@
 import com.android.customization.model.color.ColorSeedOption
 import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
 import com.android.customization.picker.clock.shared.ClockSize
+import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
 import com.android.customization.picker.color.shared.model.ColorType
 import com.android.customization.picker.color.ui.viewmodel.ColorOptionViewModel
 import com.android.wallpaper.R
-import kotlin.math.abs
-import kotlin.math.roundToInt
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
@@ -38,9 +37,10 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -57,63 +57,42 @@
         SIZE,
     }
 
-    private val helperColorHsl: FloatArray by lazy { FloatArray(3) }
+    val selectedColor: StateFlow<Int?> =
+        clockPickerInteractor.selectedColor.stateIn(viewModelScope, SharingStarted.Eagerly, null)
+
+    private val sliderProgressColorTone = MutableStateFlow(ClockMetadataModel.DEFAULT_COLOR_TONE)
+    val isSliderEnabled: Flow<Boolean> =
+        clockPickerInteractor.selectedColor.map { it != null }.distinctUntilChanged()
+    val sliderProgress: Flow<Int> = merge(clockPickerInteractor.colorTone, sliderProgressColorTone)
+
+    private val _seedColor: MutableStateFlow<Int?> = MutableStateFlow(null)
+    val seedColor: Flow<Int?> = merge(clockPickerInteractor.seedColor, _seedColor)
 
     /**
-     * Saturation level of the current selected color. Note that this can be null if the selected
-     * color is null, which means that the clock color respects the system theme color. In this
-     * case, the saturation level is no longer needed since we do not allow changing saturation
-     * level of the system theme color.
+     * The slider color tone updates are quick. Do not set color tone and the blended color to the
+     * settings until [onSliderProgressStop] is called. Update to a locally cached temporary
+     * [sliderProgressColorTone] and [_seedColor] instead.
      */
-    private val saturationLevel: Flow<Float?> =
-        clockPickerInteractor.selectedClockColor
-            .map { selectedColor ->
-                if (selectedColor == null) {
-                    null
-                } else {
-                    ColorUtils.colorToHSL(selectedColor, helperColorHsl)
-                    helperColorHsl[1]
-                }
-            }
-            .shareIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                replay = 1,
-            )
-
-    /**
-     * When the selected clock color is null, it means that the clock will respect the system theme
-     * color. And we no longer need the slider, which determines the saturation level of the clock's
-     * overridden color.
-     */
-    val isSliderEnabled: Flow<Boolean> = saturationLevel.map { it != null }
-
-    /**
-     * Slide progress from 0 to 100. Note that this can be null if the selected color is null, which
-     * means that the clock color respects the system theme color. In this case, the saturation
-     * level is no longer needed since we do not allow changing saturation level of the system theme
-     * color.
-     */
-    val sliderProgress: Flow<Int?> =
-        saturationLevel.map { saturation -> saturation?.let { (it * 100).roundToInt() } }
-
     fun onSliderProgressChanged(progress: Int) {
-        val saturation = progress / 100f
-        val selectedOption = colorOptions.value.find { option -> option.isSelected }
-        selectedOption?.let { option ->
-            ColorUtils.colorToHSL(option.color0, helperColorHsl)
-            helperColorHsl[1] = saturation
-            clockPickerInteractor.setClockColor(ColorUtils.HSLToColor(helperColorHsl))
+        sliderProgressColorTone.value = progress
+        val color = selectedColor.value
+        if (color != null) {
+            _seedColor.value = blendColorWithTone(color, progress)
         }
     }
 
+    fun onSliderProgressStop(progress: Int) {
+        val color = selectedColor.value ?: return
+        clockPickerInteractor.setClockColor(
+            selectedColor = color,
+            colorTone = progress,
+            seedColor = blendColorWithTone(color = color, colorTone = progress)
+        )
+    }
+
     @OptIn(ExperimentalCoroutinesApi::class)
     val colorOptions: StateFlow<List<ColorOptionViewModel>> =
-        combine(
-                colorPickerInteractor.colorOptions,
-                clockPickerInteractor.selectedClockColor,
-                ::Pair,
-            )
+        combine(colorPickerInteractor.colorOptions, clockPickerInteractor.selectedColor, ::Pair)
             .mapLatest { (colorOptions, selectedColor) ->
                 // Use mapLatest and delay(100) here to prevent too many selectedClockColor update
                 // events from ClockRegistry upstream, caused by sliding the saturation level bar.
@@ -138,36 +117,16 @@
                         add(defaultThemeColorOptionViewModel)
                     }
 
-                    if (selectedColor != null) {
-                        ColorUtils.colorToHSL(selectedColor, helperColorHsl)
-                    }
-
                     val selectedColorPosition =
                         if (selectedColor != null) {
-                            getSelectedColorPosition(helperColorHsl)
+                            COLOR_SET_LIST.indexOf(selectedColor)
                         } else {
                             -1
                         }
 
-                    COLOR_LIST_HSL.forEachIndexed { index, colorHSL ->
-                        val color = ColorUtils.HSLToColor(colorHSL)
+                    COLOR_SET_LIST.forEachIndexed { index, color ->
                         val isSelected = selectedColorPosition == index
-                        val colorToSet: Int by lazy {
-                            val saturation =
-                                if (selectedColor != null) {
-                                    helperColorHsl[1]
-                                } else {
-                                    colorHSL[1]
-                                }
-                            ColorUtils.HSLToColor(
-                                listOf(
-                                        colorHSL[0],
-                                        saturation,
-                                        colorHSL[2],
-                                    )
-                                    .toFloatArray()
-                            )
-                        }
+                        val colorTone = ClockMetadataModel.DEFAULT_COLOR_TONE
                         add(
                             ColorOptionViewModel(
                                 color0 = color,
@@ -184,7 +143,17 @@
                                     if (isSelected) {
                                         null
                                     } else {
-                                        { clockPickerInteractor.setClockColor(colorToSet) }
+                                        {
+                                            clockPickerInteractor.setClockColor(
+                                                selectedColor = color,
+                                                colorTone = colorTone,
+                                                seedColor =
+                                                    blendColorWithTone(
+                                                        color = color,
+                                                        colorTone = colorTone,
+                                                    ),
+                                            )
+                                        }
                                     },
                             )
                         )
@@ -214,7 +183,13 @@
                 if (selectedColor == null) {
                     null
                 } else {
-                    { clockPickerInteractor.setClockColor(null) }
+                    {
+                        clockPickerInteractor.setClockColor(
+                            selectedColor = null,
+                            colorTone = ClockMetadataModel.DEFAULT_COLOR_TONE,
+                            seedColor = null,
+                        )
+                    }
                 },
         )
     }
@@ -237,7 +212,13 @@
                 if (selectedColor == null) {
                     null
                 } else {
-                    { clockPickerInteractor.setClockColor(null) }
+                    {
+                        clockPickerInteractor.setClockColor(
+                            selectedColor = null,
+                            colorTone = ClockMetadataModel.DEFAULT_COLOR_TONE,
+                            seedColor = null,
+                        )
+                    }
                 },
         )
     }
@@ -279,19 +260,28 @@
     companion object {
         // TODO (b/241966062) The color integers here are temporary for dev purposes. We need to
         //                    finalize the overridden colors.
-        val COLOR_LIST_HSL =
+        val COLOR_SET_LIST =
             listOf(
-                arrayOf(225f, 0.65f, 0.74f).toFloatArray(),
-                arrayOf(30f, 0.65f, 0.74f).toFloatArray(),
-                arrayOf(249f, 0.65f, 0.74f).toFloatArray(),
-                arrayOf(144f, 0.65f, 0.74f).toFloatArray(),
+                -786432,
+                -3648768,
+                -9273600,
+                -16739840,
+                -9420289,
+                -6465837,
+                -5032719,
             )
 
-        const val COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
-
-        fun getSelectedColorPosition(selectedColorHsl: FloatArray): Int {
-            return COLOR_LIST_HSL.withIndex().minBy { abs(it.value[0] - selectedColorHsl[0]) }.index
+        fun blendColorWithTone(color: Int, colorTone: Int): Int {
+            val helperColorLab: DoubleArray by lazy { DoubleArray(3) }
+            ColorUtils.colorToLAB(color, helperColorLab)
+            return ColorUtils.LABToColor(
+                colorTone.toDouble(),
+                helperColorLab[1],
+                helperColorLab[2],
+            )
         }
+
+        const val COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS: Long = 100
     }
 
     class Factory(
diff --git a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index b2cb452..918cfb1 100644
--- a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -15,6 +15,8 @@
  */
 package com.android.customization.picker.clock.data.repository
 
+import androidx.annotation.ColorInt
+import androidx.annotation.IntRange
 import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository.Companion.fakeClocks
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
@@ -24,17 +26,30 @@
 import kotlinx.coroutines.flow.combine
 
 /** By default [FakeClockPickerRepository] uses [fakeClocks]. */
-open class FakeClockPickerRepository(private val clocks: List<ClockMetadataModel> = fakeClocks) :
+open class FakeClockPickerRepository(clocks: List<ClockMetadataModel> = fakeClocks) :
     ClockPickerRepository {
     override val allClocks: Flow<List<ClockMetadataModel>> = MutableStateFlow(clocks).asStateFlow()
 
     private val selectedClockId = MutableStateFlow(fakeClocks[0].clockId)
-    private val clockColor = MutableStateFlow<Int?>(null)
+    @ColorInt private val selectedColor = MutableStateFlow<Int?>(null)
+    private val colorTone = MutableStateFlow<Int>(ClockMetadataModel.DEFAULT_COLOR_TONE)
+    @ColorInt private val seedColor = MutableStateFlow<Int?>(null)
     override val selectedClock: Flow<ClockMetadataModel> =
-        combine(selectedClockId, clockColor) { selectedClockId, clockColor ->
+        combine(
+            selectedClockId,
+            selectedColor,
+            colorTone,
+            seedColor,
+        ) { selectedClockId, selectedColor, colorTone, seedColor ->
             val selectedClock = fakeClocks.find { clock -> clock.clockId == selectedClockId }
             checkNotNull(selectedClock)
-            ClockMetadataModel(selectedClock.clockId, selectedClock.name, clockColor)
+            ClockMetadataModel(
+                selectedClock.clockId,
+                selectedClock.name,
+                selectedColor,
+                colorTone,
+                seedColor,
+            )
         }
 
     private val _selectedClockSize = MutableStateFlow(ClockSize.SMALL)
@@ -44,8 +59,14 @@
         selectedClockId.value = clockId
     }
 
-    override fun setClockColor(color: Int?) {
-        clockColor.value = color
+    override fun setClockColor(
+        @ColorInt selectedColor: Int?,
+        @IntRange(from = 0, to = 100) colorTone: Int,
+        @ColorInt seedColor: Int?,
+    ) {
+        this.selectedColor.value = selectedColor
+        this.colorTone.value = colorTone
+        this.seedColor.value = seedColor
     }
 
     override suspend fun setClockSize(size: ClockSize) {
@@ -55,10 +76,12 @@
     companion object {
         val fakeClocks =
             listOf(
-                ClockMetadataModel("clock0", "clock0", null),
-                ClockMetadataModel("clock1", "clock1", null),
-                ClockMetadataModel("clock2", "clock2", null),
-                ClockMetadataModel("clock3", "clock3", null),
+                ClockMetadataModel("clock0", "clock0", null, 50, null),
+                ClockMetadataModel("clock1", "clock1", null, 50, null),
+                ClockMetadataModel("clock2", "clock2", null, 50, null),
+                ClockMetadataModel("clock3", "clock3", null, 50, null),
             )
+        val clockColor = 0
+        val clockColorTone = 87
     }
 }
diff --git a/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt b/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
index 883d68b..01bfce1 100644
--- a/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
+++ b/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
@@ -37,6 +37,14 @@
     }
 
     @Test
+    fun setSelectedClock() = runTest {
+        val observedSelectedClockId = collectLastValue(underTest.selectedClockId)
+        underTest.setSelectedClock(FakeClockPickerRepository.fakeClocks[1].clockId)
+        Truth.assertThat(observedSelectedClockId())
+            .isEqualTo(FakeClockPickerRepository.fakeClocks[1].clockId)
+    }
+
+    @Test
     fun setClockSize() = runTest {
         val observedClockSize = collectLastValue(underTest.selectedClockSize)
         underTest.setClockSize(ClockSize.DYNAMIC)
@@ -45,4 +53,19 @@
         underTest.setClockSize(ClockSize.SMALL)
         Truth.assertThat(observedClockSize()).isEqualTo(ClockSize.SMALL)
     }
+
+    @Test
+    fun setColor() = runTest {
+        val observedSelectedColor = collectLastValue(underTest.selectedColor)
+        val observedColorTone = collectLastValue(underTest.colorTone)
+        val observedSeedColor = collectLastValue(underTest.seedColor)
+        underTest.setClockColor(
+            FakeClockPickerRepository.clockColor,
+            FakeClockPickerRepository.clockColorTone,
+            FakeClockPickerRepository.clockColor,
+        )
+        Truth.assertThat(observedSelectedColor()).isEqualTo(FakeClockPickerRepository.clockColor)
+        Truth.assertThat(observedColorTone()).isEqualTo(FakeClockPickerRepository.clockColorTone)
+        Truth.assertThat(observedSeedColor()).isEqualTo(FakeClockPickerRepository.clockColor)
+    }
 }
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
index 35c3518..ea1311f 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
@@ -42,7 +42,13 @@
     private val repositoryWithSingleClock by lazy {
         FakeClockPickerRepository(
             listOf(
-                ClockMetadataModel("clock0", "clock0", null),
+                ClockMetadataModel(
+                    "clock0",
+                    "clock0",
+                    null,
+                    ClockMetadataModel.DEFAULT_COLOR_TONE,
+                    null,
+                ),
             )
         )
     }
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
index 8f61d8b..10994c7 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsTabViewModelTest.kt
@@ -11,7 +11,6 @@
 import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
 import com.android.wallpaper.testing.FakeSnapshotStore
 import com.android.wallpaper.testing.collectLastValue
-import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -66,7 +65,20 @@
     }
 
     @Test
-    fun setClockColor() = runTest {
+    fun clickOnColorSettingsTab() = runTest {
+        val tabs = collectLastValue(underTest.tabs)
+        assertThat(tabs()?.get(0)?.name).isEqualTo("Color")
+        assertThat(tabs()?.get(0)?.isSelected).isTrue()
+        assertThat(tabs()?.get(1)?.name).isEqualTo("Size")
+        assertThat(tabs()?.get(1)?.isSelected).isFalse()
+
+        tabs()?.get(1)?.onClicked?.invoke()
+        assertThat(tabs()?.get(0)?.isSelected).isFalse()
+        assertThat(tabs()?.get(1)?.isSelected).isTrue()
+    }
+
+    @Test
+    fun setSelectedColor() = runTest {
         val observedClockColorOptions = collectLastValue(underTest.colorOptions)
         // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
         advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
@@ -81,30 +93,25 @@
     }
 
     @Test
-    fun setClockSaturation() = runTest {
+    fun setColorTone() = runTest {
         val observedClockColorOptions = collectLastValue(underTest.colorOptions)
         val observedIsSliderEnabled = collectLastValue(underTest.isSliderEnabled)
         val observedSliderProgress = collectLastValue(underTest.sliderProgress)
         // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
         advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
+        assertThat(observedClockColorOptions()!![0].isSelected).isTrue()
         assertThat(observedIsSliderEnabled()).isFalse()
-        assertThat(observedSliderProgress()).isNull()
 
         observedClockColorOptions()!![1].onClick?.invoke()
         // Advance COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS since there is a delay from colorOptions
         advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
         assertThat(observedIsSliderEnabled()).isTrue()
-        val targetProgress = 99
-        underTest.onSliderProgressChanged(targetProgress)
-        advanceTimeBy(ClockSettingsViewModel.COLOR_OPTIONS_EVENT_UPDATE_DELAY_MILLIS)
-        assertThat(observedClockColorOptions()!![1].isSelected).isTrue()
-        assertThat(observedSliderProgress())
-            .isIn(
-                Range.closed(
-                    targetProgress - 1,
-                    targetProgress + 1,
-                )
-            )
+        val targetProgress1 = 99
+        underTest.onSliderProgressChanged(targetProgress1)
+        assertThat(observedSliderProgress()).isEqualTo(targetProgress1)
+        val targetProgress2 = 55
+        underTest.onSliderProgressStop(targetProgress2)
+        assertThat(observedSliderProgress()).isEqualTo(targetProgress2)
     }
 
     @Test
@@ -116,17 +123,4 @@
         underTest.setClockSize(ClockSize.SMALL)
         assertThat(observedClockSize()).isEqualTo(ClockSize.SMALL)
     }
-
-    @Test
-    fun `Click on a picker tab`() = runTest {
-        val tabs = collectLastValue(underTest.tabs)
-        assertThat(tabs()?.get(0)?.name).isEqualTo("Color")
-        assertThat(tabs()?.get(0)?.isSelected).isTrue()
-        assertThat(tabs()?.get(1)?.name).isEqualTo("Size")
-        assertThat(tabs()?.get(1)?.isSelected).isFalse()
-
-        tabs()?.get(1)?.onClicked?.invoke()
-        assertThat(tabs()?.get(0)?.isSelected).isFalse()
-        assertThat(tabs()?.get(1)?.isSelected).isTrue()
-    }
 }