Merge "Implementation of the pack theme entry in the home option" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f89ff6e..8812bec 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -85,6 +85,7 @@
             android:name="com.android.wallpaper.picker.customization.ui.CustomizationPickerActivity2"
             android:label="@string/app_name"
             android:relinquishTaskIdentity="true"
+            android:screenOrientation="portrait"
             android:resizeableActivity="false"
             android:theme="@style/WallpaperTheme"
             android:configChanges="assetsPaths"
diff --git a/res/layout/customization_option_entry_themed_icons.xml b/res/layout/customization_option_entry_themed_icons.xml
index d10f9e9..cf46f7e 100644
--- a/res/layout/customization_option_entry_themed_icons.xml
+++ b/res/layout/customization_option_entry_themed_icons.xml
@@ -33,21 +33,8 @@
         android:layout_marginEnd="@dimen/customization_option_entry_text_margin_end"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@+id/option_entry_switch"
-        app:layout_constraintBottom_toTopOf="@+id/option_entry_description"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintVertical_chainStyle="packed" />
-
-    <TextView
-        android:id="@+id/option_entry_description"
-        style="@style/CustomizationOptionEntrySubtitleTextStyle"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/customization_option_entry_text_margin_end"
-        android:text="@string/beta_title"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toStartOf="@+id/option_entry_switch"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/option_entry_title" />
+        app:layout_constraintBottom_toBottomOf="parent" />
 
     <com.google.android.material.materialswitch.MaterialSwitch
         android:id="@+id/option_entry_switch"
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 16de478..a71afa3 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -146,7 +146,7 @@
     <string name="show_notifications_on_lock_screen" msgid="4157744243084646720">"Mostrar las notificaciones en la pantalla de bloqueo"</string>
     <string name="lock_screen_notifications_section_title" msgid="906673283764252102">"Notific. en pantalla de bloqueo"</string>
     <string name="lock_screen_notifications_section_description" msgid="3321937285144466046">"Administra cómo aparecen las notificaciones y qué mostrar"</string>
-    <string name="more_settings_section_title" msgid="6854797937797818510">"Más opciones de pant. de bloqueo"</string>
+    <string name="more_settings_section_title" msgid="6854797937797818510">"Más opciones de pantalla de bloqueo"</string>
     <string name="more_settings_section_description" msgid="1860115709122398325">"Privacidad, Está sonando y más"</string>
     <string name="more_colors" msgid="3191071655353004591">"Más colores"</string>
     <string name="content_description_dynamic_color_option" msgid="2191721655642529886">"Tema dinámico principal"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 367e560..691bc2b 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -146,7 +146,7 @@
     <string name="show_notifications_on_lock_screen" msgid="4157744243084646720">"نمایش اعلان‌ها در صفحه قفل"</string>
     <string name="lock_screen_notifications_section_title" msgid="906673283764252102">"اعلان‌ها در صفحه قفل"</string>
     <string name="lock_screen_notifications_section_description" msgid="3321937285144466046">"مدیریت کردن نحوه نشان داده شدن اعلان‌ها و آنچه نشان داده می‌شود"</string>
-    <string name="more_settings_section_title" msgid="6854797937797818510">"تنظیمات دیگر صفحه قفل"</string>
+    <string name="more_settings_section_title" msgid="6854797937797818510">"تنظیمات بیشتر صفحه قفل"</string>
     <string name="more_settings_section_description" msgid="1860115709122398325">"«حریم خصوصی»، «درحال پخش»، و موارد دیگر"</string>
     <string name="more_colors" msgid="3191071655353004591">"رنگ‌های بیشتر"</string>
     <string name="content_description_dynamic_color_option" msgid="2191721655642529886">"زمینه پویای اصلی"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 6564879..e05df19 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -115,7 +115,7 @@
     <string name="accessibility_clock_slider_description" msgid="8374135133110681332">"வண்ணத்தின் அடர்த்தி"</string>
     <string name="mode_title" msgid="2394873501427436055">"டார்க் தீம்"</string>
     <string name="mode_disabled_msg" msgid="9196245518435936512">"பேட்டரிச் சேமிப்பான் காரணமாகத் தற்காலிகமாக முடக்கப்பட்டுள்ளது"</string>
-    <string name="themed_icon_title" msgid="7312460430471956558">"தீம் செய்யப்பட்ட ஐகான்கள்"</string>
+    <string name="themed_icon_title" msgid="7312460430471956558">"தீம் ஐகான்கள்"</string>
     <string name="beta_title" msgid="8703819523760746458">"பீட்டா"</string>
     <string name="gird_picker_entry_content_description" msgid="9087651470212293439">"ஆப்ஸ் கட்டக் காட்சியை மாற்றும்"</string>
     <string name="wallpaper_color_tab" msgid="1447926591721403840">"வால்பேப்பர் நிறங்கள்"</string>
diff --git a/src/com/android/customization/model/color/ColorProvider.kt b/src/com/android/customization/model/color/ColorProvider.kt
index 5c2b891..173a7d1 100644
--- a/src/com/android/customization/model/color/ColorProvider.kt
+++ b/src/com/android/customization/model/color/ColorProvider.kt
@@ -307,23 +307,35 @@
     }
 
     /**
-     * Returns the preview of a preset ColorScheme based on this order: top left, top right, bottom
-     * left, bottom right
+     * Returns the light theme contrast-adjusted preview of a preset ColorScheme, based on this
+     * order: top left, top right, bottom left, bottom right
      */
-    private fun getFixedPresetColorPreview(colorScheme: ColorScheme): IntArray {
+    private fun getDarkPresetColorPreview(colorScheme: ColorScheme): IntArray {
         val colors =
             when (colorScheme.style) {
                 Style.FRUIT_SALAD -> intArrayOf(colorScheme.accent3.s100, colorScheme.accent1.s200)
-                Style.TONAL_SPOT -> intArrayOf(colorScheme.accentColor, colorScheme.accentColor)
-                Style.RAINBOW -> intArrayOf(colorScheme.accent1.s200, colorScheme.accent1.s200)
-                else -> intArrayOf(colorScheme.accent1.s100, colorScheme.accent1.s100)
+                else -> intArrayOf(colorScheme.accent1.s200, colorScheme.accent1.s200)
             }
         return intArrayOf(colors[0], colors[1], colors[0], colors[1])
     }
 
     /**
-     * Returns the light theme contrast-adjusted preview of a preset ColorScheme, specifically for
-     * Revamped UI, based on this order: top left, top right, bottom left, bottom right
+     * Returns the preview of a preset ColorScheme based on this order: top left, top right, bottom
+     * left, bottom right
+     */
+    private fun getFixedPresetColorPreview(colorScheme: ColorScheme, seed: Int): IntArray {
+        val colors =
+            when (colorScheme.style) {
+                Style.FRUIT_SALAD -> intArrayOf(colorScheme.accent3.s100, colorScheme.accent1.s200)
+                Style.RAINBOW -> intArrayOf(colorScheme.accent1.s200, colorScheme.accent1.s200)
+                else -> intArrayOf(seed, seed)
+            }
+        return intArrayOf(colors[0], colors[1], colors[0], colors[1])
+    }
+
+    /**
+     * Returns the light theme contrast-adjusted preview of a preset ColorScheme, based on this
+     * order: top left, top right, bottom left, bottom right
      */
     private fun getLightPresetColorPreview(colorScheme: ColorScheme): IntArray {
         val colors =
@@ -333,13 +345,11 @@
                         colorScheme.accent3.getAtTone(450f),
                         colorScheme.accent1.getAtTone(550f),
                     )
-                Style.TONAL_SPOT -> intArrayOf(colorScheme.accentColor, colorScheme.accentColor)
-                Style.RAINBOW ->
+                else ->
                     intArrayOf(
                         colorScheme.accent1.getAtTone(450f),
                         colorScheme.accent1.getAtTone(450f),
                     )
-                else -> intArrayOf(colorScheme.accent1.s100, colorScheme.accent1.s100)
             }
         return intArrayOf(colors[0], colors[1], colors[0], colors[1])
     }
