Merge "Keep wallpaper quick switch order stable (2/3)." into tm-qpr-dev
diff --git a/res/drawable/ic_clock_24px.xml b/res/drawable/ic_clock_24px.xml
index b3f1fee..946bc51 100644
--- a/res/drawable/ic_clock_24px.xml
+++ b/res/drawable/ic_clock_24px.xml
@@ -20,6 +20,6 @@
     android:viewportHeight="24"
     android:tint="?attr/colorControlNormal">
     <path
-        android:fillColor="@android:color/white"
+        android:fillColor="?android:textColorPrimary"
         android:pathData="M15.3,16.7 L16.7,15.3 13,11.6V7H11V12.4ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12ZM12,20Q15.325,20 17.663,17.663Q20,15.325 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,15.325 6.338,17.663Q8.675,20 12,20Z"/>
 </vector>
\ No newline at end of file
diff --git a/res/layout/clock_section_view.xml b/res/layout/clock_section_view.xml
index 8512498..4a651ca 100644
--- a/res/layout/clock_section_view.xml
+++ b/res/layout/clock_section_view.xml
@@ -33,10 +33,11 @@
         <TextView
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:text="@string/clock_title"
+            android:text="@string/clock_settings_title"
             style="@style/SectionTitleTextStyle" />
 
         <TextView
+            android:id="@+id/selected_clock_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/clock_description"
@@ -49,7 +50,5 @@
         android:scaleType="center"
         android:src="@drawable/ic_clock_24px"
         android:background="@drawable/option_border_color"
-        android:contentDescription="@string/clock_picker_entry_content_description"
-        android:tint="@color/text_color_primary" />
-
+        android:contentDescription="@string/clock_picker_entry_content_description" />
 </com.android.customization.picker.clock.ClockSectionView>
\ No newline at end of file
diff --git a/res/layout/fragment_clock_custom_picker_demo.xml b/res/layout/fragment_clock_custom_picker_demo.xml
new file mode 100644
index 0000000..c05c4a8
--- /dev/null
+++ b/res/layout/fragment_clock_custom_picker_demo.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout
+        android:id="@+id/section_header_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toTopOf="@+id/clock_preview_card_list_demo"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+        <include layout="@layout/section_header" />
+    </FrameLayout>
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/clock_preview_card_list_demo"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/section_header_container"
+        android:clipToPadding="false" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 13fc8a0..125bda1 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -33,6 +33,9 @@
     <!-- The content description of clock entry. [CHAR LIMIT=NONE] -->
     <string name="clock_picker_entry_content_description">Change a custom clock</string>
 
+    <!-- Title of a section of the customization picker where the user can configure Clock face. [CHAR LIMIT=15] -->
+    <string name="clock_settings_title">Clock Settings</string>
+
     <!-- Title of a section of the customization picker where the user can select a Grid size for
         the home screen. [CHAR LIMIT=15] -->
     <string name="grid_title">App grid</string>
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index 3cf8393..2772e56 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -21,6 +21,8 @@
 import com.android.customization.model.theme.ThemeBundleProvider
 import com.android.customization.model.theme.ThemeManager
 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.wallpaper.module.Injector
 
 interface CustomizationInjector : Injector {
@@ -36,4 +38,8 @@
     fun getKeyguardQuickAffordancePickerInteractor(
         context: Context
     ): KeyguardQuickAffordancePickerInteractor
+
+    fun getClockRegistry(context: Context): ClockRegistry
+
+    fun getPluginManager(context: Context): PluginManager
 }
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index ab6b673..5b43654 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -16,10 +16,15 @@
 package com.android.customization.module
 
 import android.app.Activity
+import android.app.NotificationManager
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
+import android.os.Handler
+import android.os.UserHandle
+import android.view.LayoutInflater
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentActivity
 import com.android.customization.model.theme.OverlayManagerCompat
@@ -29,8 +34,18 @@
 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer
 import com.android.customization.picker.quickaffordance.ui.viewmodel.KeyguardQuickAffordancePickerViewModel
