Merge "BC25 Preview current wallpapers (2/2)" into main
diff --git a/res/drawable/ic_colors.xml b/res/drawable/ic_colors.xml
new file mode 100644
index 0000000..31bf4d9
--- /dev/null
+++ b/res/drawable/ic_colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  ~
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M346,820L100,574Q90,564 85,552Q80,540 80,527Q80,514 85,502Q90,490 100,480L330,251L224,145L286,80L686,480Q696,490 700.5,502Q705,514 705,527Q705,540 700.5,552Q696,564 686,574L440,820Q430,830 418,835Q406,840 393,840Q380,840 368,835Q356,830 346,820ZM393,314L179,528Q179,528 179,528Q179,528 179,528L607,528Q607,528 607,528Q607,528 607,528L393,314ZM792,840Q756,840 731,814.5Q706,789 706,752Q706,725 719.5,701Q733,677 750,654L792,600L836,654Q852,677 866,701Q880,725 880,752Q880,789 854,814.5Q828,840 792,840Z"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/floating_sheet_colors.xml b/res/layout/floating_sheet_colors.xml
new file mode 100644
index 0000000..8e6ed64
--- /dev/null
+++ b/res/layout/floating_sheet_colors.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingHorizontal="@dimen/floating_sheet_horizontal_padding"
+    android:orientation="vertical"
+    android:clipToPadding="false"
+    android:clipChildren="false">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@drawable/floating_sheet_content_background"
+        android:paddingVertical="@dimen/floating_sheet_list_vertical_padding"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/color_type_tab_subhead"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="16dp"
+            android:layout_marginHorizontal="20dp"
+            android:gravity="center"
+            android:text="@string/wallpaper_color_subheader"
+            android:textColor="@color/system_on_surface"
+            android:textSize="12sp" />
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/colors_horizontal_list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="12dp"
+            android:clipChildren="false"
+            android:clipToPadding="false" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clickable="true"
+            android:gravity="center_vertical"
+            android:layout_marginHorizontal="20dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:id="@+id/dark_mode_toggle_title"
+                style="@style/SectionTitleTextStyle"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/mode_title" />
+
+            <Switch
+                android:id="@+id/dark_mode_toggle"
+                style="@style/Switch.SettingsLib"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@null"
+                android:clickable="false"
+                android:focusable="false"
+                android:minHeight="0dp" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar
+        android:id="@+id/floating_bar_tabs"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginVertical="@dimen/floating_sheet_tab_toolbar_vertical_margin"  />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/floating_sheet_shortcut.xml b/res/layout/floating_sheet_shortcut.xml
index 0c5c1eb..44a9b6d 100644
--- a/res/layout/floating_sheet_shortcut.xml
+++ b/res/layout/floating_sheet_shortcut.xml
@@ -17,7 +17,7 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingHorizontal="@dimen/shortcut_floating_sheet_horizontal_padding"
+    android:paddingHorizontal="@dimen/floating_sheet_horizontal_padding"
     android:paddingBottom="16dp"
     android:orientation="vertical"
     android:clipToPadding="false"
@@ -26,7 +26,7 @@
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingVertical="@dimen/shortcut_floating_sheet_list_vertical_padding"
+        android:paddingVertical="@dimen/floating_sheet_list_vertical_padding"
         android:background="@drawable/floating_sheet_content_background"
         android:clipToPadding="false"
         android:clipChildren="false">
@@ -44,5 +44,5 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal"
-        android:layout_marginVertical="@dimen/shortcut_floating_sheet_tab_toolbar_vertical_margin" />
+        android:layout_marginVertical="@dimen/floating_sheet_tab_toolbar_vertical_margin" />
 </LinearLayout>
\ No newline at end of file
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 382224a..059eabb 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -23,7 +23,7 @@
     <string name="clock_description" msgid="3563839327378948">"घड़ी आइकॉन चुनें"</string>
     <string name="clock_picker_entry_content_description" msgid="8377139273468595734">"पसंद के मुताबिक घड़ी का आइकॉन बदलें"</string>
     <string name="select_clock_action_description" msgid="5025888763471843648">"स्मार्टवॉच की स्क्रीन के डिज़ाइन के लिए विकल्प <xliff:g id="ID_1">%1$s</xliff:g>"</string>
-    <string name="clock_settings_title" msgid="2050906379377120431">"घड़ी का रंग और साइज़"</string>
+    <string name="clock_settings_title" msgid="2050906379377120431">"वॉच का रंग और साइज़"</string>
     <string name="clock_color_and_size_title" msgid="7146791234905111351">"घड़ी का रंग और साइज़"</string>
     <string name="clock_color_and_size_description" msgid="6578061553012886817">"<xliff:g id="ID_1">%1$s</xliff:g>, <xliff:g id="ID_2">%2$s</xliff:g>"</string>
     <string name="clock_color" msgid="8081608867289156163">"रंग"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2d6fedd..a643c0a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -175,8 +175,8 @@
     <!-- Notification item dimensions -->
     <dimen name="notification_section_title_padding">8dp</dimen>
 