@@ -447,10 +457,15 @@
                     lightColors = getLightMonochromePreview(lightColorScheme)
                 }
                 else -> {
-                    darkColors = getFixedPresetColorPreview(darkColorScheme)
+                    darkColors =
+                        if (isNewPickerUi) {
+                            getFixedPresetColorPreview(darkColorScheme, colorFromStub)
+                        } else {
+                            getDarkPresetColorPreview(darkColorScheme)
+                        }
                     lightColors =
                         if (isNewPickerUi) {
-                            getFixedPresetColorPreview(lightColorScheme)
+                            getFixedPresetColorPreview(lightColorScheme, colorFromStub)
                         } else {
                             getLightPresetColorPreview(lightColorScheme)
                         }
diff --git a/src/com/android/customization/model/grid/DefaultShapeGridManager.kt b/src/com/android/customization/model/grid/DefaultShapeGridManager.kt
index f8b0c3a..421333b 100644
--- a/src/com/android/customization/model/grid/DefaultShapeGridManager.kt
+++ b/src/com/android/customization/model/grid/DefaultShapeGridManager.kt
@@ -181,6 +181,7 @@
         const val SHAPE_OPTIONS: String = "shape_options"
         const val GRID_OPTIONS: String = "list_options"
         const val SHAPE_GRID: String = "default_grid"
+        const val SET_SHAPE: String = "set_shape"
         const val COL_SHAPE_KEY: String = "shape_key"
         const val COL_GRID_KEY: String = "name"
         const val COL_GRID_NAME: String = "grid_name"
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 9690880..fa71fc2 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -20,7 +20,7 @@
 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.clocks.ClockFontAxisSetting
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.systemui.plugins.clocks.ClockId
 import kotlinx.coroutines.flow.Flow
 
@@ -52,7 +52,7 @@
 
     suspend fun setClockSize(size: ClockSize)
 
-    suspend fun setClockFontAxes(axisSettings: List<ClockFontAxisSetting>)
+    suspend fun setClockFontAxes(axisSettings: ClockAxisStyle)
 
     fun isReactiveToTone(clockId: ClockId): Boolean?
 }
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 db38342..9342ab5 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -22,8 +22,8 @@
 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.clocks.ClockAxisStyle
 import com.android.systemui.plugins.clocks.ClockFontAxis
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -191,7 +191,7 @@
         )
     }
 
-    override suspend fun setClockFontAxes(axisSettings: List<ClockFontAxisSetting>) {
+    override suspend fun setClockFontAxes(axisSettings: ClockAxisStyle) {
         registry.mutateSetting { oldSettings ->
             val newSettings = oldSettings.copy(axes = axisSettings)
             newSettings.metadata = oldSettings.metadata
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 27b8b26..acff6e8 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -23,7 +23,7 @@
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import com.android.customization.picker.clock.shared.model.ClockSnapshotModel
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.wallpaper.picker.customization.data.repository.CustomizationRuntimeValuesRepository
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -60,8 +60,10 @@
 
     val seedColor: Flow<Int?> = repository.selectedClock.map { clock -> clock.seedColor }
 
-    val axisSettings: Flow<List<ClockFontAxisSetting>?> =
-        repository.selectedClock.map { clock -> clock.fontAxes.map { it.toSetting() } }
+    val axisSettings: Flow<ClockAxisStyle?> =
+        repository.selectedClock.map { clock ->
+            if (clock.fontAxes.isEmpty()) null else ClockAxisStyle(clock.fontAxes)
+        }
 
     val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize
 
@@ -92,7 +94,7 @@
         setClockOption(ClockSnapshotModel(clockSize = size))
     }
 
-    suspend fun setClockFontAxes(axisSettings: List<ClockFontAxisSetting>) {
+    suspend fun setClockFontAxes(axisSettings: ClockAxisStyle) {
         setClockOption(ClockSnapshotModel(axisSettings = axisSettings))
     }
 
@@ -102,7 +104,7 @@
         selectedColorId: String?,
         @IntRange(from = 0, to = 100) colorToneProgress: Int?,
         @ColorInt seedColor: Int?,
-        axisSettings: List<ClockFontAxisSetting>,
+        axisSettings: ClockAxisStyle,
     ) {
         setClockOption(
             ClockSnapshotModel(
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerSnapshotRestorer.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerSnapshotRestorer.kt
index 2a74276..41e82eb 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerSnapshotRestorer.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerSnapshotRestorer.kt
@@ -21,7 +21,7 @@
 import android.util.Log
 import com.android.customization.picker.clock.data.repository.ClockPickerRepository
 import com.android.customization.picker.clock.shared.model.ClockSnapshotModel
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 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
@@ -60,7 +60,7 @@
                 seedColor = repository.selectedClock.map { clock -> clock.seedColor }.firstOrNull(),
                 axisSettings =
                     repository.selectedClock
-                        .map { clock -> clock.fontAxes.map { it.toSetting() } }
+                        .map { clock -> ClockAxisStyle(clock.fontAxes) }
                         .firstOrNull(),
             )
         return snapshot(originalOption)
@@ -76,8 +76,8 @@
                         snapshot.args[KEY_COLOR_TONE_PROGRESS] ||
                     optionToRestore.seedColor?.toString() != snapshot.args[KEY_SEED_COLOR] ||
                     optionToRestore.selectedColorId != snapshot.args[KEY_COLOR_ID] ||
-                    (optionToRestore.axisSettings ?: listOf()) !=
-                        ClockFontAxisSetting.fromJson(JSONArray(snapshot.args[KEY_FONT_AXES]))
+                    (optionToRestore.axisSettings ?: ClockAxisStyle()) !=
+                        ClockAxisStyle.fromJson(JSONArray(snapshot.args[KEY_FONT_AXES]))
             ) {
                 Log.wtf(
                     TAG,
@@ -118,7 +118,7 @@
                     }
                     clockSnapshotModel.seedColor?.let { put(KEY_SEED_COLOR, it.toString()) }
                     clockSnapshotModel.axisSettings?.let {
-                        put(KEY_FONT_AXES, ClockFontAxisSetting.toJson(it).toString())
+                        put(KEY_FONT_AXES, ClockAxisStyle.toJson(it).toString())
                     }
                 }
             }
diff --git a/src/com/android/customization/picker/clock/shared/model/ClockSnapshotModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockSnapshotModel.kt
index 6817ec1..3847ced 100644
--- a/src/com/android/customization/picker/clock/shared/model/ClockSnapshotModel.kt
+++ b/src/com/android/customization/picker/clock/shared/model/ClockSnapshotModel.kt
@@ -20,7 +20,7 @@
 import androidx.annotation.ColorInt
 import androidx.annotation.IntRange
 import com.android.customization.picker.clock.shared.ClockSize
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 
 /** Models application state for a clock option in a picker experience. */
 data class ClockSnapshotModel(
@@ -29,5 +29,5 @@
     val selectedColorId: String? = null,
     @IntRange(from = 0, to = 100) val colorToneProgress: Int? = null,
     @ColorInt val seedColor: Int? = null,
-    val axisSettings: List<ClockFontAxisSetting>? = null,
+    val axisSettings: ClockAxisStyle? = null,
 )
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
index 7fcfd9c..4f96f69 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
@@ -56,7 +56,7 @@
                 scrollBackwardCallback = {
                     // Callback code for scrolling backward
                     carouselView.transitionToPrevious()
-                }
+                },
             )
         lifecycleOwner.lifecycleScope.launch {
             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -71,7 +71,13 @@
                             },
                             isTwoPaneAndSmallWidth = isTwoPaneAndSmallWidth,
                         )
