Merge "Import translations. DO NOT MERGE ANYWHERE" into udc-dev
diff --git a/res/color/keyguard_quick_affordance_slot_tab_background_color.xml b/res/color/picker_fragment_tab_background_color.xml
similarity index 90%
rename from res/color/keyguard_quick_affordance_slot_tab_background_color.xml
rename to res/color/picker_fragment_tab_background_color.xml
index 4708cef..6edf5f8 100644
--- a/res/color/keyguard_quick_affordance_slot_tab_background_color.xml
+++ b/res/color/picker_fragment_tab_background_color.xml
@@ -16,6 +16,6 @@
   -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_selected="true" android:color="@color/color_accent_primary" />
+    <item android:state_selected="true" android:color="@color/system_primary" />
     <item android:color="@android:color/transparent" />
 </selector>
diff --git a/res/color/picker_fragment_tab_text_color.xml b/res/color/picker_fragment_tab_text_color.xml
index 84502d4..438e4fc 100644
--- a/res/color/picker_fragment_tab_text_color.xml
+++ b/res/color/picker_fragment_tab_text_color.xml
@@ -16,6 +16,6 @@
   -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_selected="true" android:color="@color/text_color_on_accent" />
-    <item android:color="@color/text_color_primary" />
+    <item android:state_selected="true" android:color="@color/system_on_primary" />
+    <item android:color="@color/system_on_surface" />
 </selector>
diff --git a/res/drawable/color_option_selected_no_background.xml b/res/drawable/color_option_selected_no_background.xml
index 13451a5..365d70a 100644
--- a/res/drawable/color_option_selected_no_background.xml
+++ b/res/drawable/color_option_selected_no_background.xml
@@ -25,7 +25,7 @@
             android:innerRadius="@dimen/component_color_overflow_small_radius_default"
             android:thickness="2dp"
             android:useLevel="false">
-            <solid android:color="@color/text_color_primary"/>
+            <solid android:color="@color/system_secondary"/>
         </shape>
     </item>
     <item
diff --git a/res/drawable/ic_settings.xml b/res/drawable/ic_settings.xml
index b4341a6..1a5b281 100644
--- a/res/drawable/ic_settings.xml
+++ b/res/drawable/ic_settings.xml
@@ -17,9 +17,7 @@
     android:width="24dp"
     android:height="24dp"
     android:viewportWidth="960"
-    android:viewportHeight="960"
-    android:tintMode="multiply"
-    android:tint="@color/text_color_primary">
+    android:viewportHeight="960">
     <path
         android:fillColor="@android:color/white"
         android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480Z"/>
diff --git a/res/drawable/picker_fragment_tab_background.xml b/res/drawable/picker_fragment_tab_background.xml
index 3dad344..2742c72 100644
--- a/res/drawable/picker_fragment_tab_background.xml
+++ b/res/drawable/picker_fragment_tab_background.xml
@@ -16,5 +16,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
     <corners android:radius="12dp" />
-    <solid android:color="@color/keyguard_quick_affordance_slot_tab_background_color" />
+    <solid android:color="@color/picker_fragment_tab_background_color" />
 </shape>
diff --git a/res/layout/clock_color_and_size_button.xml b/res/layout/clock_color_and_size_button.xml
index d54c33d..bb2b77d 100644
--- a/res/layout/clock_color_and_size_button.xml
+++ b/res/layout/clock_color_and_size_button.xml
@@ -1,7 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:minHeight="@dimen/clock_color_size_button_min_height"
     android:background="@drawable/clock_color_size_button_background"
     android:orientation="horizontal"
@@ -14,13 +16,14 @@
         android:layout_height="@dimen/clock_color_size_button_icon_size"
         android:src="@drawable/ic_settings"
         android:importantForAccessibility="no"
-        android:drawableTint="@color/text_color_primary"
-        android:layout_marginEnd="@dimen/clock_color_size_button_icon_margin_end"/>
+        android:tint="@color/system_primary"
+        android:layout_marginEnd="@dimen/clock_color_size_button_icon_margin_end"
+        tools:ignore="UseAppTint" />
 
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textColor="@color/text_color_primary"
+        android:textColor="@color/system_primary"
         android:text="@string/clock_color_and_size_title" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/clock_size_radio_button_group.xml b/res/layout/clock_size_radio_button_group.xml
index 30e39ad..4264007 100644
--- a/res/layout/clock_size_radio_button_group.xml
+++ b/res/layout/clock_size_radio_button_group.xml
@@ -57,7 +57,6 @@
         android:id="@+id/button_container_small"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:minHeight="48dp"
         android:orientation="horizontal">
 
         <RadioButton
diff --git a/res/layout/color_section_view2.xml b/res/layout/color_section_view2.xml
index 851bc5d..9dbdf15 100644
--- a/res/layout/color_section_view2.xml
+++ b/res/layout/color_section_view2.xml
@@ -58,10 +58,10 @@
         android:gravity="center"
         android:drawablePadding="12dp"
         android:drawableStart="@drawable/ic_nav_color"
-        android:drawableTint="@color/text_color_primary"
+        android:drawableTint="@color/system_primary"
         android:text="@string/more_colors"
         android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
-        android:textColor="@color/text_color_primary"
+        android:textColor="@color/system_primary"
         android:visibility="gone"
         tools:ignore="UseCompatTextViewDrawableXml" />
 </com.android.customization.picker.color.ui.view.ColorSectionView2>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 16b5de7..254a055 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -180,6 +180,4 @@
     <dimen name="small_clock_height">114dp</dimen>
     <dimen name="small_clock_padding_top">28dp</dimen>
     <dimen name="clock_padding_start">28dp</dimen>
-
-    <dimen name="tab_touch_delegate_height_padding">8dp</dimen>
 </resources>
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index 66814c5..98c6361 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -16,6 +16,7 @@
 package com.android.customization.module
 
 import android.app.UiModeManager