-    <!-- Shortcut floating sheet dimensions -->
-    <dimen name="shortcut_floating_sheet_list_vertical_padding">20dp</dimen>
-    <dimen name="shortcut_floating_sheet_horizontal_padding">16dp</dimen>
-    <dimen name="shortcut_floating_sheet_tab_toolbar_vertical_margin">8dp</dimen>
+    <!-- Floating sheet dimensions -->
+    <dimen name="floating_sheet_list_vertical_padding">20dp</dimen>
+    <dimen name="floating_sheet_horizontal_padding">16dp</dimen>
+    <dimen name="floating_sheet_tab_toolbar_vertical_margin">8dp</dimen>
 </resources>
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index d761598..2696b97 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -22,7 +22,6 @@
 import com.android.customization.picker.clock.ui.view.ClockViewFactory
 import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
 import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
-import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
 import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -40,15 +39,7 @@
 
     fun getClockPickerInteractor(context: Context): ClockPickerInteractor
 
-    fun getColorPickerInteractor(
-        context: Context,
-        wallpaperColorsRepository: WallpaperColorsRepository,
-    ): ColorPickerInteractor
-
-    fun getColorPickerViewModelFactory(
-        context: Context,
-        wallpaperColorsRepository: WallpaperColorsRepository,
-    ): ColorPickerViewModel.Factory
+    fun getColorPickerViewModelFactory(context: Context): ColorPickerViewModel.Factory
 
     fun getClockCarouselViewModelFactory(
         interactor: ClockPickerInteractor,
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index dfba2ce..08bb800 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -46,7 +46,6 @@
 import com.android.customization.picker.clock.ui.view.ClockViewFactoryImpl
 import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
 import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
-import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
 import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
 import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
@@ -101,9 +100,7 @@
     private var clockPickerSnapshotRestorer: ClockPickerSnapshotRestorer? = null
     private var notificationSettingsInteractor: NotificationSettingsInteractor? = null
     private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
-    private var colorPickerInteractor: ColorPickerInteractor? = null
     private var colorPickerViewModelFactory: ColorPickerViewModel.Factory? = null
-    private var colorPickerSnapshotRestorer: ColorPickerSnapshotRestorer? = null
     private var colorCustomizationManager: ColorCustomizationManager? = null
     private var darkModeSnapshotRestorer: DarkModeSnapshotRestorer? = null
     private var themedIconSnapshotRestorer: ThemedIconSnapshotRestorer? = null
@@ -124,6 +121,8 @@
     lateinit var keyguardQuickAffordanceSnapshotRestorer:
         Lazy<KeyguardQuickAffordanceSnapshotRestorer>
     @Inject lateinit var themesUserEventLogger: Lazy<ThemesUserEventLogger>
+    @Inject lateinit var colorPickerInteractor: Lazy<ColorPickerInteractor>
+    @Inject lateinit var colorPickerSnapshotRestorer: Lazy<ColorPickerSnapshotRestorer>
 
     override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
         val appContext = activity.applicationContext
@@ -131,10 +130,7 @@
         val resources = activity.resources
         return customizationSections
             ?: DefaultCustomizationSections(
-                    getColorPickerViewModelFactory(
-                        context = appContext,
-                        wallpaperColorsRepository = getWallpaperColorsRepository(),
-                    ),
+                    getColorPickerViewModelFactory(appContext),
                     getKeyguardQuickAffordancePickerViewModelFactory(appContext),
                     colorContrastSectionViewModelFactory.get(),
                     getNotificationSectionViewModelFactory(appContext),
@@ -147,7 +143,7 @@
                     clockViewFactory,
                     getThemedIconSnapshotRestorer(appContext),
                     getThemedIconInteractor(),
-                    getColorPickerInteractor(appContext, getWallpaperColorsRepository()),
+                    colorPickerInteractor.get(),
                     getUserEventLogger(),
                 )
                 .also { customizationSections = it }
@@ -188,8 +184,7 @@
             this[KEY_DARK_MODE_SNAPSHOT_RESTORER] = getDarkModeSnapshotRestorer(context)
             this[KEY_THEMED_ICON_SNAPSHOT_RESTORER] = getThemedIconSnapshotRestorer(context)
             this[KEY_APP_GRID_SNAPSHOT_RESTORER] = getGridSnapshotRestorer(context)
-            this[KEY_COLOR_PICKER_SNAPSHOT_RESTORER] =
-                getColorPickerSnapshotRestorer(context, getWallpaperColorsRepository())
+            this[KEY_COLOR_PICKER_SNAPSHOT_RESTORER] = colorPickerSnapshotRestorer.get()
             this[KEY_CLOCKS_SNAPSHOT_RESTORER] = getClockPickerSnapshotRestorer(context)
         }
     }
@@ -345,49 +340,16 @@
         return ThemedWallpaperColorResources(wallpaperColors, getSecureSettingsRepository(context))
     }
 