-                        carouselView.accessibilityDelegate = carouselAccessibilityDelegate
+                        // Only show accessibility action when there is >1 clock to choose from
+                        carouselView.accessibilityDelegate =
+                            if (allClocks.size > 1) {
+                                carouselAccessibilityDelegate
+                            } else {
+                                null
+                            }
                         screenPreviewClickView.setOnSideClickedListener { isStart ->
                             if (isStart) carouselView.scrollToPrevious()
                             else carouselView.scrollToNext()
diff --git a/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
index 1634cbf..eb02974 100644
--- a/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ThemePickerClockViewFactory.kt
@@ -25,8 +25,8 @@
 import androidx.annotation.ColorInt
 import androidx.lifecycle.LifecycleOwner
 import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.shared.Flags
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -136,7 +136,7 @@
         }
     }
 
-    override fun updateFontAxes(clockId: String, settings: List<ClockFontAxisSetting>) {
+    override fun updateFontAxes(clockId: String, settings: ClockAxisStyle) {
         getController(clockId)?.let { it.events.onFontAxesChanged(settings) }
     }
 
@@ -193,7 +193,7 @@
     private fun initClockController(clockId: String): ClockController? {
         val isWallpaperDark = isLockscreenWallpaperDark()
         return registry.createExampleClock(clockId)?.also { controller ->
-            controller.initialize(isWallpaperDark, 0f, 0f, {})
+            controller.initialize(isWallpaperDark, 0f, 0f, null)
 
             // Initialize large clock
             controller.largeClock.events.onFontSettingChanged(
diff --git a/src/com/android/customization/picker/grid/data/repository/ShapeRepository.kt b/src/com/android/customization/picker/grid/data/repository/ShapeRepository.kt
new file mode 100644
index 0000000..bcadcc4
--- /dev/null
+++ b/src/com/android/customization/picker/grid/data/repository/ShapeRepository.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 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.customization.picker.grid.data.repository
+
+import android.content.ContentValues
+import android.content.Context
+import com.android.customization.model.grid.DefaultShapeGridManager.Companion.COL_SHAPE_KEY
+import com.android.customization.model.grid.DefaultShapeGridManager.Companion.SET_SHAPE
+import com.android.customization.model.grid.ShapeGridManager
+import com.android.customization.model.grid.ShapeOptionModel
+import com.android.wallpaper.R
+import com.android.wallpaper.picker.di.modules.BackgroundDispatcher
+import com.android.wallpaper.util.PreviewUtils
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@Singleton
+class ShapeRepository
+@Inject
+constructor(
+    @ApplicationContext private val context: Context,
+    private val shapeGridManager: ShapeGridManager,
+    @BackgroundDispatcher private val bgScope: CoroutineScope,
+    @BackgroundDispatcher private val bgDispatcher: CoroutineDispatcher,
+) {
+    private val authorityMetadataKey: String =
+        context.getString(R.string.grid_control_metadata_name)
+    private val previewUtils: PreviewUtils = PreviewUtils(context, authorityMetadataKey)
+
+    private val _shapeOptions = MutableStateFlow<List<ShapeOptionModel>?>(null)
+
+    init {
+        bgScope.launch { _shapeOptions.value = shapeGridManager.getShapeOptions() }
+    }
+
+    val shapeOptions: StateFlow<List<ShapeOptionModel>?> = _shapeOptions.asStateFlow()
+
+    val selectedShapeOption: Flow<ShapeOptionModel?> =
+        shapeOptions.map { shapeOptions -> shapeOptions?.firstOrNull { it.isCurrent } }
+
+    suspend fun applyShape(shapeKey: String) =
+        withContext(bgDispatcher) {
+            context.contentResolver.update(
+                previewUtils.getUri(SET_SHAPE),
+                ContentValues().apply { put(COL_SHAPE_KEY, shapeKey) },
+                null,
+                null,
+            )
+            // After applying, we should query and update shape and grid options again.
+            _shapeOptions.value = shapeGridManager.getShapeOptions()
+        }
+}
diff --git a/src/com/android/customization/picker/grid/domain/interactor/AppIconInteractor.kt b/src/com/android/customization/picker/grid/domain/interactor/AppIconInteractor.kt
new file mode 100644
index 0000000..07ef52a
--- /dev/null
+++ b/src/com/android/customization/picker/grid/domain/interactor/AppIconInteractor.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2025 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.customization.picker.grid.domain.interactor
+
+import com.android.customization.picker.grid.data.repository.ShapeRepository
+import com.android.customization.picker.themedicon.data.repository.ThemedIconRepository
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.flow.Flow
+
+@Singleton
+class AppIconInteractor
+@Inject
+constructor(
+    private val shapeRepository: ShapeRepository,
+    private val themedIconRepository: ThemedIconRepository,
+) {
+
+    val shapeOptions = shapeRepository.shapeOptions
+
+    val selectedShapeOption = shapeRepository.selectedShapeOption
+
+    val isThemedIconAvailable: Flow<Boolean> = themedIconRepository.isAvailable
+
+    val isThemedIconEnabled: Flow<Boolean> = themedIconRepository.isActivated
+
+    suspend fun applyThemedIconEnabled(enabled: Boolean) =
+        themedIconRepository.setThemedIconEnabled(enabled)
+
+    suspend fun applyShape(shapeKey: String) = shapeRepository.applyShape(shapeKey)
+}
diff --git a/src/com/android/customization/picker/mode/data/repository/DarkModeRepository.kt b/src/com/android/customization/picker/mode/data/repository/DarkModeRepository.kt
index 28f5017..69b489a 100644
--- a/src/com/android/customization/picker/mode/data/repository/DarkModeRepository.kt
+++ b/src/com/android/customization/picker/mode/data/repository/DarkModeRepository.kt
@@ -18,11 +18,9 @@
 
 import com.android.customization.picker.mode.shared.util.DarkModeUtil
 import com.android.wallpaper.system.PowerManagerWrapper
-import com.android.wallpaper.system.UiModeManagerWrapper
 import javax.inject.Inject
 import javax.inject.Singleton
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
@@ -31,7 +29,7 @@
 @Inject
 constructor(
     darkModeUtil: DarkModeUtil,
-    private val uiModeManager: UiModeManagerWrapper,
+    private val darkModeStateRepository: DarkModeStateRepository,
     private val powerManager: PowerManagerWrapper,
 ) {
     private val isPowerSaveMode = MutableStateFlow(powerManager.getIsPowerSaveMode() ?: false)
@@ -43,17 +41,11 @@
             isPowerSaveMode.map { !it }
         } else flowOf(false)
 
-    private val _isDarkMode = MutableStateFlow(uiModeManager.getIsNightModeActivated())
-    val isDarkMode = _isDarkMode.asStateFlow()
+    val isDarkMode = darkModeStateRepository.isDarkMode
 
-    fun setDarkModeActivated(isActive: Boolean) {
-        uiModeManager.setNightModeActivated(isActive)
-        refreshIsDarkModeActivated()
-    }
+    fun setIsDarkMode(isActive: Boolean) = darkModeStateRepository.setIsDarkMode(isActive)
 
-    fun refreshIsDarkModeActivated() {
-        _isDarkMode.value = uiModeManager.getIsNightModeActivated()
-    }
+    fun refreshIsDarkMode() = darkModeStateRepository.refreshIsDarkMode()
 
     fun refreshIsPowerSaveModeActivated() {
         powerManager.getIsPowerSaveMode()?.let { isPowerSaveMode.value = it }
diff --git a/src/com/android/customization/picker/mode/domain/interactor/DarkModeInteractor.kt b/src/com/android/customization/picker/mode/domain/interactor/DarkModeInteractor.kt
index 1b74e33..e805222 100644
--- a/src/com/android/customization/picker/mode/domain/interactor/DarkModeInteractor.kt
+++ b/src/com/android/customization/picker/mode/domain/interactor/DarkModeInteractor.kt
@@ -25,5 +25,5 @@
     val isEnabled = repository.isEnabled
     val isDarkMode = repository.isDarkMode
 
-    fun setDarkModeActivated(isActive: Boolean) = repository.setDarkModeActivated(isActive)
+    fun setIsDarkMode(isActive: Boolean) = repository.setIsDarkMode(isActive)
 }
diff --git a/src/com/android/customization/picker/mode/shared/util/DarkModeLifecycleUtil.kt b/src/com/android/customization/picker/mode/shared/util/DarkModeLifecycleUtil.kt
index 749ac2e..f7bbfd2 100644
--- a/src/com/android/customization/picker/mode/shared/util/DarkModeLifecycleUtil.kt
+++ b/src/com/android/customization/picker/mode/shared/util/DarkModeLifecycleUtil.kt
@@ -59,7 +59,6 @@
             @Synchronized
             override fun onStart(owner: LifecycleOwner) {
                 super.onStart(owner)
-                darkModeRepository.refreshIsDarkModeActivated()
                 darkModeRepository.refreshIsPowerSaveModeActivated()
                 if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
                     activityContext.registerReceiver(
diff --git a/src/com/android/customization/picker/mode/ui/binder/DarkModeBinder.kt b/src/com/android/customization/picker/mode/ui/binder/DarkModeBinder.kt
index 9e9db7a..3e46288 100644
--- a/src/com/android/customization/picker/mode/ui/binder/DarkModeBinder.kt
+++ b/src/com/android/customization/picker/mode/ui/binder/DarkModeBinder.kt
@@ -54,7 +54,11 @@
                 }
                 launch {
                     viewModel.toggleDarkMode.collect {
-                        darkModeToggle.setOnCheckedChangeListener { _, _ -> it.invoke() }
+                        // Use onClickListener instead of onCheckedChangeListener to avoid the
+                        // potential cycle of: system value changes->the toggle isChecked value is
+                        // updated->the onCheckedChangeListener is called->the overriding value is
+                        // set. The overriding value should not be set by the system, only by user.
+                        darkModeToggle.setOnClickListener { _ -> it.invoke() }
                     }
                 }
             }
diff --git a/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModel.kt b/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModel.kt
index f51d966..aacf001 100644
--- a/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModel.kt
+++ b/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModel.kt
@@ -44,14 +44,17 @@
     val toggleDarkMode =
         combine(overridingIsDarkMode, isDarkMode) { override, current ->
             // Only set override if its value is different from current, else set to null
-            { _overridingIsDarkMode.value = if (override == null) !current else null }
+            {
+                _overridingIsDarkMode.value =
+                    if (override == null || override == current) !current else null
+            }
         }
 
     val onApply: Flow<(suspend () -> Unit)?> =
         combine(overridingIsDarkMode, isDarkMode, isEnabled) { override, current, isEnabled ->
             if (override != null && override != current && isEnabled) {
                 {
-                    interactor.setDarkModeActivated(override)
+                    interactor.setIsDarkMode(override)
                     logger.logDarkThemeApplied(override)
                 }
             } else null
diff --git a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
index c3e1371..5736e0e 100644
--- a/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
+++ b/src/com/android/wallpaper/customization/ui/binder/ThemePickerCustomizationOptionBinder.kt
@@ -38,7 +38,7 @@
 import com.android.customization.picker.color.ui.view.ColorOptionIconView2
 import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
 import com.android.customization.picker.settings.ui.binder.ColorContrastSectionViewBinder2
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.systemui.plugins.clocks.ClockPreviewConfig
 import com.android.systemui.shared.Flags
 import com.android.themepicker.R
@@ -493,8 +493,7 @@
                         .collect { quadruple ->
                             val (color, clock, axisMap, _) = quadruple
                             clockViewFactory.updateColor(clock.clockId, color)
-                            val axisList = axisMap.map { ClockFontAxisSetting(it.key, it.value) }
-                            clockViewFactory.updateFontAxes(clock.clockId, axisList)
+                            clockViewFactory.updateFontAxes(clock.clockId, ClockAxisStyle(axisMap))
                         }
                 }
 
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/AppIconPickerViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/AppIconPickerViewModel.kt
new file mode 100644
index 0000000..989879e
--- /dev/null
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/AppIconPickerViewModel.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2025 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 com.android.customization.model.grid.ShapeOptionModel
+import com.android.customization.picker.grid.domain.interactor.AppIconInteractor
+import com.android.customization.picker.grid.ui.viewmodel.ShapeIconViewModel
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel2
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import dagger.hilt.android.scopes.ViewModelScoped
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+
+class AppIconPickerViewModel
+@AssistedInject
+constructor(interactor: AppIconInteractor, @Assisted private val viewModelScope: CoroutineScope) {
+    //// Shape
+
+    // The currently-set system shape option
+    val selectedShapeKey =
+        interactor.selectedShapeOption
+            .filterNotNull()
+            .map { it.key }
+            .shareIn(scope = viewModelScope, started = SharingStarted.Lazily, replay = 1)
+    private val overridingShapeKey = MutableStateFlow<String?>(null)
+    // If the overriding key is null, use the currently-set system shape option
+    val previewingShapeKey =
+        combine(overridingShapeKey, selectedShapeKey) { overridingShapeOptionKey, selectedShapeKey
+            ->
+            overridingShapeOptionKey ?: selectedShapeKey
+        }
+
+    val shapeOptions: Flow<List<OptionItemViewModel2<ShapeIconViewModel>>> =
+        interactor.shapeOptions
+            .filterNotNull()
+            .map { shapeOptions -> shapeOptions.map { toShapeOptionItemViewModel(it) } }
+            .shareIn(scope = viewModelScope, started = SharingStarted.Lazily, replay = 1)
+
+    //// Themed icons enabled
+    val isThemedIconAvailable =
+        interactor.isThemedIconAvailable.shareIn(
+            scope = viewModelScope,
+            started = SharingStarted.Lazily,
+            replay = 1,
+        )
+
+    private val overridingIsThemedIconEnabled = MutableStateFlow<Boolean?>(null)
+    val isThemedIconEnabled =
+        interactor.isThemedIconEnabled.shareIn(
+            scope = viewModelScope,
+            started = SharingStarted.Lazily,
+            replay = 1,
+        )
+    val previewingIsThemeIconEnabled =
+        combine(overridingIsThemedIconEnabled, isThemedIconEnabled) {
+            overridingIsThemeIconEnabled,
+            isThemeIconEnabled ->
+            overridingIsThemeIconEnabled ?: isThemeIconEnabled
+        }
+    val toggleThemedIcon: Flow<suspend () -> Unit> =
+        previewingIsThemeIconEnabled.map {
+            {
+                val newValue = !it
+                overridingIsThemedIconEnabled.value = newValue
+            }
+        }
+
+    val onApply: Flow<(suspend () -> Unit)?> =
+        combine(
+            overridingShapeKey,
+            selectedShapeKey,
+            overridingIsThemedIconEnabled,
+            isThemedIconEnabled,
+        ) { overridingShapeKey, selectedShapeKey, overridingIsThemeIconEnabled, isThemeIconEnabled
+            ->
+            if (
+                (overridingShapeKey != null && overridingShapeKey != selectedShapeKey) ||
+                    (overridingIsThemeIconEnabled != null &&
+                        overridingIsThemeIconEnabled != isThemeIconEnabled)
+            ) {
+                {
+                    overridingShapeKey?.let { interactor.applyShape(it) }
+                    overridingIsThemeIconEnabled?.let { interactor.applyThemedIconEnabled(it) }
+                }
+            } else {
+                null
+            }
+        }
+
+    fun resetPreview() {
+        overridingShapeKey.value = null
+        overridingIsThemedIconEnabled.value = null
+    }
+
+    private fun toShapeOptionItemViewModel(
+        option: ShapeOptionModel
+    ): OptionItemViewModel2<ShapeIconViewModel> {
+        val isSelected =
+            previewingShapeKey
+                .map { it == option.key }
+                .stateIn(
+                    scope = viewModelScope,
+                    started = SharingStarted.Lazily,
+                    initialValue = false,
+                )
+
+        return OptionItemViewModel2(
+            key = MutableStateFlow(option.key),
+            payload = ShapeIconViewModel(option.key, option.path),
+            text = Text.Loaded(option.title),
+            isSelected = isSelected,
+            onClicked =
+                isSelected.map {
+                    if (!it) {
+                        { overridingShapeKey.value = option.key }
+                    } else {
+                        null
+                    }
+                },
+        )
+    }
+
+    @ViewModelScoped
+    @AssistedFactory
+    interface Factory {
+        fun create(viewModelScope: CoroutineScope): AppIconPickerViewModel
+    }
+}
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
index 7fe81ed..dca361f 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ClockPickerViewModel.kt
@@ -28,7 +28,7 @@
 import com.android.customization.picker.clock.ui.viewmodel.ClockColorViewModel
 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor2
 import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.themepicker.R
 import com.android.wallpaper.picker.common.icon.ui.viewmodel.Icon
 import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
@@ -464,7 +464,7 @@
                                     colorTone = it.getColorTone(previewProgress),
                                 )
                             },
-                        axisSettings = axisMap.map { ClockFontAxisSetting(it.key, it.value) },
+                        axisSettings = ClockAxisStyle(axisMap),
                     )
                 }
             } else {
diff --git a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
index 99746e0..95f55c0 100644
--- a/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
+++ b/src/com/android/wallpaper/customization/ui/viewmodel/ThemePickerCustomizationOptionsViewModel.kt
@@ -47,6 +47,7 @@
     colorPickerViewModel2Factory: ColorPickerViewModel2.Factory,
     clockPickerViewModelFactory: ClockPickerViewModel.Factory,
     shapeGridPickerViewModelFactory: ShapeGridPickerViewModel.Factory,
+    appIconPickerViewModelFactory: AppIconPickerViewModel.Factory,
     val colorContrastSectionViewModel: ColorContrastSectionViewModel2,
     val darkModeViewModel: DarkModeViewModel,
     val themedIconViewModel: ThemedIconViewModel,
@@ -65,6 +66,8 @@
     val colorPickerViewModel2 = colorPickerViewModel2Factory.create(viewModelScope = viewModelScope)
     val shapeGridPickerViewModel =
         shapeGridPickerViewModelFactory.create(viewModelScope = viewModelScope)
+    val appIconPickerViewModel =
+        appIconPickerViewModelFactory.create(viewModelScope = viewModelScope)
 
     private var onApplyJob: Job? = null
 
diff --git a/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt b/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
index 1f2a0f3..cd63a75 100644
--- a/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
+++ b/src/com/android/wallpaper/picker/common/preview/ui/binder/ThemePickerWorkspaceCallbackBinder.kt
@@ -228,6 +228,15 @@
                             workspaceCallback.sendMessage(MESSAGE_ID_UPDATE_COLOR, Bundle.EMPTY)
                         }
                     }