+import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.clocks.DefaultClockProvider
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
+import com.android.systemui.shared.plugins.PluginActionManager
+import com.android.systemui.shared.plugins.PluginEnabler
+import com.android.systemui.shared.plugins.PluginInstance
+import com.android.systemui.shared.plugins.PluginManagerImpl
+import com.android.systemui.shared.plugins.PluginPrefs
+import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager_Factory
 import com.android.wallpaper.model.LiveWallpaperInfo
 import com.android.wallpaper.model.WallpaperInfo
 import com.android.wallpaper.module.CustomizationSections
@@ -43,6 +58,7 @@
 import com.android.wallpaper.picker.LivePreviewFragment
 import com.android.wallpaper.picker.PreviewFragment
 import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
+import java.util.concurrent.Executors
 import kotlinx.coroutines.Dispatchers.IO
 
 open class ThemePickerInjector : WallpaperPicker2Injector(), CustomizationInjector {
@@ -58,6 +74,8 @@
     private var fragmentFactory: FragmentFactory? = null
     private var keyguardQuickAffordanceSnapshotRestorer: KeyguardQuickAffordanceSnapshotRestorer? =
         null
+    private var clockRegistry: ClockRegistry? = null
+    private var pluginManager: PluginManager? = null
 
     override fun getCustomizationSections(activity: Activity): CustomizationSections {
         return customizationSections
@@ -191,6 +209,80 @@
                 .also { keyguardQuickAffordanceSnapshotRestorer = it }
     }
 
+    override fun getClockRegistry(context: Context): ClockRegistry {
+        return clockRegistry
+            ?: ClockRegistry(
+                    context,
+                    getPluginManager(context),
+                    Handler.getMain(),
+                    isEnabled = true,
+                    userHandle = UserHandle.USER_SYSTEM,
+                    DefaultClockProvider(context, LayoutInflater.from(context), context.resources)
+                )
+                .also { clockRegistry = it }
+    }
+
+    override fun getPluginManager(context: Context): PluginManager {
+        return pluginManager ?: createPluginManager(context).also { pluginManager = it }
+    }
+
+    private fun createPluginManager(context: Context): PluginManager {
+        val privilegedPlugins = listOf<String>()
+        val isDebugDevice = true
+
+        val instanceFactory =
+            PluginInstance.Factory(
+                this::class.java.classLoader,
+                PluginInstance.InstanceFactory<Plugin>(),
+                PluginInstance.VersionChecker(),
+                privilegedPlugins,
+                isDebugDevice,
+            )
+
+        /*
+         * let SystemUI handle plugin, in this class assume plugins are enabled
+         */
+        val pluginEnabler =
+            object : PluginEnabler {
+                override fun setEnabled(component: ComponentName) = Unit
+
+                override fun setDisabled(
+                    component: ComponentName,
+                    @PluginEnabler.DisableReason reason: Int
+                ) = Unit
+
+                override fun isEnabled(component: ComponentName): Boolean {
+                    return true
+                }
+
+                @PluginEnabler.DisableReason
+                override fun getDisableReason(componentName: ComponentName): Int {
+                    return PluginEnabler.ENABLED
+                }
+            }
+
+        val pluginActionManager =
+            PluginActionManager.Factory(
+                context,
+                context.packageManager,
+                context.mainExecutor,
+                Executors.newSingleThreadExecutor(),
+                context.getSystemService(NotificationManager::class.java),
+                pluginEnabler,
+                privilegedPlugins,
+                instanceFactory,
+            )
+        return PluginManagerImpl(
+            context,
+            pluginActionManager,
+            isDebugDevice,
+            UncaughtExceptionPreHandlerManager_Factory.create().get(),
+            pluginEnabler,
+            PluginPrefs(context),
+            listOf(),
+        )
+    }
+
     companion object {
         @JvmStatic
         private val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER =
diff --git a/src/com/android/customization/picker/clock/ClockCustomDemoFragment.kt b/src/com/android/customization/picker/clock/ClockCustomDemoFragment.kt
index 8648dca..4f65080 100644
--- a/src/com/android/customization/picker/clock/ClockCustomDemoFragment.kt
+++ b/src/com/android/customization/picker/clock/ClockCustomDemoFragment.kt
@@ -1,11 +1,8 @@
 package com.android.customization.picker.clock
 
-import android.app.NotificationManager
-import android.content.ComponentName
 import android.content.Context
 import android.os.Bundle
-import android.os.Handler
-import android.os.UserHandle
+import android.util.TypedValue
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.view.View
@@ -14,36 +11,23 @@
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
 import android.widget.TextView
+import android.widget.Toast
 import androidx.core.view.setPadding
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.android.customization.module.ThemePickerInjector
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProviderPlugin
-import com.android.systemui.plugins.Plugin
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.shared.clocks.DefaultClockProvider
-import com.android.systemui.shared.plugins.PluginActionManager
-import com.android.systemui.shared.plugins.PluginEnabler
-import com.android.systemui.shared.plugins.PluginEnabler.ENABLED
-import com.android.systemui.shared.plugins.PluginInstance
-import com.android.systemui.shared.plugins.PluginManagerImpl
-import com.android.systemui.shared.plugins.PluginPrefs
-import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager_Factory
 import com.android.wallpaper.R
+import com.android.wallpaper.module.InjectorProvider
 import com.android.wallpaper.picker.AppbarFragment
-import java.util.concurrent.Executors
-
-private val TAG = ClockCustomDemoFragment::class.simpleName
 
 class ClockCustomDemoFragment : AppbarFragment() {
     @VisibleForTesting lateinit var clockRegistry: ClockRegistry
-    val isDebugDevice = true
-    val privilegedPlugins = listOf<String>()
-    val action = ClockProviderPlugin.ACTION
-    lateinit var view: ViewGroup
     @VisibleForTesting lateinit var recyclerView: RecyclerView
     lateinit var pluginManager: PluginManager
     @VisibleForTesting
@@ -51,24 +35,19 @@
         object : PluginListener<ClockProviderPlugin> {
             override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
                 val listInUse = clockRegistry.getClocks().filter { "NOT_IN_USE" !in it.clockId }
-                recyclerView.adapter = ClockRecyclerAdapter(listInUse, context, clockRegistry)
+                recyclerView.adapter =
+                    ClockRecyclerAdapter(listInUse, context) {
+                        clockRegistry.currentClockId = it.clockId
+                        Toast.makeText(context, "${it.name} selected", Toast.LENGTH_SHORT).show()
+                    }
             }
         }
 
     override fun onAttach(context: Context) {
         super.onAttach(context)
-        val defaultClockProvider =
-            DefaultClockProvider(context, LayoutInflater.from(context), context.resources)
-        pluginManager = createPluginManager(context)
-        clockRegistry =
-            ClockRegistry(
-                context,
-                pluginManager,
-                Handler.getMain(),
-                isEnabled = true,
-                userHandle = UserHandle.USER_OWNER,
-                defaultClockProvider
-            )
+        val injector = InjectorProvider.getInjector() as ThemePickerInjector
+        pluginManager = injector.getPluginManager(context)
+        clockRegistry = injector.getClockRegistry(context)
         pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java, true)
     }
 
@@ -92,69 +71,10 @@
         return getString(R.string.clock_title)
     }
 