-    override fun getColorPickerInteractor(
-        context: Context,
-        wallpaperColorsRepository: WallpaperColorsRepository,
-    ): ColorPickerInteractor {
-        val appContext = context.applicationContext
-        return colorPickerInteractor
-            ?: ColorPickerInteractor(
-                    repository =
-                        ColorPickerRepositoryImpl(
-                            wallpaperColorsRepository,
-                            getColorCustomizationManager(appContext)
-                        ),
-                    snapshotRestorer = {
-                        getColorPickerSnapshotRestorer(appContext, wallpaperColorsRepository)
-                    }
-                )
-                .also { colorPickerInteractor = it }
-    }
-
-    override fun getColorPickerViewModelFactory(
-        context: Context,
-        wallpaperColorsRepository: WallpaperColorsRepository,
-    ): ColorPickerViewModel.Factory {
+    override fun getColorPickerViewModelFactory(context: Context): ColorPickerViewModel.Factory {
         return colorPickerViewModelFactory
             ?: ColorPickerViewModel.Factory(
                     context.applicationContext,
-                    getColorPickerInteractor(context, wallpaperColorsRepository),
+                    colorPickerInteractor.get(),
                     getUserEventLogger(),
                 )
                 .also { colorPickerViewModelFactory = it }
     }
 
-    private fun getColorPickerSnapshotRestorer(
-        context: Context,
-        wallpaperColorsRepository: WallpaperColorsRepository,
-    ): ColorPickerSnapshotRestorer {
-        return colorPickerSnapshotRestorer
-            ?: ColorPickerSnapshotRestorer(
-                    getColorPickerInteractor(context, wallpaperColorsRepository)
-                )
-                .also { colorPickerSnapshotRestorer = it }
-    }
-
     private fun getColorCustomizationManager(context: Context): ColorCustomizationManager {
         return colorCustomizationManager
             ?: ColorCustomizationManager.getInstance(context, OverlayManagerCompat(context)).also {
@@ -440,10 +402,7 @@
             ?: ClockSettingsViewModel.Factory(
                     context.applicationContext,
                     getClockPickerInteractor(context),
-                    getColorPickerInteractor(
-                        context,
-                        wallpaperColorsRepository,
-                    ),
+                    colorPickerInteractor.get(),
                     getUserEventLogger(),
                 ) { clockId ->
                     clockId?.let { clockViewFactory.getController(clockId).config.isReactiveToTone }
diff --git a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
index 942a846..f5b4ac5 100644
--- a/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/color/data/repository/ColorPickerRepositoryImpl.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.monet.Style
 import com.android.wallpaper.picker.customization.data.repository.WallpaperColorsRepository
 import com.android.wallpaper.picker.customization.shared.model.WallpaperColorsModel
+import javax.inject.Inject
+import javax.inject.Singleton
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -36,7 +38,10 @@
 
 // TODO (b/262924623): refactor to remove dependency on ColorCustomizationManager & ColorOption
 // TODO (b/268203200): Create test for ColorPickerRepositoryImpl
-class ColorPickerRepositoryImpl(
+@Singleton
+class ColorPickerRepositoryImpl
+@Inject
+constructor(
     wallpaperColorsRepository: WallpaperColorsRepository,
     private val colorManager: ColorCustomizationManager,
 ) : ColorPickerRepository {
diff --git a/src/com/android/customization/picker/color/domain/interactor/ColorPickerInteractor.kt b/src/com/android/customization/picker/color/domain/interactor/ColorPickerInteractor.kt
index e7759ce..aebc6c2 100644
--- a/src/com/android/customization/picker/color/domain/interactor/ColorPickerInteractor.kt
+++ b/src/com/android/customization/picker/color/domain/interactor/ColorPickerInteractor.kt
@@ -18,15 +18,19 @@
 
 import com.android.customization.picker.color.data.repository.ColorPickerRepository
 import com.android.customization.picker.color.shared.model.ColorOptionModel
-import javax.inject.Provider
+import javax.inject.Inject
+import javax.inject.Singleton
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.onEach
 
 /** Single entry-point for all application state and business logic related to system color. */
-class ColorPickerInteractor(
+@Singleton
+class ColorPickerInteractor
+@Inject
+constructor(
     private val repository: ColorPickerRepository,
-    private val snapshotRestorer: Provider<ColorPickerSnapshotRestorer>,
+    private val snapshotRestorer: ColorPickerSnapshotRestorer,
 ) {
     val isApplyingSystemColor = repository.isApplyingSystemColor
 
@@ -51,7 +55,7 @@
             // actually updated until the picker restarts. Wait to do so when updated color options
             // become available
             repository.select(colorOptionModel)
-            snapshotRestorer.get().storeSnapshot(colorOptionModel)
+            snapshotRestorer.storeSnapshot(colorOptionModel)
         } catch (e: Exception) {
             _selectingColorOption.value = null
         }
diff --git a/src/com/android/customization/picker/color/domain/interactor/ColorPickerSnapshotRestorer.kt b/src/com/android/customization/picker/color/domain/interactor/ColorPickerSnapshotRestorer.kt
index dce59eb..656663c 100644
--- a/src/com/android/customization/picker/color/domain/interactor/ColorPickerSnapshotRestorer.kt
+++ b/src/com/android/customization/picker/color/domain/interactor/ColorPickerSnapshotRestorer.kt
@@ -18,14 +18,20 @@
 package com.android.customization.picker.color.domain.interactor
 
 import android.util.Log
+import com.android.customization.picker.color.data.repository.ColorPickerRepository
 import com.android.customization.picker.color.shared.model.ColorOptionModel
 import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
 import com.android.wallpaper.picker.undo.domain.interactor.SnapshotStore
 import com.android.wallpaper.picker.undo.shared.model.RestorableSnapshot
+import javax.inject.Inject
+import javax.inject.Singleton
 
 /** Handles state restoration for the color picker system. */
-class ColorPickerSnapshotRestorer(
-    private val interactor: ColorPickerInteractor,
+@Singleton
+class ColorPickerSnapshotRestorer
+@Inject
+constructor(
+    private val repository: ColorPickerRepository,
 ) : SnapshotRestorer {
 
     private var snapshotStore: SnapshotStore = SnapshotStore.NOOP
@@ -39,7 +45,7 @@
         store: SnapshotStore,
     ): RestorableSnapshot {
         snapshotStore = store
-        originalOption = interactor.getCurrentColorOption()
+        originalOption = repository.getCurrentColorOption()
         return snapshot(originalOption)
     }
 
@@ -60,7 +66,7 @@
                 )
             }
 
-            interactor.select(optionToRestore)
+            repository.select(optionToRestore)
         }
     }
 
diff --git a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
index 7b5b598..05e42c9 100644
--- a/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
+++ b/src/com/android/customization/picker/color/ui/binder/ColorPickerBinder.kt
@@ -133,9 +133,10 @@
 
     interface Binding {
         fun saveInstanceState(savedState: Bundle)
+
         fun restoreInstanceState(savedState: Bundle)
     }
 
-    private val LAYOUT_MANAGER_SAVED_STATE: String = "layout_manager_state"
+    private const val LAYOUT_MANAGER_SAVED_STATE: String = "layout_manager_state"
     private var layoutManagerSavedState: Parcelable? = null
 }
diff --git a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
index a2dc526..c5eb76f 100644
--- a/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
+++ b/src/com/android/customization/picker/color/ui/fragment/ColorPickerFragment.kt
@@ -85,10 +85,7 @@
                 viewModel =
                     ViewModelProvider(
                             requireActivity(),
-                            injector.getColorPickerViewModelFactory(
-                                context = requireContext(),
-                                wallpaperColorsRepository = wallpaperColorsRepository,
-                            ),
+                            injector.getColorPickerViewModelFactory(requireContext()),
                         )
                         .get(),
                 lifecycleOwner = this,
diff --git a/src/com/android/customization/picker/common/ui/view/KeyguardQuickAffordanceItemSpacing.kt b/src/com/android/customization/picker/common/ui/view/DoubleRowListItemSpacing.kt
similarity index 89%
rename from src/com/android/customization/picker/common/ui/view/KeyguardQuickAffordanceItemSpacing.kt
rename to src/com/android/customization/picker/common/ui/view/DoubleRowListItemSpacing.kt
index c88a819..746061c 100644
--- a/src/com/android/customization/picker/common/ui/view/KeyguardQuickAffordanceItemSpacing.kt
+++ b/src/com/android/customization/picker/common/ui/view/DoubleRowListItemSpacing.kt
@@ -19,13 +19,13 @@
 import android.view.View
 import androidx.recyclerview.widget.RecyclerView
 
-/** Item spacing used by the RecyclerView. */
-class KeyguardQuickAffordanceItemSpacing() : RecyclerView.ItemDecoration() {
+/** Item spacing used by the RecyclerView with 2 rows. */
+class DoubleRowListItemSpacing(private val rowSpaceDp: Int) : RecyclerView.ItemDecoration() {
     override fun getItemOffsets(
         outRect: Rect,
         view: View,
         parent: RecyclerView,
-        state: RecyclerView.State
+        state: RecyclerView.State,
     ) {
         val itemIndex = parent.getChildAdapterPosition(view)
         val columnIndex = itemIndex / 2
@@ -62,7 +62,7 @@
         }
 
         if (itemIndex % 2 == 0) {
-            outRect.bottom = FIRST_ROW_BOTTOM_SPACING_DP.toPx(density)
+            outRect.bottom = rowSpaceDp.toPx(density)
         }
     }
 
@@ -72,7 +72,6 @@
 
     companion object {
         const val EDGE_ITEM_HORIZONTAL_SPACING_DP = 20
-        const val COMMON_HORIZONTAL_SPACING_DP = 9
-        const val FIRST_ROW_BOTTOM_SPACING_DP = 8
+        const val COMMON_HORIZONTAL_SPACING_DP = 4
     }
 }
diff --git a/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt
new file mode 100644
index 0000000..82f5291
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/binder/ColorsFloatingSheetBinder.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.wallpaper.customization.ui.binder
+
+import android.content.Context
+import android.content.res.Configuration.UI_MODE_NIGHT_MASK
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.view.View
+import android.widget.TextView
+import androidx.core.content.res.ResourcesCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.picker.color.shared.model.ColorType
+import com.android.customization.picker.color.ui.binder.ColorOptionIconBinder
+import com.android.customization.picker.color.ui.view.ColorOptionIconView
+import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
+import com.android.customization.picker.color.ui.viewmodel.ColorTypeTabViewModel
+import com.android.customization.picker.common.ui.view.DoubleRowListItemSpacing
+import com.android.themepicker.R
+import com.android.wallpaper.customization.ui.viewmodel.ColorPickerViewModel2
+import com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar
+import com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar.Tab.PRIMARY
+import com.android.wallpaper.picker.customization.ui.view.FloatingTabToolbar.Tab.SECONDARY
+import com.android.wallpaper.picker.option.ui.adapter.OptionItemAdapter
+import kotlinx.coroutines.launch
+
+object ColorsFloatingSheetBinder {
+
+    fun bind(
+        view: View,
+        viewModel: ColorPickerViewModel2,
+        lifecycleOwner: LifecycleOwner,
+    ) {
+        val subhead = view.requireViewById<TextView>(R.id.color_type_tab_subhead)
+
+        val colorsAdapter =
+            createOptionItemAdapter(view.resources.configuration.uiMode, lifecycleOwner)
+        val colorsList =
+            view.requireViewById<RecyclerView>(R.id.colors_horizontal_list).also {
+                it.initColorsList(view.context.applicationContext, colorsAdapter)
+            }
+
+        val tabs = view.requireViewById<FloatingTabToolbar>(R.id.floating_bar_tabs)
+
+        lifecycleOwner.lifecycleScope.launch {
+            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.colorTypeTabs.collect { colorTypes ->
+                        colorTypes.forEach { (colorType, tabViewModel) ->
+                            bindTab(tabs, colorType, tabViewModel)
+                        }
+                        colorTypes
+                            .filterValues { it.isSelected }
+                            .keys
+                            .firstOrNull()
+                            ?.let {
+                                when (it) {
+                                    ColorType.WALLPAPER_COLOR -> tabs.setTabSelected(PRIMARY)
+                                    ColorType.PRESET_COLOR -> tabs.setTabSelected(SECONDARY)
+                                }
+                            }
+                    }
+                }
+
+                launch { viewModel.colorTypeTabSubheader.collect { subhead.text = it } }
+
+                launch {
+                    viewModel.colorOptions.collect { colorOptions ->
+                        colorsAdapter.setItems(colorOptions) {
+                            var indexToFocus = colorOptions.indexOfFirst { it.isSelected.value }
+                            indexToFocus = if (indexToFocus < 0) 0 else indexToFocus
+                            (colorsList.layoutManager as LinearLayoutManager)
+                                .scrollToPositionWithOffset(indexToFocus, 0)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private fun createOptionItemAdapter(
+        uiMode: Int,
+        lifecycleOwner: LifecycleOwner
+    ): OptionItemAdapter<ColorOptionIconViewModel> =
+        OptionItemAdapter(
+            layoutResourceId = R.layout.color_option,
+            lifecycleOwner = lifecycleOwner,
+            bindIcon = { foregroundView: View, colorIcon: ColorOptionIconViewModel ->
+                val colorOptionIconView = foregroundView as? ColorOptionIconView
+                val night = uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
+                colorOptionIconView?.let { ColorOptionIconBinder.bind(it, colorIcon, night) }
+            }
+        )
+
+    private fun RecyclerView.initColorsList(
+        context: Context,
+        adapter: OptionItemAdapter<ColorOptionIconViewModel>,
+    ) {
+        apply {
+            this.adapter = adapter
+            layoutManager =
+                GridLayoutManager(
+                    context,
+                    2,
+                    GridLayoutManager.HORIZONTAL,
+                    false,
+                )
+            addItemDecoration(DoubleRowListItemSpacing(4))
+        }
+    }
+
+    private fun bindTab(
+        tabs: FloatingTabToolbar,
+        colorType: ColorType,
+        viewModel: ColorTypeTabViewModel
+    ) {
+        val tab =
+            when (colorType) {
+                ColorType.WALLPAPER_COLOR -> PRIMARY
+                ColorType.PRESET_COLOR -> SECONDARY
+            }
+        val iconDrawable =
+            ResourcesCompat.getDrawable(
+                tabs.resources,
+                when (colorType) {
+                    ColorType.WALLPAPER_COLOR ->
+                        com.android.wallpaper.R.drawable.ic_baseline_wallpaper_24
+                    ColorType.PRESET_COLOR -> R.drawable.ic_colors
+                },
+                null,
+            )
+        when (colorType) {
+            ColorType.WALLPAPER_COLOR -> tabs.primaryIcon.setImageDrawable(iconDrawable)
+            ColorType.PRESET_COLOR -> tabs.secondaryIcon.setImageDrawable(iconDrawable)
+        }
+        tabs.setTabText(tab, viewModel.name)
+        tabs.setOnTabClick(tab, viewModel.onClick)
+    }
+}
diff --git a/src/com/android/wallpaper/customization/ui/binder/ShortcutFloatingSheetBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ShortcutFloatingSheetBinder.kt
index 962278a..6fc1b7f 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ShortcutFloatingSheetBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ShortcutFloatingSheetBinder.kt
@@ -26,7 +26,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import com.android.customization.picker.common.ui.view.KeyguardQuickAffordanceItemSpacing
+import com.android.customization.picker.common.ui.view.DoubleRowListItemSpacing
 import com.android.themepicker.R
 import com.android.wallpaper.customization.ui.viewmodel.KeyguardQuickAffordancePickerViewModel2
 import com.android.wallpaper.picker.common.dialog.ui.viewbinder.DialogViewBinder
@@ -184,7 +184,7 @@
                     GridLayoutManager.HORIZONTAL,
                     false,
                 )
-            addItemDecoration(KeyguardQuickAffordanceItemSpacing())
+            addItemDecoration(DoubleRowListItemSpacing(12))
         }
     }
 
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
index 0fa1283..250c3d1 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
@@ -21,6 +21,7 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption
 import com.android.wallpaper.customization.ui.util.ThemePickerCustomizationOptionUtil.ThemePickerLockCustomizationOption
 import com.android.wallpaper.customization.ui.viewmodel.ThemePickerCustomizationOptionsViewModel
 import com.android.wallpaper.picker.customization.ui.binder.CustomizationOptionsBinder
@@ -62,6 +63,10 @@
             lockScreenCustomizationOptionEntries
                 .find { it.first == ThemePickerLockCustomizationOption.SHORTCUTS }
                 ?.second
+        val optionColors =
+            homeScreenCustomizationOptionEntries
+                .find { it.first == ThemePickerHomeCustomizationOption.COLORS }
+                ?.second
         viewModel as ThemePickerCustomizationOptionsViewModel
 
         lifecycleOwner.lifecycleScope.launch {
@@ -77,6 +82,12 @@
                         optionShortcut?.setOnClickListener { _ -> it?.invoke() }
                     }
                 }
+
+                launch {
+                    viewModel.onCustomizeColorsClicked.collect {
+                        optionColors?.setOnClickListener { _ -> it?.invoke() }
+                    }
+                }
             }
         }
 
@@ -85,5 +96,11 @@
             viewModel.keyguardQuickAffordancePickerViewModel2,
             lifecycleOwner,
         )