+import android.app.WallpaperManager
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
@@ -45,6 +46,7 @@
 import com.android.customization.picker.clock.data.repository.ClockPickerRepositoryImpl
 import com.android.customization.picker.clock.data.repository.ClockRegistryProvider
 import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer
 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.ClockSectionViewModel
@@ -98,15 +100,11 @@
     private var keyguardQuickAffordanceSnapshotRestorer: KeyguardQuickAffordanceSnapshotRestorer? =
         null
     private var notificationsSnapshotRestorer: NotificationsSnapshotRestorer? = null
-    /**
-     * Mapping from LifeCycleOwner's hashcode to ClockRegistry as we need to keep different
-     * ClockRegistries per LifeCycle to ensure proper cleanup
-     */
-    private var clockRegistries: MutableMap<Int, ClockRegistry> = HashMap()
     private var clockPickerInteractor: ClockPickerInteractor? = null
     private var clockSectionViewModel: ClockSectionViewModel? = null
     private var clockCarouselViewModelFactory: ClockCarouselViewModel.Factory? = null
     private var clockViewFactories: MutableMap<Int, ClockViewFactory> = HashMap()
+    private var clockPickerSnapshotRestorer: ClockPickerSnapshotRestorer? = null
     private var notificationsInteractor: NotificationsInteractor? = null
     private var notificationSectionViewModelFactory: NotificationSectionViewModel.Factory? = null
     private var colorPickerInteractor: ColorPickerInteractor? = null
@@ -120,6 +118,7 @@
     private var gridInteractor: GridInteractor? = null
     private var gridSnapshotRestorer: GridSnapshotRestorer? = null
     private var gridScreenViewModelFactory: GridScreenViewModel.Factory? = null
+    private var clockRegistryProvider: ClockRegistryProvider? = null
 
     override fun getCustomizationSections(activity: ComponentActivity): CustomizationSections {
         return customizationSections
@@ -195,18 +194,26 @@
         return fragmentFactory ?: ThemePickerFragmentFactory().also { fragmentFactory }
     }
 
-    override fun getSnapshotRestorers(context: Context): Map<Int, SnapshotRestorer> {
-        return super<WallpaperPicker2Injector>.getSnapshotRestorers(context).toMutableMap().apply {
-            this[KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER] =
-                getKeyguardQuickAffordanceSnapshotRestorer(context)
-            this[KEY_WALLPAPER_SNAPSHOT_RESTORER] = getWallpaperSnapshotRestorer(context)
-            this[KEY_NOTIFICATIONS_SNAPSHOT_RESTORER] = getNotificationsSnapshotRestorer(context)
-            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, getWallpaperColorsViewModel())
-        }
+    override fun getSnapshotRestorers(
+        context: Context,
+        lifecycleOwner: LifecycleOwner
+    ): Map<Int, SnapshotRestorer> {
+        return super<WallpaperPicker2Injector>.getSnapshotRestorers(context, lifecycleOwner)
+            .toMutableMap()
+            .apply {
+                this[KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER] =
+                    getKeyguardQuickAffordanceSnapshotRestorer(context)
+                this[KEY_WALLPAPER_SNAPSHOT_RESTORER] = getWallpaperSnapshotRestorer(context)
+                this[KEY_NOTIFICATIONS_SNAPSHOT_RESTORER] =
+                    getNotificationsSnapshotRestorer(context)
+                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, getWallpaperColorsViewModel())
+                this[KEY_CLOCKS_SNAPSHOT_RESTORER] =
+                    getClockPickerSnapshotRestorer(context, lifecycleOwner)
+            }
     }
 
     override fun getCustomizationPreferences(context: Context): CustomizationPreferences {
@@ -264,16 +271,6 @@
                 .also { keyguardQuickAffordancePickerViewModelFactory = it }
     }
 
-    fun getNotificationSectionViewModelFactory(
-        context: Context,
-    ): NotificationSectionViewModel.Factory {
-        return notificationSectionViewModelFactory
-            ?: NotificationSectionViewModel.Factory(
-                    interactor = getNotificationsInteractor(context),
-                )
-                .also { notificationSectionViewModelFactory = it }
-    }
-
     private fun getKeyguardQuickAffordancePickerInteractorImpl(
         context: Context
     ): KeyguardQuickAffordancePickerInteractor {
@@ -306,6 +303,32 @@
                 .also { keyguardQuickAffordanceSnapshotRestorer = it }
     }
 