+
+                    launch {
+                        viewModel.appIconPickerViewModel.previewingIsThemeIconEnabled.collect {
+                            workspaceCallback.sendMessage(
+                                MESSAGE_ID_UPDATE_ICON_THEMED,
+                                Bundle().apply { putBoolean(KEY_BOOLEAN_VALUE, it) },
+                            )
+                        }
+                    }
                 }
         }
     }
@@ -235,10 +244,11 @@
     companion object {
         const val MESSAGE_ID_UPDATE_SHAPE = 2586
         const val MESSAGE_ID_UPDATE_GRID = 7414
-
         const val MESSAGE_ID_UPDATE_COLOR = 856
+        const val MESSAGE_ID_UPDATE_ICON_THEMED = 311
         const val KEY_COLOR_RESOURCE_IDS: String = "color_resource_ids"
         const val KEY_COLOR_VALUES: String = "color_values"
         const val KEY_DARK_MODE: String = "use_dark_mode"
+        const val KEY_BOOLEAN_VALUE: String = "boolean_value"
     }
 }
diff --git a/src/com/android/wallpaper/picker/di/modules/ThemePickerSharedAppModule.kt b/src/com/android/wallpaper/picker/di/modules/ThemePickerSharedAppModule.kt
index 98c881f..dc3d810 100644
--- a/src/com/android/wallpaper/picker/di/modules/ThemePickerSharedAppModule.kt
+++ b/src/com/android/wallpaper/picker/di/modules/ThemePickerSharedAppModule.kt
@@ -30,9 +30,9 @@
 @InstallIn(SingletonComponent::class)
 abstract class ThemePickerSharedAppModule {
 
+    @Binds @Singleton abstract fun bindDarkModeUtil(impl: DarkModeUtilImpl): DarkModeUtil
+
     @Binds
     @Singleton
-    abstract fun bindGridOptionsManager2(impl: DefaultShapeGridManager): ShapeGridManager
-
-    @Binds @Singleton abstract fun bindDarkModeUtil(impl: DarkModeUtilImpl): DarkModeUtil
+    abstract fun bindGridOptionsManager(impl: DefaultShapeGridManager): ShapeGridManager
 }