-    private fun createPluginManager(context: Context): PluginManager {
-        val instanceFactory =
-            PluginInstance.Factory(
-                this::class.java.classLoader,
-                PluginInstance.InstanceFactory<Plugin>(),
-                PluginInstance.VersionChecker(),
-                privilegedPlugins,
-                isDebugDevice
-            )
-
-        /*
-         * let SystemUI handle plugin, in this class assume plugins are enabled
-         */
-        val pluginEnabler =
-            object : PluginEnabler {
-                override fun setEnabled(component: ComponentName) {}
-
-                override fun setDisabled(
-                    component: ComponentName,
-                    @PluginEnabler.DisableReason reason: Int
-                ) {}
-
-                override fun isEnabled(component: ComponentName): Boolean {
-                    return true
-                }
-
-                @PluginEnabler.DisableReason
-                override fun getDisableReason(componentName: ComponentName): Int {
-                    return ENABLED
-                }
-            }
-
-        val pluginActionManager =
-            PluginActionManager.Factory(
-                context,
-                context.packageManager,
-                context.mainExecutor,
-                Executors.newSingleThreadExecutor(),
-                context.getSystemService(NotificationManager::class.java),
-                pluginEnabler,
-                privilegedPlugins,
-                instanceFactory
-            )
-        return PluginManagerImpl(
-            context,
-            pluginActionManager,
-            isDebugDevice,
-            uncaughtExceptionPreHandlerManager,
-            pluginEnabler,
-            PluginPrefs(context),
-            listOf()
-        )
-    }
-
-    companion object {
-        private val uncaughtExceptionPreHandlerManager =
-            UncaughtExceptionPreHandlerManager_Factory.create().get()
-    }
-
     internal class ClockRecyclerAdapter(
         val list: List<ClockMetadata>,
         val context: Context,
-        val clockRegistry: ClockRegistry
+        val onClockSelected: (ClockMetadata) -> Unit
     ) : RecyclerView.Adapter<ClockRecyclerAdapter.ViewHolder>() {
         class ViewHolder(val view: View, val textView: TextView, val onItemClicked: (Int) -> Unit) :
             RecyclerView.ViewHolder(view) {
@@ -169,13 +89,12 @@
                 TextView(ContextThemeWrapper(viewGroup.context, R.style.SectionTitleTextStyle))
             textView.setPadding(ITEM_PADDING)
             rootView.addView(textView)
+            val outValue = TypedValue()
+            context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
+            rootView.setBackgroundResource(outValue.resourceId)
             val lp = RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
-            rootView.setLayoutParams(lp)
-            return ViewHolder(
-                rootView,
-                textView,
-                { clockRegistry.currentClockId = list[it].clockId }
-            )
+            rootView.layoutParams = lp
+            return ViewHolder(rootView, textView) { onClockSelected(list[it]) }
         }
 
         override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
diff --git a/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
new file mode 100644
index 0000000..52c2430
--- /dev/null
+++ b/src/com/android/customization/picker/clock/data/repository/ClockPickerRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.data.repository
+
+import android.util.Log
+import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.shared.clocks.ClockRegistry
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+
+/**
+ * Repository for accessing application clock settings, as well as selecting and configuring custom
+ * clocks.
+ */
+class ClockPickerRepository(registry: ClockRegistry) {
+
+    /** The currently-selected clock. */
+    val selectedClock: Flow<ClockMetadataModel?> = callbackFlow {
+        fun send() {
+            val model =
+                registry
+                    .getClocks()
+                    .find { clockMetadata -> clockMetadata.clockId == registry.currentClockId }
+                    ?.toModel()
+            if (model == null) {
+                Log.e(TAG, "Currently selected clock ID is not one of the available clocks.")
+            }
+            trySend(model)
+        }
+
+        val listener = ClockRegistry.ClockChangeListener { send() }
+        registry.registerClockChangeListener(listener)
+        send()
+        awaitClose { registry.unregisterClockChangeListener(listener) }
+    }
+
+    private fun ClockMetadata.toModel(): ClockMetadataModel {
+        return ClockMetadataModel(clockId = clockId, name = name)
+    }
+
+    companion object {
+        private const val TAG = "ClockPickerRepository"
+    }
+}
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.kt
new file mode 100644
index 0000000..63b3638
--- /dev/null
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClockPickerInteractor.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.domain.interactor
+
+import com.android.customization.picker.clock.data.repository.ClockPickerRepository
+import com.android.customization.picker.clock.shared.model.ClockMetadataModel
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor for accessing application clock settings, as well as selecting and configuring custom
+ * clocks.
+ */
+class ClockPickerInteractor(private val repository: ClockPickerRepository) {
+
+    val selectedClock: Flow<ClockMetadataModel?> = repository.selectedClock
+}
diff --git a/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt b/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt
index 63b4a9b..904d2f9 100644
--- a/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt
+++ b/src/com/android/customization/picker/clock/domain/interactor/ClocksSnapshotRestorer.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
diff --git a/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
new file mode 100644
index 0000000..ea84d2b
--- /dev/null
+++ b/src/com/android/customization/picker/clock/shared/model/ClockMetadataModel.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+/** Model for clock metadata. */
+data class ClockMetadataModel(
+    val clockId: String,
+    val name: String,
+)
diff --git a/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt b/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt
new file mode 100644
index 0000000..5a3286d
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockSectionViewBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.ui.binder
+
+import android.view.View
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
+import com.android.wallpaper.R
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+object ClockSectionViewBinder {
+    fun bind(
+        view: View,
+        viewModel: ClockSectionViewModel,
+        lifecycleOwner: LifecycleOwner,
+        onClicked: () -> Unit,
+    ) {
+        view.setOnClickListener { onClicked() }
+
+        val selectedClockTextView: TextView = view.requireViewById(R.id.selected_clock_text)
+
+        lifecycleOwner.lifecycleScope.launch {
+            viewModel.selectedClockName
+                .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
+                .collectLatest { selectedClockName ->
+                    selectedClockTextView.text = selectedClockName
+                }
+        }
+    }
+}
diff --git a/src/com/android/customization/model/clock/ClockSectionController.kt b/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt
similarity index 78%
rename from src/com/android/customization/model/clock/ClockSectionController.kt
rename to src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt
index 1e339bb..848f226 100644
--- a/src/com/android/customization/model/clock/ClockSectionController.kt
+++ b/src/com/android/customization/picker/clock/ui/section/ClockSectionController.kt
@@ -13,12 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.customization.model.clock
+package com.android.customization.picker.clock.ui.section
 
 import android.content.Context
 import android.view.LayoutInflater
+import androidx.lifecycle.LifecycleOwner
 import com.android.customization.picker.clock.ClockCustomDemoFragment
 import com.android.customization.picker.clock.ClockSectionView
+import com.android.customization.picker.clock.ui.binder.ClockSectionViewBinder
+import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
 import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.wallpaper.R
@@ -30,6 +33,8 @@
 class ClockSectionController(
     private val navigationController: CustomizationSectionNavigationController,
     private val customizationProviderClient: CustomizationProviderClient,
+    private val viewModel: ClockSectionViewModel,
+    private val lifecycleOwner: LifecycleOwner,
 ) : CustomizationSectionController<ClockSectionView?> {
     override fun isAvailable(context: Context?): Boolean {
         return runBlocking { customizationProviderClient.queryFlags() }
@@ -44,7 +49,11 @@
                     R.layout.clock_section_view,
                     null,
                 ) as ClockSectionView
-        view.setOnClickListener { navigationController.navigateTo(ClockCustomDemoFragment()) }
+        ClockSectionViewBinder.bind(
+            view = view,
+            viewModel = viewModel,
+            lifecycleOwner = lifecycleOwner
+        ) { navigationController.navigateTo(ClockCustomDemoFragment()) }
         return view
     }
 }
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt
new file mode 100644
index 0000000..f50bb9c
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockSectionViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.customization.picker.clock.domain.interactor.ClockPickerInteractor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View model for the clock section view on the lockscreen customization surface. */
+class ClockSectionViewModel(interactor: ClockPickerInteractor) {
+
+    val selectedClockName: Flow<String?> = interactor.selectedClock.map { it?.name }
+}
diff --git a/tests/src/com/android/customization/testing/TestCustomizationInjector.java b/tests/src/com/android/customization/testing/TestCustomizationInjector.java
deleted file mode 100644
index d609335..0000000
--- a/tests/src/com/android/customization/testing/TestCustomizationInjector.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.android.customization.testing;
-
-import android.content.Context;
-
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.customization.model.theme.OverlayManagerCompat;
-import com.android.customization.model.theme.ThemeBundleProvider;
-import com.android.customization.model.theme.ThemeManager;
-import com.android.customization.module.CustomizationInjector;
-import com.android.customization.module.CustomizationPreferences;
-import com.android.customization.module.ThemesUserEventLogger;
-import com.android.customization.picker.quickaffordance.data.repository.KeyguardQuickAffordancePickerRepository;
-import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor;
-import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer;
-import com.android.systemui.shared.customization.data.content.CustomizationProviderClient;
-import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl;
-import com.android.wallpaper.config.BaseFlags;
-import com.android.wallpaper.module.DrawableLayerResolver;
-import com.android.wallpaper.module.PackageStatusNotifier;
-import com.android.wallpaper.module.UserEventLogger;
-import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer;
-import com.android.wallpaper.testing.TestInjector;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import kotlinx.coroutines.Dispatchers;
-
-/**
- * Test implementation of the dependency injector.
- */
-public class TestCustomizationInjector extends TestInjector implements CustomizationInjector {
-    private CustomizationPreferences mCustomizationPreferences;
-    private ThemeManager mThemeManager;
-    private PackageStatusNotifier mPackageStatusNotifier;
-    private DrawableLayerResolver mDrawableLayerResolver;
-    private UserEventLogger mUserEventLogger;
-    private KeyguardQuickAffordancePickerInteractor mKeyguardQuickAffordancePickerInteractor;
-    private BaseFlags mFlags;
-    private CustomizationProviderClient mCustomizationProviderClient;
-    private KeyguardQuickAffordanceSnapshotRestorer mKeyguardQuickAffordanceSnapshotRestorer;
-
-    @Override
-    public CustomizationPreferences getCustomizationPreferences(Context context) {
-        if (mCustomizationPreferences == null) {
-            mCustomizationPreferences = new TestDefaultCustomizationPreferences(context);
-        }
-        return mCustomizationPreferences;
-    }
-
-    @Override
-    public ThemeManager getThemeManager(
-            ThemeBundleProvider provider,
-            FragmentActivity activity,
-            OverlayManagerCompat overlayManagerCompat,
-            ThemesUserEventLogger logger) {
-        if (mThemeManager == null) {
-            mThemeManager = new TestThemeManager(provider, activity, overlayManagerCompat, logger);
-        }
-        return mThemeManager;
-    }
-
-    @Override
-    public PackageStatusNotifier getPackageStatusNotifier(Context context) {
-        if (mPackageStatusNotifier == null) {
-            mPackageStatusNotifier =  new TestPackageStatusNotifier();
-        }
-        return mPackageStatusNotifier;
-    }
-
-    @Override
-    public DrawableLayerResolver getDrawableLayerResolver() {
-        if (mDrawableLayerResolver == null) {
-            mDrawableLayerResolver = new TestDrawableLayerResolver();
-        }
-        return mDrawableLayerResolver;
-    }
-
-    @Override
-    public UserEventLogger getUserEventLogger(Context unused) {
-        if (mUserEventLogger == null) {
-            mUserEventLogger = new TestThemesUserEventLogger();
-        }
-        return mUserEventLogger;
-    }
-
-    @Override
-    public KeyguardQuickAffordancePickerInteractor getKeyguardQuickAffordancePickerInteractor(
-            Context context) {
-        if (mKeyguardQuickAffordancePickerInteractor == null) {
-            final CustomizationProviderClient client =
-                    new CustomizationProviderClientImpl(context, Dispatchers.getIO());
-            mKeyguardQuickAffordancePickerInteractor = new KeyguardQuickAffordancePickerInteractor(
-                    new KeyguardQuickAffordancePickerRepository(client, Dispatchers.getIO()),
-                    client,
-                    () -> getKeyguardQuickAffordanceSnapshotRestorer(context));
-        }
-        return mKeyguardQuickAffordancePickerInteractor;
-    }
-
-    @Override
-    public BaseFlags getFlags() {
-        if (mFlags == null) {
-            mFlags = new BaseFlags() {};
-        }
-
-        return mFlags;
-    }
-
-    @Override
-    public Map<Integer, SnapshotRestorer> getSnapshotRestorers(Context context) {
-        final Map<Integer, SnapshotRestorer> restorers = new HashMap<>();
-        restorers.put(
-                KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER,
-                getKeyguardQuickAffordanceSnapshotRestorer(context));
-        return restorers;
-    }
-
-    /** Returns the {@link CustomizationProviderClient}. */
-    private CustomizationProviderClient getKeyguardQuickAffordancePickerProviderClient(
-            Context context) {
-        if (mCustomizationProviderClient == null) {
-            mCustomizationProviderClient =
-                    new CustomizationProviderClientImpl(context, Dispatchers.getIO());
-        }
-
-        return mCustomizationProviderClient;
-    }
-
-    private KeyguardQuickAffordanceSnapshotRestorer getKeyguardQuickAffordanceSnapshotRestorer(
-            Context context) {
-        if (mKeyguardQuickAffordanceSnapshotRestorer == null) {
-            mKeyguardQuickAffordanceSnapshotRestorer = new KeyguardQuickAffordanceSnapshotRestorer(
-                    getKeyguardQuickAffordancePickerInteractor(context),
-                    getKeyguardQuickAffordancePickerProviderClient(context));
-        }
-
-        return mKeyguardQuickAffordanceSnapshotRestorer;
-    }
-
-    private static final int KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER = 1;
-}
diff --git a/tests/src/com/android/customization/testing/TestCustomizationInjector.kt b/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
new file mode 100644
index 0000000..afc7131
--- /dev/null
+++ b/tests/src/com/android/customization/testing/TestCustomizationInjector.kt
@@ -0,0 +1,151 @@
+package com.android.customization.testing
+
+import android.content.Context
+import android.os.Handler
+import android.os.UserHandle
+import android.view.LayoutInflater
+import androidx.fragment.app.FragmentActivity
+import com.android.customization.model.theme.OverlayManagerCompat
+import com.android.customization.model.theme.ThemeBundleProvider
+import com.android.customization.model.theme.ThemeManager
+import com.android.customization.module.CustomizationInjector
+import com.android.customization.module.CustomizationPreferences
+import com.android.customization.module.ThemesUserEventLogger
+import com.android.customization.picker.quickaffordance.data.repository.KeyguardQuickAffordancePickerRepository
+import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
+import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordanceSnapshotRestorer
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.clocks.DefaultClockProvider
+import com.android.systemui.shared.customization.data.content.CustomizationProviderClient
+import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl
+import com.android.wallpaper.config.BaseFlags
+import com.android.wallpaper.module.DrawableLayerResolver
+import com.android.wallpaper.module.PackageStatusNotifier
+import com.android.wallpaper.module.UserEventLogger
+import com.android.wallpaper.picker.undo.domain.interactor.SnapshotRestorer
+import com.android.wallpaper.testing.TestInjector
+import java.util.HashMap
+import kotlinx.coroutines.Dispatchers.IO
+
+/** Test implementation of the dependency injector. */
+class TestCustomizationInjector : TestInjector(), CustomizationInjector {
+    private var customizationPreferences: CustomizationPreferences? = null
+    private var themeManager: ThemeManager? = null
+    private var packageStatusNotifier: PackageStatusNotifier? = null
+    private var drawableLayerResolver: DrawableLayerResolver? = null
+    private var userEventLogger: UserEventLogger? = null
+    private var keyguardQuickAffordancePickerInteractor: KeyguardQuickAffordancePickerInteractor? =
+        null
+    private var flags: BaseFlags? = null
+    private var customizationProviderClient: CustomizationProviderClient? = null
+    private var keyguardQuickAffordanceSnapshotRestorer: KeyguardQuickAffordanceSnapshotRestorer? =
+        null
+    private var clockRegistry: ClockRegistry? = null
+    private var pluginManager: PluginManager? = null
+
+    override fun getCustomizationPreferences(context: Context): CustomizationPreferences {
+        return customizationPreferences
+            ?: TestDefaultCustomizationPreferences(context).also { customizationPreferences = it }
+    }
+
+    override fun getThemeManager(
+        provider: ThemeBundleProvider,
+        activity: FragmentActivity,
+        overlayManagerCompat: OverlayManagerCompat,
+        logger: ThemesUserEventLogger
+    ): ThemeManager {
+        return themeManager
+            ?: TestThemeManager(provider, activity, overlayManagerCompat, logger).also {
+                themeManager = it
+            }
+    }
+
+    override fun getPackageStatusNotifier(context: Context): PackageStatusNotifier {
+        return packageStatusNotifier
+            ?: TestPackageStatusNotifier().also {
+                packageStatusNotifier = TestPackageStatusNotifier()
+            }
+    }
+
+    override fun getDrawableLayerResolver(): DrawableLayerResolver {
+        return drawableLayerResolver
+            ?: TestDrawableLayerResolver().also { drawableLayerResolver = it }
+    }
+
+    override fun getUserEventLogger(context: Context): UserEventLogger {
+        return userEventLogger ?: TestThemesUserEventLogger().also { userEventLogger = it }
+    }
+
+    override fun getKeyguardQuickAffordancePickerInteractor(
+        context: Context
+    ): KeyguardQuickAffordancePickerInteractor {
+        return keyguardQuickAffordancePickerInteractor
+            ?: createCustomizationProviderClient(context).also {
+                keyguardQuickAffordancePickerInteractor = it
+            }
+    }
+
+    private fun createCustomizationProviderClient(
+        context: Context
+    ): KeyguardQuickAffordancePickerInteractor {
+        val client: CustomizationProviderClient = CustomizationProviderClientImpl(context, IO)
+        return KeyguardQuickAffordancePickerInteractor(
+            KeyguardQuickAffordancePickerRepository(client, IO),
+            client
+        ) { getKeyguardQuickAffordanceSnapshotRestorer(context) }
+    }
+
+    override fun getFlags(): BaseFlags {
+        return flags ?: object : BaseFlags() {}.also { flags = it }
+    }
+
+    override fun getSnapshotRestorers(context: Context): Map<Int, SnapshotRestorer> {
+        val restorers: MutableMap<Int, SnapshotRestorer> = HashMap()
+        restorers[KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER] =
+            getKeyguardQuickAffordanceSnapshotRestorer(context)
+        return restorers
+    }
+
+    /** Returns the [CustomizationProviderClient]. */
+    private fun getKeyguardQuickAffordancePickerProviderClient(
+        context: Context
+    ): CustomizationProviderClient {
+        return customizationProviderClient
+            ?: CustomizationProviderClientImpl(context, IO).also {
+                customizationProviderClient = it
+            }
+    }
+
+    private fun getKeyguardQuickAffordanceSnapshotRestorer(
+        context: Context
+    ): KeyguardQuickAffordanceSnapshotRestorer {
+        return keyguardQuickAffordanceSnapshotRestorer
+            ?: KeyguardQuickAffordanceSnapshotRestorer(
+                    getKeyguardQuickAffordancePickerInteractor(context),
+                    getKeyguardQuickAffordancePickerProviderClient(context)
+                )
+                .also { keyguardQuickAffordanceSnapshotRestorer = it }
+    }
+
+    override fun getClockRegistry(context: Context): ClockRegistry {
+        return clockRegistry
+            ?: ClockRegistry(
+                    context,
+                    getPluginManager(context),
+                    Handler.getMain(),
+                    isEnabled = true,
+                    userHandle = UserHandle.USER_SYSTEM,
+                    DefaultClockProvider(context, LayoutInflater.from(context), context.resources)
+                )
+                .also { clockRegistry = it }
+    }
+
+    override fun getPluginManager(context: Context): PluginManager {
+        return pluginManager ?: TestPluginManager().also { pluginManager = it }
+    }
+
+    companion object {
+        private const val KEY_QUICK_AFFORDANCE_SNAPSHOT_RESTORER = 1
+    }
+}
diff --git a/tests/src/com/android/customization/testing/TestPluginManager.kt b/tests/src/com/android/customization/testing/TestPluginManager.kt
new file mode 100644
index 0000000..167d8dd
--- /dev/null
+++ b/tests/src/com/android/customization/testing/TestPluginManager.kt
@@ -0,0 +1,36 @@
+package com.android.customization.testing
+
+import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.plugins.PluginManager
+
+class TestPluginManager : PluginManager {
+    override fun getPrivilegedPlugins(): Array<String> {
+        return emptyArray()
+    }
+
+    override fun <T : Plugin?> addPluginListener(listener: PluginListener<T>, cls: Class<T>) {}
+    override fun <T : Plugin?> addPluginListener(
+        listener: PluginListener<T>,
+        cls: Class<T>,
+        allowMultiple: Boolean
+    ) {}
+
+    override fun <T : Plugin?> addPluginListener(
+        action: String,
+        listener: PluginListener<T>,
+        cls: Class<T>
+    ) {}
+
+    override fun <T : Plugin?> addPluginListener(
+        action: String,
+        listener: PluginListener<T>,
+        cls: Class<T>,
+        allowMultiple: Boolean
+    ) {}
+
+    override fun removePluginListener(listener: PluginListener<*>?) {}
+    override fun <T> dependsOn(p: Plugin, cls: Class<T>): Boolean {
+        return false
+    }
+}