+    fun getNotificationSectionViewModelFactory(
+        context: Context,
+    ): NotificationSectionViewModel.Factory {
+        return notificationSectionViewModelFactory
+            ?: NotificationSectionViewModel.Factory(
+                    interactor = getNotificationsInteractor(context),
+                )
+                .also { notificationSectionViewModelFactory = it }
+    }
+
+    private fun getNotificationsInteractor(
+        context: Context,
+    ): NotificationsInteractor {
+        return notificationsInteractor
+            ?: NotificationsInteractor(
+                    repository =
+                        NotificationsRepository(
+                            scope = getApplicationCoroutineScope(),
+                            backgroundDispatcher = Dispatchers.IO,
+                            secureSettingsRepository = getSecureSettingsRepository(context),
+                        ),
+                    snapshotRestorer = { getNotificationsSnapshotRestorer(context) },
+                )
+                .also { notificationsInteractor = it }
+    }
+
     private fun getNotificationsSnapshotRestorer(context: Context): NotificationsSnapshotRestorer {
         return notificationsSnapshotRestorer
             ?: NotificationsSnapshotRestorer(
@@ -318,26 +341,15 @@
     }
 
     override fun getClockRegistry(context: Context, lifecycleOwner: LifecycleOwner): ClockRegistry {
-        return clockRegistries[lifecycleOwner.hashCode()]
-            ?: ClockRegistryProvider(
-                    context = context,
-                    coroutineScope = getApplicationCoroutineScope(),
-                    mainDispatcher = Dispatchers.Main,
-                    backgroundDispatcher = Dispatchers.IO,
-                )
-                .get()
-                .also {
-                    clockRegistries[lifecycleOwner.hashCode()] = it
-                    lifecycleOwner.lifecycle.addObserver(
-                        object : DefaultLifecycleObserver {
-                            override fun onDestroy(owner: LifecycleOwner) {
-                                super.onDestroy(owner)
-                                clockRegistries[lifecycleOwner.hashCode()]?.unregisterListeners()
-                                clockRegistries.remove(lifecycleOwner.hashCode())
-                            }
-                        }
+        return (clockRegistryProvider
+                ?: ClockRegistryProvider(
+                        context = context,
+                        coroutineScope = getApplicationCoroutineScope(),
+                        mainDispatcher = Dispatchers.Main,
+                        backgroundDispatcher = Dispatchers.IO,
                     )
-                }
+                    .also { clockRegistryProvider = it })
+            .getForOwner(lifecycleOwner)
     }
 
     override fun getClockPickerInteractor(
@@ -346,11 +358,14 @@
     ): ClockPickerInteractor {
         return clockPickerInteractor
             ?: ClockPickerInteractor(
-                    ClockPickerRepositoryImpl(
-                        secureSettingsRepository = getSecureSettingsRepository(context),
-                        registry = getClockRegistry(context, lifecycleOwner),
-                        scope = getApplicationCoroutineScope(),
-                    ),
+                    repository =
+                        ClockPickerRepositoryImpl(
+                            secureSettingsRepository = getSecureSettingsRepository(context),
+                            registry = getClockRegistry(context, lifecycleOwner),
+                            scope = getApplicationCoroutineScope(),
+                            mainDispatcher = Dispatchers.Main,
+                        ),
+                    snapshotRestorer = { getClockPickerSnapshotRestorer(context, lifecycleOwner) },
                 )
                 .also { clockPickerInteractor = it }
     }
@@ -380,7 +395,11 @@
                     activity.applicationContext,
                     ScreenSizeCalculator.getInstance()
                         .getScreenSize(activity.windowManager.defaultDisplay),
-                    getClockRegistry(activity.applicationContext, activity),
+                    WallpaperManager.getInstance(activity.applicationContext),
+                    getClockRegistry(
+                        context = activity.applicationContext,
+                        lifecycleOwner = activity,
+                    ),
                 )
                 .also {
                     clockViewFactories[activityHashCode] = it
@@ -396,20 +415,14 @@
                 }
     }
 
-    private fun getNotificationsInteractor(
+    private fun getClockPickerSnapshotRestorer(
         context: Context,
-    ): NotificationsInteractor {
-        return notificationsInteractor
-            ?: NotificationsInteractor(
-                    repository =
-                        NotificationsRepository(
-                            scope = getApplicationCoroutineScope(),
-                            backgroundDispatcher = Dispatchers.IO,
-                            secureSettingsRepository = getSecureSettingsRepository(context),
-                        ),
-                    snapshotRestorer = { getNotificationsSnapshotRestorer(context) },
-                )
-                .also { notificationsInteractor = it }
+        lifecycleOwner: LifecycleOwner
+    ): ClockPickerSnapshotRestorer {
+        return clockPickerSnapshotRestorer
+            ?: ClockPickerSnapshotRestorer(getClockPickerInteractor(context, lifecycleOwner)).also {
+                clockPickerSnapshotRestorer = it
+            }
     }
 
     override fun getColorPickerInteractor(
@@ -570,6 +583,7 @@
         private val KEY_APP_GRID_SNAPSHOT_RESTORER = KEY_THEMED_ICON_SNAPSHOT_RESTORER + 1
         @JvmStatic
         private val KEY_COLOR_PICKER_SNAPSHOT_RESTORER = KEY_APP_GRID_SNAPSHOT_RESTORER + 1
+        @JvmStatic private val KEY_CLOCKS_SNAPSHOT_RESTORER = KEY_COLOR_PICKER_SNAPSHOT_RESTORER + 1
 
         /**
          * When this injector is overridden, this is the minimal value that should be used by
@@ -577,6 +591,6 @@
          *
          * It should always be greater than the biggest restorer key.
          */
-        @JvmStatic protected val MIN_SNAPSHOT_RESTORER_KEY = KEY_COLOR_PICKER_SNAPSHOT_RESTORER + 1
+        @JvmStatic protected val MIN_SNAPSHOT_RESTORER_KEY = KEY_CLOCKS_SNAPSHOT_RESTORER + 1
     }
 }
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 cb2c86e..57f77b0 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -42,7 +42,7 @@
      * @param colorToneProgress color tone from 0 to 100 to apply to the selected color
      * @param seedColor the actual clock color after blending the selected color and color tone
      */
-    fun setClockColor(
+    suspend fun setClockColor(
         selectedColorId: String?,
         @IntRange(from = 0, to = 100) colorToneProgress: Int,
         @ColorInt seedColor: Int?,
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 747f174..be6c6cb 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepositoryImpl.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.wallpaper.settings.data.repository.SecureSettingsRepository
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
@@ -32,6 +33,7 @@
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.mapNotNull
@@ -43,6 +45,7 @@
     private val secureSettingsRepository: SecureSettingsRepository,
     private val registry: ClockRegistry,
     scope: CoroutineScope,
+    mainDispatcher: CoroutineDispatcher,
 ) : ClockPickerRepository {
 
     @OptIn(ExperimentalCoroutinesApi::class)
@@ -67,6 +70,7 @@
                 send()
                 awaitClose { registry.unregisterClockChangeListener(listener) }
             }
+            .flowOn(mainDispatcher)
             .mapLatest { allClocks ->
                 // Loading list of clock plugins can cause many consecutive calls of
                 // onAvailableClocksChanged(). We only care about the final fully-initiated clock
@@ -108,6 +112,7 @@
                 send()
                 awaitClose { registry.unregisterClockChangeListener(listener) }
             }
+            .flowOn(mainDispatcher)
             .mapNotNull { it }
 
     override suspend fun setSelectedClock(clockId: String) {
@@ -118,7 +123,7 @@
         }
     }
 