diff --git a/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt b/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
index 539f4f9..41b8099 100644
--- a/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
+++ b/src_override/com/android/wallpaper/modules/ThemePickerAppModule.kt
@@ -44,7 +44,9 @@
 import com.android.wallpaper.effects.DefaultEffectsController
 import com.android.wallpaper.effects.EffectsController
 import com.android.wallpaper.module.DefaultPartnerProvider
+import com.android.wallpaper.module.DefaultRecentWallpaperManager
 import com.android.wallpaper.module.PartnerProvider
+import com.android.wallpaper.module.RecentWallpaperManager
 import com.android.wallpaper.module.WallpaperPreferences
 import com.android.wallpaper.module.logging.UserEventLogger
 import com.android.wallpaper.picker.category.domain.interactor.CategoriesLoadingStatusInteractor
@@ -203,6 +205,12 @@
         impl: ThemePickerWorkspaceCallbackBinder
     ): WorkspaceCallbackBinder
 
+    @Binds
+    @Singleton
+    abstract fun bindRecentWallpaperManager(
+        impl: DefaultRecentWallpaperManager
+    ): RecentWallpaperManager
+
     companion object {
 
         @Provides
diff --git a/tests/common/src/com/android/wallpaper/di/modules/ThemePickerSharedAppTestModule.kt b/tests/common/src/com/android/wallpaper/di/modules/ThemePickerSharedAppTestModule.kt
index 4969db4..2422410 100644
--- a/tests/common/src/com/android/wallpaper/di/modules/ThemePickerSharedAppTestModule.kt
+++ b/tests/common/src/com/android/wallpaper/di/modules/ThemePickerSharedAppTestModule.kt
@@ -34,9 +34,9 @@
 )
 abstract class ThemePickerSharedAppTestModule {
 
+    @Binds @Singleton abstract fun bindDarkModeUtil(impl: FakeDarkModeUtil): DarkModeUtil
+
     @Binds
     @Singleton
-    abstract fun bindGridOptionsManager2(impl: FakeShapeGridManager): ShapeGridManager
-
-    @Binds @Singleton abstract fun bindDarkModeUtil(impl: FakeDarkModeUtil): DarkModeUtil
+    abstract fun bindGridOptionsManager(impl: FakeShapeGridManager): ShapeGridManager
 }