+
+        ColorsFloatingSheetBinder.bind(
+            view,
+            viewModel.colorPickerViewModel2,
+            lifecycleOwner,
+        )
     }
 }
diff --git a/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt b/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
index 83c4154..5e7f568 100644
--- a/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
+++ b/src/com/android/wallpaper/customization/ui/util/ThemePickerCustomizationOptionUtil.kt
@@ -162,17 +162,27 @@
                     )
                     .also { bottomSheetContainer.addView(it) }
             )
+            put(
+                ThemePickerHomeCustomizationOption.COLORS,
+                inflateFloatingSheet(
+                        ThemePickerHomeCustomizationOption.COLORS,
+                        bottomSheetContainer,
+                        layoutInflater,
+                    )
+                    .also { bottomSheetContainer.addView(it) }
+            )
         }
     }
 
     private fun inflateFloatingSheet(
-        option: ThemePickerLockCustomizationOption,
+        option: CustomizationOptionUtil.CustomizationOption,
         bottomSheetContainer: FrameLayout,
         layoutInflater: LayoutInflater,
     ): View =
         when (option) {
             ThemePickerLockCustomizationOption.CLOCK -> R.layout.floating_sheet_clock
             ThemePickerLockCustomizationOption.SHORTCUTS -> R.layout.floating_sheet_shortcut
+            ThemePickerHomeCustomizationOption.COLORS -> R.layout.floating_sheet_colors
             else ->
                 throw IllegalStateException(
                     "Customization option $option does not have a bottom sheet view"
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt
new file mode 100644
index 0000000..3c77423
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.wallpaper.customization.ui.viewmodel
+
+import android.content.Context
+import com.android.customization.model.color.ColorOptionImpl
+import com.android.customization.module.logging.ThemesUserEventLogger
+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.ColorOptionIconViewModel
+import com.android.customization.picker.color.ui.viewmodel.ColorTypeTabViewModel
+import com.android.themepicker.R
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.android.scopes.ViewModelScoped
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Models UI state for a color picker experience. */
+class ColorPickerViewModel2
+@AssistedInject
+constructor(
+    @ApplicationContext context: Context,
+    private val interactor: ColorPickerInteractor,
+    private val logger: ThemesUserEventLogger,
+    @Assisted private val viewModelScope: CoroutineScope,
+) {
+
+    private val selectedColorTypeTabId = MutableStateFlow<ColorType?>(null)
+
+    /** View-models for each color tab. */
+    val colorTypeTabs: Flow<Map<ColorType, ColorTypeTabViewModel>> =
+        combine(
+            interactor.colorOptions,
+            selectedColorTypeTabId,
+        ) { colorOptions, selectedColorTypeIdOrNull ->
+            colorOptions.keys
+                .mapIndexed { index, colorType ->
+                    val isSelected =
+                        (selectedColorTypeIdOrNull == null && index == 0) ||
+                            selectedColorTypeIdOrNull == colorType
+                    colorType to
+                        ColorTypeTabViewModel(
+                            name =
+                                when (colorType) {
+                                    ColorType.WALLPAPER_COLOR ->
+                                        context.resources.getString(R.string.wallpaper_color_tab)
+                                    ColorType.PRESET_COLOR ->
+                                        context.resources.getString(R.string.preset_color_tab_2)
+                                },
+                            isSelected = isSelected,
+                            onClick =
+                                if (isSelected) {
+                                    null
+                                } else {
+                                    { this.selectedColorTypeTabId.value = colorType }
+                                },
+                        )
+                }
+                .toMap()
+        }
+
+    /** View-models for each color tab subheader */
+    val colorTypeTabSubheader: Flow<String> =
+        selectedColorTypeTabId.map { selectedColorTypeIdOrNull ->
+            when (selectedColorTypeIdOrNull ?: ColorType.WALLPAPER_COLOR) {
+                ColorType.WALLPAPER_COLOR ->
+                    context.resources.getString(R.string.wallpaper_color_subheader)
+                ColorType.PRESET_COLOR ->
+                    context.resources.getString(R.string.preset_color_subheader)
+            }
+        }
+
+    /** The list of all color options mapped by their color type */
+    private val allColorOptions:
+        Flow<Map<ColorType, List<OptionItemViewModel<ColorOptionIconViewModel>>>> =
+        interactor.colorOptions.map { colorOptions ->
+            colorOptions
+                .map { colorOptionEntry ->
+                    colorOptionEntry.key to
+                        colorOptionEntry.value.map { colorOptionModel ->
+                            val colorOption: ColorOptionImpl =
+                                colorOptionModel.colorOption as ColorOptionImpl
+                            val lightThemeColors =
+                                colorOption.previewInfo.resolveColors(/* darkTheme= */ false)
+                            val darkThemeColors =
+                                colorOption.previewInfo.resolveColors(/* darkTheme= */ true)
+                            val isSelectedFlow: StateFlow<Boolean> =
+                                interactor.selectingColorOption
+                                    .map {
+                                        it?.colorOption?.isEquivalent(colorOptionModel.colorOption)
+                                            ?: colorOptionModel.isSelected
+                                    }
+                                    .stateIn(viewModelScope)
+                            OptionItemViewModel<ColorOptionIconViewModel>(
+                                key = MutableStateFlow(colorOptionModel.key) as StateFlow<String>,
+                                payload =
+                                    ColorOptionIconViewModel(
+                                        lightThemeColor0 = lightThemeColors[0],
+                                        lightThemeColor1 = lightThemeColors[1],
+                                        lightThemeColor2 = lightThemeColors[2],
+                                        lightThemeColor3 = lightThemeColors[3],
+                                        darkThemeColor0 = darkThemeColors[0],
+                                        darkThemeColor1 = darkThemeColors[1],
+                                        darkThemeColor2 = darkThemeColors[2],
+                                        darkThemeColor3 = darkThemeColors[3],
+                                    ),
+                                text =
+                                    Text.Loaded(
+                                        colorOption.getContentDescription(context).toString()
+                                    ),
+                                isTextUserVisible = false,
+                                isSelected = isSelectedFlow,
+                                onClicked =
+                                    isSelectedFlow.map { isSelected ->
+                                        if (isSelected) {
+                                            null
+                                        } else {
+                                            {
+                                                viewModelScope.launch {
+                                                    interactor.select(colorOptionModel)
+                                                    logger.logThemeColorApplied(
+                                                        colorOptionModel.colorOption
+                                                            .sourceForLogging,
+                                                        colorOptionModel.colorOption
+                                                            .styleForLogging,
+                                                        colorOptionModel.colorOption
+                                                            .seedColorForLogging,
+                                                    )
+                                                }
+                                            }
+                                        }
+                                    },
+                            )
+                        }
+                }
+                .toMap()
+        }
+
+    /** The list of all available color options for the selected Color Type. */
+    val colorOptions: Flow<List<OptionItemViewModel<ColorOptionIconViewModel>>> =
+        combine(allColorOptions, selectedColorTypeTabId) {
+            allColorOptions: Map<ColorType, List<OptionItemViewModel<ColorOptionIconViewModel>>>,
+            selectedColorTypeIdOrNull ->
+            val selectedColorTypeId = selectedColorTypeIdOrNull ?: ColorType.WALLPAPER_COLOR
+            allColorOptions[selectedColorTypeId]!!
+        }
+
+    @ViewModelScoped
+    @AssistedFactory
+    interface Factory {
+        fun create(viewModelScope: CoroutineScope): ColorPickerViewModel2
+    }
+}
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
index 6628b3d..bc0366a 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
@@ -33,6 +33,7 @@
 constructor(
     defaultCustomizationOptionsViewModelFactory: DefaultCustomizationOptionsViewModel.Factory,
     keyguardQuickAffordancePickerViewModel2Factory: KeyguardQuickAffordancePickerViewModel2.Factory,
+    colorPickerViewModel2Factory: ColorPickerViewModel2.Factory,
     @Assisted private val viewModelScope: CoroutineScope,
 ) : CustomizationOptionsViewModel {
 
@@ -41,6 +42,7 @@
 
     val keyguardQuickAffordancePickerViewModel2 =
         keyguardQuickAffordancePickerViewModel2Factory.create(viewModelScope = viewModelScope)
+    val colorPickerViewModel2 = colorPickerViewModel2Factory.create(viewModelScope = viewModelScope)
 
     override val selectedOption = defaultCustomizationOptionsViewModel.selectedOption
 
@@ -73,6 +75,19 @@
             }
         }
 
+    val onCustomizeColorsClicked: Flow<(() -> Unit)?> =
+        selectedOption.map {
+            if (it == null) {
+                {
+                    defaultCustomizationOptionsViewModel.selectOption(
+                        ThemePickerCustomizationOptionUtil.ThemePickerHomeCustomizationOption.COLORS
+                    )
+                }
+            } else {
+                null
+            }
+        }
+
     @ViewModelScoped
     @AssistedFactory
     interface Factory : CustomizationOptionsViewModelFactory {
diff --git a/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt b/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
index 3f41f0b..b700d2f 100644
--- a/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
+++ b/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
@@ -23,6 +23,8 @@
 import com.android.customization.module.ThemePickerInjector
 import com.android.customization.module.logging.ThemesUserEventLogger
 import com.android.customization.module.logging.ThemesUserEventLoggerImpl
+import com.android.customization.picker.color.data.repository.ColorPickerRepository
+import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
 import com.android.wallpaper.customization.ui.binder.ThemePickerCustomizationOptionsBinder
@@ -80,6 +82,10 @@
         impl: ThemePickerCustomizationOptionsBinder
     ): CustomizationOptionsBinder
 
+    @Binds
+    @Singleton
+    abstract fun bindColorPickerRepository(impl: ColorPickerRepositoryImpl): ColorPickerRepository
+
     companion object {
         @Provides
         @Singleton
diff --git a/tests/common/src/com/android/customization/testing/TestCustomizationInjector.kt b/tests/common/src/com/android/customization/testing/TestCustomizationInjector.kt
index caa5029..c0ae6f0 100644
--- a/tests/common/src/com/android/customization/testing/TestCustomizationInjector.kt
+++ b/tests/common/src/com/android/customization/testing/TestCustomizationInjector.kt
@@ -12,7 +12,6 @@
 import com.android.customization.picker.clock.ui.view.ClockViewFactory
 import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
 import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
-import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
 import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -58,17 +57,7 @@
         throw UnsupportedOperationException("not implemented")
     }
 
-    override fun getColorPickerInteractor(
-        context: Context,
-        wallpaperColorsRepository: WallpaperColorsRepository,
-    ): ColorPickerInteractor {
-        throw UnsupportedOperationException("not implemented")
-    }
-
-    override fun getColorPickerViewModelFactory(
-        context: Context,
-        wallpaperColorsRepository: WallpaperColorsRepository,
-    ): ColorPickerViewModel.Factory {
+    override fun getColorPickerViewModelFactory(context: Context): ColorPickerViewModel.Factory {
         throw UnsupportedOperationException("not implemented")
     }
 
diff --git a/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt b/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
index 249f6af..4a484f3 100644
--- a/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
+++ b/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
@@ -23,6 +23,8 @@
 import com.android.customization.module.CustomizationPreferences
 import com.android.customization.module.logging.TestThemesUserEventLogger
 import com.android.customization.module.logging.ThemesUserEventLogger
+import com.android.customization.picker.color.data.repository.ColorPickerRepository
+import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
 import com.android.customization.testing.TestCustomizationInjector
 import com.android.customization.testing.TestDefaultCustomizationPreferences
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
@@ -115,6 +117,10 @@
         impl: DefaultCustomizationOptionsBinder
     ): CustomizationOptionsBinder
 
+    @Binds
+    @Singleton
+    abstract fun bindColorPickerRepository(impl: ColorPickerRepositoryImpl): ColorPickerRepository
+
     companion object {
         @Provides
         @Singleton
diff --git a/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerInteractorTest.kt b/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerInteractorTest.kt
index d4f24ee..97d4a9a 100644
--- a/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerInteractorTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerInteractorTest.kt
@@ -52,11 +52,10 @@
         underTest =
             ColorPickerInteractor(
                 repository = repository,
-                snapshotRestorer = {
-                    ColorPickerSnapshotRestorer(interactor = underTest).apply {
+                snapshotRestorer =
+                    ColorPickerSnapshotRestorer(repository = repository).apply {
                         runBlocking { setUpSnapshotRestorer(store = store) }
-                    }
-                },
+                    },
             )
         repository.setOptions(4, 4, ColorType.WALLPAPER_COLOR, 0)
     }
diff --git a/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerSnapshotRestorerTest.kt b/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerSnapshotRestorerTest.kt
index 5f3e39e..b050237 100644
--- a/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerSnapshotRestorerTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/color/domain/interactor/ColorPickerSnapshotRestorerTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.customization.picker.color.data.repository.FakeColorPickerRepository
-import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
 import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
 import com.android.customization.picker.color.shared.model.ColorOptionModel
 import com.android.customization.picker.color.shared.model.ColorType
@@ -51,14 +50,7 @@
     fun setUp() {
         context = InstrumentationRegistry.getInstrumentation().targetContext
         repository = FakeColorPickerRepository(context = context)
-        underTest =
-            ColorPickerSnapshotRestorer(
-                interactor =
-                    ColorPickerInteractor(
-                        repository = repository,
-                        snapshotRestorer = { underTest },
-                    )
-            )
+        underTest = ColorPickerSnapshotRestorer(repository = repository)
         store = FakeSnapshotStore()
     }
 
diff --git a/tests/robotests/src/com/android/customization/model/picker/color/ui/viewmodel/ColorPickerViewModelTest.kt b/tests/robotests/src/com/android/customization/model/picker/color/ui/viewmodel/ColorPickerViewModelTest.kt
index 889720e..f5878a4 100644
--- a/tests/robotests/src/com/android/customization/model/picker/color/ui/viewmodel/ColorPickerViewModelTest.kt
+++ b/tests/robotests/src/com/android/customization/model/picker/color/ui/viewmodel/ColorPickerViewModelTest.kt
@@ -76,11 +76,10 @@
         interactor =
             ColorPickerInteractor(
                 repository = repository,
-                snapshotRestorer = {
-                    ColorPickerSnapshotRestorer(interactor = interactor).apply {
+                snapshotRestorer =
+                    ColorPickerSnapshotRestorer(repository = repository).apply {
                         runBlocking { setUpSnapshotRestorer(store = store) }
-                    }
-                },
+                    },
             )
 
         underTest =
diff --git a/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt b/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
index d3ae9cb..9d82fc1 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
@@ -70,14 +70,14 @@
                     }
                 },
             )
+        val colorPickerRepository = FakeColorPickerRepository(context = context)
         colorPickerInteractor =
             ColorPickerInteractor(
-                repository = FakeColorPickerRepository(context = context),
-                snapshotRestorer = {
-                    ColorPickerSnapshotRestorer(interactor = colorPickerInteractor).apply {
+                repository = colorPickerRepository,
+                snapshotRestorer =
+                    ColorPickerSnapshotRestorer(repository = colorPickerRepository).apply {
                         runBlocking { setUpSnapshotRestorer(store = FakeSnapshotStore()) }
-                    }
-                },
+                    },
             )
         underTest =
             ClockSettingsViewModel.Factory(