-    override fun setClockColor(
+    override suspend fun setClockColor(
         selectedColorId: String?,
         @IntRange(from = 0, to = 100) colorToneProgress: Int,
         @ColorInt seedColor: Int?,
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockRegistryProvider.kt b/src/com/android/customization/picker/clock/data/repository/ClockRegistryProvider.kt
index e359117..52c3c4e 100644
--- a/src/com/android/customization/picker/clock/data/repository/ClockRegistryProvider.kt
+++ b/src/com/android/customization/picker/clock/data/repository/ClockRegistryProvider.kt
@@ -19,6 +19,8 @@
 import android.content.ComponentName
 import android.content.Context
 import android.view.LayoutInflater
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.plugins.Plugin
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -43,6 +45,7 @@
     private val mainDispatcher: CoroutineDispatcher,
     private val backgroundDispatcher: CoroutineDispatcher,
 ) {
+    private val lifecycleOwners = mutableSetOf<Int>()
     private val pluginManager: PluginManager by lazy { createPluginManager(context) }
     private val clockRegistry: ClockRegistry by lazy {
         ClockRegistry(
@@ -60,10 +63,32 @@
             .apply { registerListeners() }
     }
 
-    fun get(): ClockRegistry {
+    fun getForOwner(lifecycleOwner: LifecycleOwner): ClockRegistry {
+        registerLifecycleOwner(lifecycleOwner)
         return clockRegistry
     }
 
+    private fun registerLifecycleOwner(lifecycleOwner: LifecycleOwner) {
+        lifecycleOwners.add(lifecycleOwner.hashCode())
+
+        lifecycleOwner.lifecycle.addObserver(
+            object : DefaultLifecycleObserver {
+                override fun onDestroy(owner: LifecycleOwner) {
+                    super.onDestroy(owner)
+                    unregisterLifecycleOwner(owner)
+                }
+            }
+        )
+    }
+
+    private fun unregisterLifecycleOwner(lifecycleOwner: LifecycleOwner) {
+        lifecycleOwners.remove(lifecycleOwner.hashCode())
+
+        if (lifecycleOwners.isEmpty()) {
+            clockRegistry.unregisterListeners()
+        }
+    }
+
     private fun createPluginManager(context: Context): PluginManager {
         val privilegedPlugins = listOf<String>()
         val isDebugDevice = true
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 91b2773..30887e5 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
@@ -22,15 +22,21 @@
 import com.android.customization.picker.clock.data.repository.ClockPickerRepository
 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 javax.inject.Provider
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.flow.map
 
 /**
  * Interactor for accessing application clock settings, as well as selecting and configuring custom
  * clocks.
  */
-class ClockPickerInteractor(private val repository: ClockPickerRepository) {
+class ClockPickerInteractor(
+    private val repository: ClockPickerRepository,
+    private val snapshotRestorer: Provider<ClockPickerSnapshotRestorer>,
+) {
 
     val allClocks: Flow<List<ClockMetadataModel>> = repository.allClocks
 
@@ -48,18 +54,68 @@
     val selectedClockSize: Flow<ClockSize> = repository.selectedClockSize
 
     suspend fun setSelectedClock(clockId: String) {
-        repository.setSelectedClock(clockId)
+        // Use the [clockId] to override saved clock id, since it might not be updated in time
+        setClockOption(ClockSnapshotModel(clockId = clockId))
     }
 
-    fun setClockColor(
+    suspend fun setClockColor(
         selectedColorId: String?,
         @IntRange(from = 0, to = 100) colorToneProgress: Int,
         @ColorInt seedColor: Int?,
     ) {
-        repository.setClockColor(selectedColorId, colorToneProgress, seedColor)
+        // Use the color to override saved color, since it might not be updated in time
+        setClockOption(
+            ClockSnapshotModel(
+                selectedColorId = selectedColorId,
+                colorToneProgress = colorToneProgress,
+                seedColor = seedColor,
+            )
+        )
     }
 
     suspend fun setClockSize(size: ClockSize) {
-        repository.setClockSize(size)
+        // Use the [ClockSize] to override saved clock size, since it might not be updated in time
+        setClockOption(ClockSnapshotModel(clockSize = size))
+    }
+
+    suspend fun setClockOption(clockSnapshotModel: ClockSnapshotModel) {
+        // [ClockCarouselViewModel] is monitoring the [ClockPickerInteractor.setSelectedClock] job,
+        // so it needs to finish last.
+        storeCurrentClockOption(clockSnapshotModel)
+
+        clockSnapshotModel.clockSize?.let { repository.setClockSize(it) }
+        clockSnapshotModel.colorToneProgress?.let {
+            repository.setClockColor(
+                selectedColorId = clockSnapshotModel.selectedColorId,
+                colorToneProgress = clockSnapshotModel.colorToneProgress,
+                seedColor = clockSnapshotModel.seedColor
+            )
+        }
+        clockSnapshotModel.clockId?.let { repository.setSelectedClock(it) }
+    }
+
+    /**
+     * Gets the [ClockSnapshotModel] from the storage and override with [latestOption].
+     *
+     * The storage might be in the middle of a write, and not reflecting the user's options, always
+     * pass in a [ClockSnapshotModel] if we know it's the latest option from a user's point of view.
+     *
+     * [selectedColorId] and [seedColor] have null state collide with nullable type, but we know
+     * they are presented whenever there's a [colorToneProgress].
+     */
+    suspend fun getCurrentClockToRestore(latestOption: ClockSnapshotModel? = null) =
+        ClockSnapshotModel(
+            clockId = latestOption?.clockId ?: selectedClockId.firstOrNull(),
+            clockSize = latestOption?.clockSize ?: selectedClockSize.firstOrNull(),
+            colorToneProgress = latestOption?.colorToneProgress ?: colorToneProgress.firstOrNull(),
+            selectedColorId = latestOption?.colorToneProgress?.let { latestOption.selectedColorId }
+                    ?: selectedColorId.firstOrNull(),
+            seedColor = latestOption?.colorToneProgress?.let { latestOption.seedColor }
+                    ?: seedColor.firstOrNull(),
+        )
+
+    private suspend fun storeCurrentClockOption(clockSnapshotModel: ClockSnapshotModel) {
+        val option = getCurrentClockToRestore(clockSnapshotModel)
+        snapshotRestorer.get().storeSnapshot(option)
     }
 }
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerSnapshotRestorer.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerSnapshotRestorer.kt
new file mode 100644
index 0000000..ecaf10f
--- /dev/null
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerSnapshotRestorer.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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.clock.domain.interactor
+
+import android.text.TextUtils
+import android.util.Log
+import com.android.customization.picker.clock.shared.model.ClockSnapshotModel
+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
+
+/** Handles state restoration for clocks. */
+class ClockPickerSnapshotRestorer(private val interactor: ClockPickerInteractor) :
+    SnapshotRestorer {
+    private var snapshotStore: SnapshotStore = SnapshotStore.NOOP
+    private var originalOption: ClockSnapshotModel? = null
+
+    override suspend fun setUpSnapshotRestorer(
+        store: SnapshotStore,
+    ): RestorableSnapshot {
+        snapshotStore = store
+        originalOption = interactor.getCurrentClockToRestore()
+        return snapshot(originalOption)
+    }
+
+    override suspend fun restoreToSnapshot(snapshot: RestorableSnapshot) {
+        originalOption?.let { optionToRestore ->
+            if (
+                TextUtils.isEmpty(optionToRestore.clockId) ||
+                    optionToRestore.clockId != snapshot.args[KEY_CLOCK_ID] ||
+                    optionToRestore.clockSize?.toString() != snapshot.args[KEY_CLOCK_SIZE] ||
+                    optionToRestore.colorToneProgress?.toString() !=
+                        snapshot.args[KEY_COLOR_TONE_PROGRESS] ||
+                    optionToRestore.seedColor?.toString() != snapshot.args[KEY_SEED_COLOR] ||
+                    optionToRestore.selectedColorId != snapshot.args[KEY_COLOR_ID]
+            ) {
+                Log.wtf(
+                    TAG,
+                    """ Original clock option does not match snapshot option to restore to. The
+                        | current implementation doesn't support undo, only a reset back to the
+                        | original clock option."""
+                        .trimMargin(),
+                )
+            }
+
+            interactor.setClockOption(optionToRestore)
+        }
+    }
+
+    fun storeSnapshot(clockSnapshotModel: ClockSnapshotModel) {
+        snapshotStore.store(snapshot(clockSnapshotModel))
+    }
+
+    private fun snapshot(clockSnapshotModel: ClockSnapshotModel? = null): RestorableSnapshot {
+        val options =
+            if (clockSnapshotModel == null) emptyMap()
+            else
+                buildMap {
+                    clockSnapshotModel.clockId?.let { put(KEY_CLOCK_ID, it) }
+                    clockSnapshotModel.clockSize?.let { put(KEY_CLOCK_SIZE, it.toString()) }
+                    clockSnapshotModel.selectedColorId?.let { put(KEY_COLOR_ID, it) }
+                    clockSnapshotModel.colorToneProgress?.let {
+                        put(KEY_COLOR_TONE_PROGRESS, it.toString())
+                    }
+                    clockSnapshotModel.seedColor?.let { put(KEY_SEED_COLOR, it.toString()) }
+                }
+
+        return RestorableSnapshot(options)
+    }
+
+    companion object {
+        private const val TAG = "ClockPickerSnapshotRestorer"
+        private const val KEY_CLOCK_ID = "clock_id"
+        private const val KEY_CLOCK_SIZE = "clock_size"
+        private const val KEY_COLOR_ID = "color_id"
+        private const val KEY_COLOR_TONE_PROGRESS = "color_tone_progress"
+        private const val KEY_SEED_COLOR = "seed_color"
+    }
+}
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt b/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt
deleted file mode 100644
index 7bb3232..0000000
--- a/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2023 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.clock.domain.interactor
-
-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
-
-/** Handles state restoration for clocks. */
-class ClocksSnapshotRestorer : SnapshotRestorer {
-    override suspend fun setUpSnapshotRestorer(
-        store: SnapshotStore,
-    ): RestorableSnapshot {
-        // TODO(b/262924055): implement as part of the clock settings screen.
-        return RestorableSnapshot(mapOf())
-    }
-
-    override suspend fun restoreToSnapshot(snapshot: RestorableSnapshot) {
-        // TODO(b/262924055): implement as part of the clock settings screen.
-    }
-}
diff --git a/src/com/android/customization/picker/clock/shared/model/ClockSnapshotModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockSnapshotModel.kt
new file mode 100644
index 0000000..942cc59
--- /dev/null
+++ b/src/com/android/customization/picker/clock/shared/model/ClockSnapshotModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.clock.shared.model
+
+import androidx.annotation.ColorInt
+import androidx.annotation.IntRange
+import com.android.customization.picker.clock.shared.ClockSize
+
+/** Models application state for a clock option in a picker experience. */
+data class ClockSnapshotModel(
+    val clockId: String? = null,
+    val clockSize: ClockSize? = null,
+    val selectedColorId: String? = null,
+    @IntRange(from = 0, to = 100) val colorToneProgress: Int? = null,
+    @ColorInt val seedColor: Int? = null,
+)
diff --git a/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt b/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt
index d0e6f18..746fdb3 100644
--- a/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt
+++ b/src/com/android/customization/picker/clock/ui/adapter/ClockSettingsTabAdapter.kt
@@ -16,17 +16,13 @@
  */
 package com.android.customization.picker.clock.ui.adapter
 
-import android.graphics.Rect
 import android.view.LayoutInflater
-import android.view.TouchDelegate
 import android.view.View
 import android.view.ViewGroup
 import android.widget.TextView
 import androidx.recyclerview.widget.RecyclerView
 import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsTabViewModel
 import com.android.wallpaper.R
-import com.android.wallpaper.util.ViewUtils.setupTouchDelegate
-
 
 /** Adapter for the tab recycler view on the clock settings screen. */
 class ClockSettingsTabAdapter : RecyclerView.Adapter<ClockSettingsTabAdapter.ViewHolder>() {
@@ -58,10 +54,6 @@
         val item = items[position]
         holder.itemView.isSelected = item.isSelected
         holder.textView.text = item.name
-        holder.textView.setupTouchDelegate(
-            parentView = holder.itemView,
-            heightRes = R.dimen.tab_touch_delegate_height_padding
-        )
         holder.textView.setOnClickListener(
             if (item.onClicked != null) {
                 View.OnClickListener { item.onClicked.invoke() }
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 e703517..270931c 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
@@ -41,6 +41,7 @@
         lifecycleOwner: LifecycleOwner,
     ) {
         carouselView.setClockViewFactory(clockViewFactory)
+        clockViewFactory.updateRegionDarkness()
         val singleClockHostView =
             singleClockView.requireViewById<FrameLayout>(R.id.single_clock_host_view)
         lifecycleOwner.lifecycleScope.launch {
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
index 671a7ae..d8c5dce 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSettingsBinder.kt
@@ -75,7 +75,9 @@
 
                 override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit
                 override fun onStopTrackingTouch(seekBar: SeekBar?) {
-                    seekBar?.progress?.let { viewModel.onSliderProgressStop(it) }
+                    seekBar?.progress?.let {
+                        lifecycleOwner.lifecycleScope.launch { viewModel.onSliderProgressStop(it) }
+                    }
                 }
             }
         )
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
index 1aa6206..3d490d4 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockViewFactory.kt
@@ -15,11 +15,12 @@
  */
 package com.android.customization.picker.clock.ui.view
 
+import android.app.WallpaperColors
+import android.app.WallpaperManager
 import android.content.Context
 import android.content.res.Resources
 import android.graphics.Point
 import android.graphics.Rect
-import android.util.TypedValue
 import android.view.View
 import android.widget.FrameLayout
 import androidx.annotation.ColorInt
@@ -39,6 +40,7 @@
 class ClockViewFactory(
     private val appContext: Context,
     val screenSize: Point,
+    private val wallpaperManager: WallpaperManager,
     private val registry: ClockRegistry,
 ) {
     private val resources = appContext.resources
@@ -89,9 +91,21 @@
     }
 
     fun updateColor(clockId: String, @ColorInt seedColor: Int?) {
-        return (clockControllers[clockId] ?: initClockController(clockId))
-            .events
-            .onSeedColorChanged(seedColor)
+        clockControllers[clockId]?.events?.onSeedColorChanged(seedColor)
+    }
+
+    fun updateRegionDarkness() {
+        val isRegionDark = isLockscreenWallpaperDark()
+        clockControllers.values.forEach {
+            it.largeClock.events.onRegionDarknessChanged(isRegionDark)
+            it.smallClock.events.onRegionDarknessChanged(isRegionDark)
+        }
+    }
+
+    private fun isLockscreenWallpaperDark(): Boolean {
+        val colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK)
+        return (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) !=
+            WallpaperColors.HINT_SUPPORTS_DARK_TEXT
     }
 
     fun updateTimeFormat(clockId: String) {
@@ -136,18 +150,16 @@
             registry.createExampleClock(clockId).also { it?.initialize(resources, 0f, 0f) }
         checkNotNull(controller)
 
-        // Configure light/dark theme
-        val isLightTheme = TypedValue()
-        appContext.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
-        val isRegionDark = isLightTheme.data == 0
-        controller.largeClock.events.onRegionDarknessChanged(isRegionDark)
-        // Configure font size
+        val isWallpaperDark = isLockscreenWallpaperDark()
+        // Initialize large clock
+        controller.largeClock.events.onRegionDarknessChanged(isWallpaperDark)
         controller.largeClock.events.onFontSettingChanged(
             resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
         )
         controller.largeClock.events.onTargetRegionChanged(getLargeClockRegion())
 
-        controller.smallClock.events.onRegionDarknessChanged(isRegionDark)
+        // Initialize small clock
+        controller.smallClock.events.onRegionDarknessChanged(isWallpaperDark)
         controller.smallClock.events.onFontSettingChanged(
             resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
         )
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
index b0ff1db..a498c71 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModel.kt
@@ -103,7 +103,7 @@
             )
     }
 
-    fun onSliderProgressStop(progress: Int) {
+    suspend fun onSliderProgressStop(progress: Int) {
         val selectedColorId = selectedColorId.value ?: return
         val clockColorViewModel = colorMap[selectedColorId] ?: return
         clockPickerInteractor.setClockColor(
@@ -168,18 +168,20 @@
                                         null
                                     } else {
                                         {
-                                            clockPickerInteractor.setClockColor(
-                                                selectedColorId = colorModel.colorId,
-                                                colorToneProgress = colorToneProgress,
-                                                seedColor =
-                                                    blendColorWithTone(
-                                                        color = colorModel.color,
-                                                        colorTone =
-                                                            colorModel.getColorTone(
-                                                                colorToneProgress,
-                                                            ),
-                                                    ),
-                                            )
+                                            viewModelScope.launch {
+                                                clockPickerInteractor.setClockColor(
+                                                    selectedColorId = colorModel.colorId,
+                                                    colorToneProgress = colorToneProgress,
+                                                    seedColor =
+                                                        blendColorWithTone(
+                                                            color = colorModel.color,
+                                                            colorTone =
+                                                                colorModel.getColorTone(
+                                                                    colorToneProgress,
+                                                                ),
+                                                        ),
+                                                )
+                                            }
                                         }
                                     }
                                 },
@@ -235,11 +237,14 @@
                         null
                     } else {
                         {
-                            clockPickerInteractor.setClockColor(
-                                selectedColorId = null,
-                                colorToneProgress = ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
-                                seedColor = null,
-                            )
+                            viewModelScope.launch {
+                                clockPickerInteractor.setClockColor(
+                                    selectedColorId = null,
+                                    colorToneProgress =
+                                        ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
+                                    seedColor = null,
+                                )
+                            }
                         }
                     }
                 },
diff --git a/src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt b/src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt
index ac5ad81..bb9f082 100644
--- a/src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt
+++ b/src/com/android/customization/picker/color/ui/adapter/ColorTypeTabAdapter.kt
@@ -17,16 +17,13 @@
 
 package com.android.customization.picker.color.ui.adapter
 
-import android.graphics.Rect
 import android.view.LayoutInflater
-import android.view.TouchDelegate
 import android.view.View
 import android.view.ViewGroup
 import android.widget.TextView
 import androidx.recyclerview.widget.RecyclerView
 import com.android.customization.picker.color.ui.viewmodel.ColorTypeTabViewModel
 import com.android.wallpaper.R
-import com.android.wallpaper.util.ViewUtils.setupTouchDelegate
 
 /** Adapts between color type items and views. */
 class ColorTypeTabAdapter : RecyclerView.Adapter<ColorTypeTabAdapter.ViewHolder>() {
@@ -58,10 +55,6 @@
         val item = items[position]
         holder.itemView.isSelected = item.isSelected
         holder.textView.text = item.name
-        holder.textView.setupTouchDelegate(
-            parentView = holder.itemView,
-            heightRes = R.dimen.tab_touch_delegate_height_padding
-        )
         holder.textView.setOnClickListener(
             if (item.onClick != null) {
                 View.OnClickListener { item.onClick.invoke() }
diff --git a/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt b/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
index b34ea1b..5203ed3 100644
--- a/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
+++ b/src/com/android/customization/picker/quickaffordance/ui/adapter/SlotTabAdapter.kt
@@ -17,16 +17,13 @@
 
 package com.android.customization.picker.quickaffordance.ui.adapter
 
-import android.graphics.Rect
 import android.view.LayoutInflater
-import android.view.TouchDelegate
 import android.view.View
 import android.view.ViewGroup
 import android.widget.TextView
 import androidx.recyclerview.widget.RecyclerView
 import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordanceSlotViewModel
 import com.android.wallpaper.R
-import com.android.wallpaper.util.ViewUtils.setupTouchDelegate
 
 /** Adapts between lock screen quick affordance slot items and views. */
 class SlotTabAdapter : RecyclerView.Adapter<SlotTabAdapter.ViewHolder>() {
@@ -58,10 +55,6 @@
         val item = items[position]
         holder.itemView.isSelected = item.isSelected
         holder.textView.text = item.name
-        holder.textView.setupTouchDelegate(
-            parentView = holder.itemView,
-            heightRes = R.dimen.tab_touch_delegate_height_padding
-        )
         holder.textView.setOnClickListener(
             if (item.onClicked != null) {
                 View.OnClickListener { item.onClicked.invoke() }
diff --git a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
index 38bf25a..bf2766d 100644
--- a/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
+++ b/tests/src/com/android/customization/picker/clock/data/repository/FakeClockPickerRepository.kt
@@ -60,7 +60,7 @@
         selectedClockId.value = clockId
     }
 
-    override fun setClockColor(
+    override suspend fun setClockColor(
         selectedColorId: String?,
         @IntRange(from = 0, to = 100) colorToneProgress: Int,
         @ColorInt seedColor: Int?,
diff --git a/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt b/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
index cd41d7d..1a7ebb5 100644
--- a/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
+++ b/tests/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractorTest.kt
@@ -3,10 +3,12 @@
 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.wallpaper.testing.FakeSnapshotStore
 import com.android.wallpaper.testing.collectLastValue
 import com.google.common.truth.Truth
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.resetMain
 import kotlinx.coroutines.test.runTest
@@ -28,7 +30,15 @@
     fun setUp() {
         val testDispatcher = StandardTestDispatcher()
         Dispatchers.setMain(testDispatcher)
-        underTest = ClockPickerInteractor(FakeClockPickerRepository())
+        underTest =
+            ClockPickerInteractor(
+                repository = FakeClockPickerRepository(),
+                snapshotRestorer = {
+                    ClockPickerSnapshotRestorer(interactor = underTest).apply {
+                        runBlocking { setUpSnapshotRestorer(store = FakeSnapshotStore()) }
+                    }
+                },
+            )
     }
 
     @After
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
index d3e458f..c5eb796 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModelTest.kt
@@ -16,14 +16,18 @@
 package com.android.customization.picker.clock.ui.viewmodel
 
 import androidx.test.filters.SmallTest
+import com.android.customization.picker.clock.data.repository.ClockPickerRepository
 import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
 import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import com.android.wallpaper.testing.FakeSnapshotStore
 import com.android.wallpaper.testing.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.resetMain
@@ -55,6 +59,7 @@
     }
     private lateinit var testDispatcher: CoroutineDispatcher
     private lateinit var underTest: ClockCarouselViewModel
+    private lateinit var interactor: ClockPickerInteractor
 
     @Before
     fun setUp() {
@@ -71,12 +76,14 @@
     fun setSelectedClock() = runTest {
         underTest =
             ClockCarouselViewModel(
-                ClockPickerInteractor(repositoryWithMultipleClocks),
-                testDispatcher,
+                getClockPickerInteractor(repositoryWithMultipleClocks),
+                testDispatcher
             )
         val observedSelectedIndex = collectLastValue(underTest.selectedIndex)
         advanceTimeBy(ClockCarouselViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
         underTest.setSelectedClock(FakeClockPickerRepository.fakeClocks[2].clockId)
+
         assertThat(observedSelectedIndex()).isEqualTo(2)
     }
 
@@ -84,12 +91,14 @@
     fun multipleClockCase() = runTest {
         underTest =
             ClockCarouselViewModel(
-                ClockPickerInteractor(repositoryWithMultipleClocks),
-                testDispatcher,
+                getClockPickerInteractor(repositoryWithMultipleClocks),
+                testDispatcher
             )
         val observedIsCarouselVisible = collectLastValue(underTest.isCarouselVisible)
         val observedIsSingleClockViewVisible = collectLastValue(underTest.isSingleClockViewVisible)
+
         advanceTimeBy(ClockCarouselViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
         assertThat(observedIsCarouselVisible()).isTrue()
         assertThat(observedIsSingleClockViewVisible()).isFalse()
     }
@@ -98,13 +107,27 @@
     fun singleClockCase() = runTest {
         underTest =
             ClockCarouselViewModel(
-                ClockPickerInteractor(repositoryWithSingleClock),
-                testDispatcher,
+                getClockPickerInteractor(repositoryWithSingleClock),
+                testDispatcher
             )
         val observedIsCarouselVisible = collectLastValue(underTest.isCarouselVisible)
         val observedIsSingleClockViewVisible = collectLastValue(underTest.isSingleClockViewVisible)
+
         advanceTimeBy(ClockCarouselViewModel.CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
+
         assertThat(observedIsCarouselVisible()).isFalse()
         assertThat(observedIsSingleClockViewVisible()).isTrue()
     }
+
+    private fun getClockPickerInteractor(repository: ClockPickerRepository): ClockPickerInteractor {
+        return ClockPickerInteractor(
+                repository = repository,
+                snapshotRestorer = {
+                    ClockPickerSnapshotRestorer(interactor = interactor).apply {
+                        runBlocking { setUpSnapshotRestorer(store = FakeSnapshotStore()) }
+                    }
+                }
+            )
+            .also { interactor = it }
+    }
 }
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModelTest.kt
index 573777d..293e393 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModelTest.kt
@@ -19,12 +19,15 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
 import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import com.android.wallpaper.testing.FakeSnapshotStore
 import com.android.wallpaper.testing.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.resetMain
 import kotlinx.coroutines.test.runTest
@@ -50,7 +53,15 @@
         Dispatchers.setMain(testDispatcher)
         val context = InstrumentationRegistry.getInstrumentation().targetContext
         clockColorMap = ClockColorViewModel.getPresetColorMap(context.resources)
-        interactor = ClockPickerInteractor(FakeClockPickerRepository())
+        interactor =
+            ClockPickerInteractor(
+                repository = FakeClockPickerRepository(),
+                snapshotRestorer = {
+                    ClockPickerSnapshotRestorer(interactor = interactor).apply {
+                        runBlocking { setUpSnapshotRestorer(store = FakeSnapshotStore()) }
+                    }
+                },
+            )
         underTest =
             ClockSectionViewModel(
                 context,
@@ -68,6 +79,7 @@
         val colorGrey = clockColorMap.values.first()
         val observedSelectedClockColorAndSizeText =
             collectLastValue(underTest.selectedClockColorAndSizeText)
+
         interactor.setClockColor(
             colorGrey.colorId,
             ClockMetadataModel.DEFAULT_COLOR_TONE_PROGRESS,
@@ -77,6 +89,7 @@
             )
         )
         interactor.setClockSize(ClockSize.DYNAMIC)
+
         assertThat(observedSelectedClockColorAndSizeText()).isEqualTo("Grey, dynamic")
     }
 }
diff --git a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
index a329bb3..f58baf8 100644
--- a/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
+++ b/tests/src/com/android/customization/picker/clock/ui/viewmodel/ClockSettingsViewModelTest.kt
@@ -5,6 +5,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.customization.picker.clock.data.repository.FakeClockPickerRepository
 import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import com.android.customization.picker.clock.domain.interactor.ClockPickerSnapshotRestorer
 import com.android.customization.picker.clock.shared.ClockSize
 import com.android.customization.picker.clock.shared.model.ClockMetadataModel
 import com.android.customization.picker.color.data.repository.FakeColorPickerRepository
@@ -57,7 +58,15 @@
         Dispatchers.setMain(testDispatcher)
         context = InstrumentationRegistry.getInstrumentation().targetContext
         testScope = TestScope(testDispatcher)
-        clockPickerInteractor = ClockPickerInteractor(FakeClockPickerRepository())
+        clockPickerInteractor =
+            ClockPickerInteractor(
+                repository = FakeClockPickerRepository(),
+                snapshotRestorer = {
+                    ClockPickerSnapshotRestorer(interactor = clockPickerInteractor).apply {
+                        runBlocking { setUpSnapshotRestorer(store = FakeSnapshotStore()) }
+                    }
+                },
+            )
         colorPickerInteractor =
             ColorPickerInteractor(
                 repository = FakeColorPickerRepository(context = context),
@@ -160,7 +169,7 @@
         underTest.onSliderProgressChanged(targetProgress1)
         assertThat(observedSliderProgress()).isEqualTo(targetProgress1)
         val targetProgress2 = 55
-        underTest.onSliderProgressStop(targetProgress2)
+        testScope.launch { underTest.onSliderProgressStop(targetProgress2) }
         assertThat(observedSliderProgress()).isEqualTo(targetProgress2)
         val expectedSelectedColorModel = colorMap.values.first() // RED
         assertThat(observedSeedColor())