diff --git a/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt b/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
index 7b7f600..47ea474 100644
--- a/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
+++ b/tests/module/src/com/android/wallpaper/ThemePickerTestModule.kt
@@ -42,8 +42,10 @@
 import com.android.wallpaper.customization.ui.binder.ThemePickerToolbarBinder
 import com.android.wallpaper.effects.EffectsController
 import com.android.wallpaper.effects.FakeEffectsController
+import com.android.wallpaper.module.DefaultRecentWallpaperManager
 import com.android.wallpaper.module.Injector
 import com.android.wallpaper.module.PartnerProvider
+import com.android.wallpaper.module.RecentWallpaperManager
 import com.android.wallpaper.module.WallpaperPreferences
 import com.android.wallpaper.module.logging.TestUserEventLogger
 import com.android.wallpaper.module.logging.UserEventLogger
@@ -200,6 +202,12 @@
         impl: ThemePickerWorkspaceCallbackBinder
     ): WorkspaceCallbackBinder
 
+    @Binds
+    @Singleton
+    abstract fun bindRecentWallpaperManager(
+        impl: DefaultRecentWallpaperManager
+    ): RecentWallpaperManager
+
     companion object {
 
         @Provides
diff --git a/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index d3fc546..fd2c801 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -23,9 +23,9 @@
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import com.android.systemui.plugins.clocks.AxisType
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.systemui.plugins.clocks.ClockFontAxis
 import com.android.systemui.plugins.clocks.ClockFontAxis.Companion.merge
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.ClockId
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -88,7 +88,7 @@
         _selectedClockSize.value = size
     }
 
-    override suspend fun setClockFontAxes(axisSettings: List<ClockFontAxisSetting>) {
+    override suspend fun setClockFontAxes(axisSettings: ClockAxisStyle) {
         fontAxes.update { fontAxes -> fontAxes.merge(axisSettings) }
     }
 
diff --git a/tests/robotests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt b/tests/robotests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
index 4bb542c..484d810 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
@@ -3,6 +3,7 @@
 import androidx.test.filters.SmallTest
 import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
 import com.android.customization.picker.clock.shared.ClockSize
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient
 import com.android.wallpaper.picker.customization.data.repository.CustomizationRuntimeValuesRepository
 import com.android.wallpaper.testing.FakeSnapshotStore
@@ -88,7 +89,7 @@
     @Test
     fun setFontAxisSettings() = runTest {
         val axisSettings = collectLastValue(underTest.axisSettings)
-        val fakeSettings = listOf(FakeClockPickerRepository.buildFakeAxis(10).toSetting())
+        val fakeSettings = ClockAxisStyle(listOf(FakeClockPickerRepository.buildFakeAxis(10)))
 
         underTest.setClockFontAxes(fakeSettings)
 
diff --git a/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
index 719bfd2..b570003 100644
--- a/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
+++ b/tests/robotests/src/com/android/customization/picker/clock/ui/FakeClockViewFactory.kt
@@ -1,15 +1,15 @@
 package com.android.customization.picker.clock.ui
 
-import android.graphics.RectF
 import android.view.View
 import androidx.lifecycle.LifecycleOwner
 import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
 import com.android.customization.picker.clock.ui.view.ClockViewFactory
+import com.android.systemui.plugins.clocks.ClockAxisStyle
 import com.android.systemui.plugins.clocks.ClockConfig
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockEventListener
 import com.android.systemui.plugins.clocks.ClockEvents
 import com.android.systemui.plugins.clocks.ClockFaceController
-import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -34,7 +34,7 @@
             isDarkTheme: Boolean,
             dozeFraction: Float,
             foldFraction: Float,
-            onBoundsChanged: (RectF) -> Unit,
+            clockListener: ClockEventListener?,
         ) = TODO("Not yet implemented")
 
         override fun dump(pw: PrintWriter) = TODO("Not yet implemented")
@@ -58,7 +58,7 @@
         TODO("Not yet implemented")
     }
 
-    override fun updateFontAxes(clockId: String, settings: List<ClockFontAxisSetting>) {
+    override fun updateFontAxes(clockId: String, settings: ClockAxisStyle) {
         TODO("Not yet implemented")
     }
 
diff --git a/tests/robotests/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModelTest.kt b/tests/robotests/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModelTest.kt
index fbd56bc..3bdbb9d 100644
--- a/tests/robotests/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModelTest.kt
+++ b/tests/robotests/src/com/android/customization/picker/mode/ui/viewmodel/DarkModeViewModelTest.kt
@@ -49,7 +49,7 @@
     @Inject lateinit var darkModeRepository: DarkModeRepository
     @Inject lateinit var darkModeInteractor: DarkModeInteractor
     @Inject lateinit var logger: TestThemesUserEventLogger
-    lateinit var darkModeViewModel: DarkModeViewModel
+    private lateinit var darkModeViewModel: DarkModeViewModel
 
     @Inject lateinit var testDispatcher: TestDispatcher
     @Inject lateinit var testScope: TestScope
@@ -90,10 +90,11 @@
     fun toggleDarkMode() {
         testScope.runTest {
             uiModeManager.setNightModeActivated(false)
-            darkModeRepository.refreshIsDarkModeActivated()
+            darkModeRepository.refreshIsDarkMode()
             val getOverridingIsDarkMode = collectLastValue(darkModeViewModel.overridingIsDarkMode)
             val getPreviewingIsDarkMode = collectLastValue(darkModeViewModel.previewingIsDarkMode)
             val getToggleDarkMode = collectLastValue(darkModeViewModel.toggleDarkMode)
+            assertThat(getOverridingIsDarkMode()).isNull()
             assertThat(getPreviewingIsDarkMode()).isFalse()
 
             getToggleDarkMode()?.invoke()
@@ -109,10 +110,36 @@
     }
 
     @Test
+    fun previewingIsDarkMode_systemChanges() {
+        testScope.runTest {
+            uiModeManager.setNightModeActivated(false)
+            darkModeRepository.refreshIsDarkMode()
+            val getOverridingIsDarkMode = collectLastValue(darkModeViewModel.overridingIsDarkMode)
+            val getPreviewingIsDarkMode = collectLastValue(darkModeViewModel.previewingIsDarkMode)
+            assertThat(getOverridingIsDarkMode()).isNull()
+            assertThat(getPreviewingIsDarkMode()).isFalse()
+
+            // Turn on dark mode
+            uiModeManager.setNightModeActivated(true)
+            darkModeRepository.refreshIsDarkMode()
+
+            assertThat(getOverridingIsDarkMode()).isNull()
+            assertThat(getPreviewingIsDarkMode()).isTrue()
+
+            // Turn off dark mode
+            uiModeManager.setNightModeActivated(false)
+            darkModeRepository.refreshIsDarkMode()
+
+            assertThat(getOverridingIsDarkMode()).isNull()
+            assertThat(getPreviewingIsDarkMode()).isFalse()
+        }
+    }
+
+    @Test
     fun onApply_shouldLogDarkTheme() {
         testScope.runTest {
             uiModeManager.setNightModeActivated(false)
-            darkModeRepository.refreshIsDarkModeActivated()
+            darkModeRepository.refreshIsDarkMode()
             val getToggleDarkMode = collectLastValue(darkModeViewModel.toggleDarkMode)
             val onApply = collectLastValue(darkModeViewModel.onApply)
 
@@ -127,7 +154,7 @@
     fun onApply_shouldApplyDarkTheme() {
         testScope.runTest {
             uiModeManager.setNightModeActivated(false)
-            darkModeRepository.refreshIsDarkModeActivated()
+            darkModeRepository.refreshIsDarkMode()
             val getToggleDarkMode = collectLastValue(darkModeViewModel.toggleDarkMode)
             val onApply = collectLastValue(darkModeViewModel.onApply)
 
diff --git a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/AppIconPickerViewModelTest.kt b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/AppIconPickerViewModelTest.kt
new file mode 100644
index 0000000..dd5d48e
--- /dev/null
+++ b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/AppIconPickerViewModelTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2025 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 androidx.test.filters.SmallTest
+import com.android.customization.model.grid.FakeShapeGridManager
+import com.android.customization.picker.grid.domain.interactor.AppIconInteractor
+import com.android.customization.picker.grid.ui.viewmodel.ShapeIconViewModel
+import com.android.wallpaper.picker.common.text.ui.viewmodel.Text
+import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel2
+import com.android.wallpaper.testing.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.android.testing.HiltAndroidRule
+import dagger.hilt.android.testing.HiltAndroidTest
+import javax.inject.Inject
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@HiltAndroidTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(RobolectricTestRunner::class)
+class AppIconPickerViewModelTest {
+
+    @get:Rule var hiltRule = HiltAndroidRule(this)
+    @Inject lateinit var testScope: TestScope
+    @Inject lateinit var gridOptionsManager: FakeShapeGridManager
+    @Inject lateinit var interactor: AppIconInteractor
+    @Inject @ApplicationContext lateinit var appContext: Context
+
+    private lateinit var underTest: AppIconPickerViewModel
+
+    @Before
+    fun setUp() {
+        hiltRule.inject()
+        underTest = AppIconPickerViewModel(interactor, testScope.backgroundScope)
+    }
+
+    @After
+    fun tearDown() {
+        Dispatchers.resetMain()
+    }
+
+    @Test
+    fun selectedShapeKey() =
+        testScope.runTest {
+            val selectedShapeKey = collectLastValue(underTest.selectedShapeKey)
+
+            assertThat(selectedShapeKey()).isEqualTo("arch")
+        }
+
+    @Test
+    fun shapeOptions() =
+        testScope.runTest {
+            val shapeOptions = collectLastValue(underTest.shapeOptions)
+
+            for (i in 0 until FakeShapeGridManager.DEFAULT_SHAPE_OPTION_LIST.size) {
+                val (expectedKey, expectedPath, expectedTitle) =
+                    with(FakeShapeGridManager.DEFAULT_SHAPE_OPTION_LIST[i]) {
+                        arrayOf(key, path, title)
+                    }
+                assertShapeItem(
+                    optionItem = shapeOptions()?.get(i),
+                    key = FakeShapeGridManager.DEFAULT_SHAPE_OPTION_LIST[i].key,
+                    payload = ShapeIconViewModel(expectedKey, expectedPath),
+                    text = Text.Loaded(expectedTitle),
+                    isTextUserVisible = true,
+                    isSelected = expectedKey == "arch",
+                    isEnabled = true,
+                )
+            }
+        }
+
+    @Test
+    fun shapeOptions_whenClickOnCircleOption() =
+        testScope.runTest {
+            val shapeOptions = collectLastValue(underTest.shapeOptions)
+            val previewingShapeKey = collectLastValue(underTest.previewingShapeKey)
+            val circleOption = shapeOptions()?.firstOrNull { it.key.value == "circle" }
+            val onCircleOptionClicked = circleOption?.onClicked?.let { collectLastValue(it) }
+            checkNotNull(onCircleOptionClicked)
+
+            onCircleOptionClicked()?.invoke()
+
+            assertThat(previewingShapeKey()).isEqualTo("circle")
+            for (i in 0 until FakeShapeGridManager.DEFAULT_SHAPE_OPTION_LIST.size) {
+                val expectedKey = FakeShapeGridManager.DEFAULT_SHAPE_OPTION_LIST[i].key
+                val expectedPath = FakeShapeGridManager.DEFAULT_SHAPE_OPTION_LIST[i].path
+                val expectedTitle = FakeShapeGridManager.DEFAULT_SHAPE_OPTION_LIST[i].title
+                assertShapeItem(
+                    optionItem = shapeOptions()?.get(i),
+                    key = expectedKey,
+                    payload = ShapeIconViewModel(expectedKey, expectedPath),
+                    text = Text.Loaded(expectedTitle),
+                    isTextUserVisible = true,
+                    isSelected = expectedKey == "circle",
+                    isEnabled = true,
+                )
+            }
+        }
+
+    @Test
+    fun onApple_shouldBeNonnull_whenClickOnCircleOption() =
+        testScope.runTest {
+            val shapeOptions = collectLastValue(underTest.shapeOptions)
+            val circleOption = shapeOptions()?.firstOrNull { it.key.value == "circle" }
+            val onCircleOptionClicked = circleOption?.onClicked?.let { collectLastValue(it) }
+            val onApply = collectLastValue(underTest.onApply)
+            checkNotNull(onCircleOptionClicked)
+
+            assertThat(onApply()).isNull()
+
+            onCircleOptionClicked()?.invoke()
+
+            assertThat(onApply()).isNotNull()
+        }
+
+    @Test
+    fun isThemeIconEnabled_shouldBeFalseByDefault() =
+        testScope.runTest {
+            val isThemeIconEnabled = collectLastValue(underTest.isThemedIconEnabled)
+
+            assertThat(isThemeIconEnabled()).isFalse()
+        }
+
+    @Test
+    fun previewingIsThemeIconEnabled_shouldBeFalseByDefault() =
+        testScope.runTest {
+            val previewingIsThemeIconEnabled =
+                collectLastValue(underTest.previewingIsThemeIconEnabled)
+
+            assertThat(previewingIsThemeIconEnabled()).isFalse()
+        }
+
+    @Test
+    fun previewingIsThemeIconEnabled_shouldBeTrue_whenToggle() =
+        testScope.runTest {
+            val toggleThemedIcon = collectLastValue(underTest.toggleThemedIcon)
+            val previewingIsThemeIconEnabled =
+                collectLastValue(underTest.previewingIsThemeIconEnabled)
+
+            assertThat(previewingIsThemeIconEnabled()).isFalse()
+
+            toggleThemedIcon()?.invoke()
+
+            assertThat(previewingIsThemeIconEnabled()).isTrue()
+        }
+
+    @Test
+    fun onApple_shouldBeNonnull_whenToggle() =
+        testScope.runTest {
+            val toggleThemedIcon = collectLastValue(underTest.toggleThemedIcon)
+            val onApply = collectLastValue(underTest.onApply)
+
+            assertThat(onApply()).isNull()
+
+            toggleThemedIcon()?.invoke()
+
+            assertThat(onApply()).isNotNull()
+        }
+
+    private fun TestScope.assertShapeItem(
+        optionItem: OptionItemViewModel2<ShapeIconViewModel>?,
+        key: String,
+        payload: ShapeIconViewModel?,
+        text: Text,
+        isTextUserVisible: Boolean,
+        isSelected: Boolean,
+        isEnabled: Boolean,
+    ) {
+        checkNotNull(optionItem)
+        assertThat(collectLastValue(optionItem.key)()).isEqualTo(key)
+        assertThat(optionItem.text).isEqualTo(text)
+        assertThat(optionItem.payload).isEqualTo(payload)
+        assertThat(optionItem.isTextUserVisible).isEqualTo(isTextUserVisible)
+        assertThat(collectLastValue(optionItem.isSelected)()).isEqualTo(isSelected)
+        assertThat(optionItem.isEnabled).isEqualTo(isEnabled)
+    }
+}
diff --git a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt
index 649c298..a8edc4d 100644
--- a/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt
+++ b/tests/robotests/src/com/android/wallpaper/customization/ui/viewmodel/ColorPickerViewModel2Test.kt
@@ -26,15 +26,18 @@
 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor2
 import com.android.customization.picker.color.shared.model.ColorType
 import com.android.customization.picker.color.ui.viewmodel.ColorOptionIconViewModel
+import com.android.customization.picker.mode.data.repository.DarkModeStateRepository
 import com.android.systemui.monet.Style
 import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel
 import com.android.wallpaper.picker.customization.ui.viewmodel.FloatingToolbarTabViewModel
 import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel2
-import com.android.wallpaper.testing.FakeSnapshotStore
 import com.android.wallpaper.testing.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import dagger.hilt.android.internal.lifecycle.RetainedLifecycleImpl
+import dagger.hilt.android.testing.HiltAndroidRule
+import dagger.hilt.android.testing.HiltAndroidTest
+import javax.inject.Inject
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
@@ -45,36 +48,40 @@
 import kotlinx.coroutines.test.setMain
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 
+@HiltAndroidTest
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(RobolectricTestRunner::class)
 class ColorPickerViewModel2Test {
+    @get:Rule var hiltRule = HiltAndroidRule(this)
+
     private val logger = TestThemesUserEventLogger()
     private lateinit var underTest: ColorPickerViewModel2
-    private lateinit var repository: FakeColorPickerRepository2
-    private lateinit var interactor: ColorPickerInteractor2
-    private lateinit var store: FakeSnapshotStore
     private lateinit var colorUpdateViewModel: ColorUpdateViewModel
 
     private lateinit var context: Context
     private lateinit var testScope: TestScope
 
+    @Inject lateinit var repository: FakeColorPickerRepository2
+    @Inject lateinit var interactor: ColorPickerInteractor2
+    @Inject lateinit var darkModeStateRepository: DarkModeStateRepository
+
     @Before
     fun setUp() {
+        hiltRule.inject()
+
         context = InstrumentationRegistry.getInstrumentation().targetContext
         val testDispatcher = UnconfinedTestDispatcher()
         Dispatchers.setMain(testDispatcher)
         testScope = TestScope(testDispatcher)
-        repository = FakeColorPickerRepository2()
-        store = FakeSnapshotStore()
 
-        interactor = ColorPickerInteractor2(repository = repository)
-
-        colorUpdateViewModel = ColorUpdateViewModel(context, RetainedLifecycleImpl())
+        colorUpdateViewModel =
+            ColorUpdateViewModel(context, RetainedLifecycleImpl(), darkModeStateRepository)
 
         underTest =
             ColorPickerViewModel2(