Merge UP1A.230905.019

Merged-In: Iadbdec303acd4fcb5e244ffa2292cc4c9112253d
Change-Id: I78aac8befea151dacf5ee0925dbea55432506152
diff --git a/res/layout/clock_carousel.xml b/res/layout/clock_carousel.xml
index a43e804..e93a697 100644
--- a/res/layout/clock_carousel.xml
+++ b/res/layout/clock_carousel.xml
@@ -43,7 +43,8 @@
             android:layout_width="@dimen/screen_preview_width"
             android:layout_height="wrap_content"
             android:layout_gravity="center"
-            android:clipChildren="false">
+            android:clipChildren="false"
+            android:importantForAccessibility="noHideDescendants">
             <com.android.customization.picker.clock.ui.view.ClockHostView
                 android:id="@+id/clock_host_view_0"
                 android:layout_width="match_parent"
@@ -74,7 +75,8 @@
             android:layout_width="@dimen/screen_preview_width"
             android:layout_height="wrap_content"
             android:layout_gravity="center"
-            android:clipChildren="false">
+            android:clipChildren="false"
+            android:importantForAccessibility="noHideDescendants">
             <com.android.customization.picker.clock.ui.view.ClockHostView
                 android:id="@+id/clock_host_view_1"
                 android:layout_width="match_parent"
@@ -107,7 +109,8 @@
             android:layout_width="@dimen/screen_preview_width"
             android:layout_height="wrap_content"
             android:layout_gravity="center"
-            android:clipChildren="false">
+            android:clipChildren="false"
+            android:importantForAccessibility="noHideDescendants">
             <com.android.customization.picker.clock.ui.view.ClockHostView
                 android:id="@+id/clock_host_view_2"
                 android:layout_width="match_parent"
@@ -138,7 +141,8 @@
             android:layout_width="@dimen/screen_preview_width"
             android:layout_height="wrap_content"
             android:layout_gravity="center"
-            android:clipChildren="false">
+            android:clipChildren="false"
+            android:importantForAccessibility="noHideDescendants">
             <com.android.customization.picker.clock.ui.view.ClockHostView
                 android:id="@+id/clock_host_view_3"
                 android:layout_width="match_parent"
@@ -169,7 +173,8 @@
             android:layout_width="@dimen/screen_preview_width"
             android:layout_height="wrap_content"
             android:layout_gravity="center"
-            android:clipChildren="false">
+            android:clipChildren="false"
+            android:importantForAccessibility="noHideDescendants">
             <com.android.customization.picker.clock.ui.view.ClockHostView
                 android:id="@+id/clock_host_view_4"
                 android:layout_width="match_parent"
diff --git a/res/values/config.xml b/res/values/config.xml
index d992bf3..ad81e67 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -19,4 +19,8 @@
     <item type="id" name="option_tile" />
     <!-- ID for the label of an option tile -->
     <item type="id" name="option_label" />
+
+    <!-- ID for the a11y actions on carousel -->
+    <item type="id" name="action_scroll_forward" />
+    <item type="id" name="action_scroll_backward" />
 </resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 40142d1..680947d 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>
 
+    <!-- action description for announcing selected Clock [CHAR LIMIT=NONE]-->
+    <string name="select_clock_action_description">Clock face option <xliff:g name="clock_face_description">%1$s</xliff:g></string>
+
     <!-- Title of a section of the customization picker where the user can configure Clock face. [CHAR LIMIT=19] -->
     <string name="clock_settings_title">Clock color &amp; size</string>
 
@@ -504,4 +507,22 @@
     [CHAR LIMIT=NONE].
     -->
     <string name="content_description_color_option">Color option <xliff:g name="color_number" example="1">%1$d</xliff:g></string>
+
+    <!--
+    Accessibility label for forward scrolling in the carousel of clock faces.
+    [CHAR LIMIT=128].
+    -->
+    <string name="scroll_forward_and_select">Swipe left to choose a different clock face</string>
+
+    <!--
+    Accessibility label for backward scrolling in the carousel of clock faces.
+    [CHAR LIMIT=128].
+    -->
+    <string name="scroll_backward_and_select">Swipe right to choose a different clock face</string>
+
+    <!--
+    Accessibility label for the carousel of clock faces.
+    [CHAR LIMIT=128].
+    -->
+    <string name="custom_clocks_label">Custom Clocks</string>
 </resources>
diff --git a/src/com/android/customization/module/CustomizationInjector.kt b/src/com/android/customization/module/CustomizationInjector.kt
index 8b0d90f..5f8f9d3 100644
--- a/src/com/android/customization/module/CustomizationInjector.kt
+++ b/src/com/android/customization/module/CustomizationInjector.kt
@@ -26,6 +26,7 @@
 import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
 import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
 import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
+import com.android.customization.picker.clock.utils.ClockDescriptionUtils
 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
 import com.android.customization.picker.color.ui.viewmodel.ColorPickerViewModel
 import com.android.customization.picker.quickaffordance.domain.interactor.KeyguardQuickAffordancePickerInteractor
@@ -76,4 +77,6 @@
         wallpaperColorsViewModel: WallpaperColorsViewModel,
         clockViewFactory: ClockViewFactory,
     ): ClockSettingsViewModel.Factory
+
+    fun getClockDescriptionUtils(): ClockDescriptionUtils
 }
diff --git a/src/com/android/customization/module/ThemePickerInjector.kt b/src/com/android/customization/module/ThemePickerInjector.kt
index d00ed28..cceb896 100644
--- a/src/com/android/customization/module/ThemePickerInjector.kt
+++ b/src/com/android/customization/module/ThemePickerInjector.kt
@@ -51,6 +51,8 @@
 import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
 import com.android.customization.picker.clock.ui.viewmodel.ClockSectionViewModel
 import com.android.customization.picker.clock.ui.viewmodel.ClockSettingsViewModel
+import com.android.customization.picker.clock.utils.ClockDescriptionUtils
+import com.android.customization.picker.clock.utils.ThemePickerClockDescriptionUtils
 import com.android.customization.picker.color.data.repository.ColorPickerRepositoryImpl
 import com.android.customization.picker.color.domain.interactor.ColorPickerInteractor
 import com.android.customization.picker.color.domain.interactor.ColorPickerSnapshotRestorer
@@ -115,6 +117,7 @@
     private var themedIconSnapshotRestorer: ThemedIconSnapshotRestorer? = null
     private var themedIconInteractor: ThemedIconInteractor? = null
     private var clockSettingsViewModelFactory: ClockSettingsViewModel.Factory? = null
+    private var clockDescriptionUtils: ClockDescriptionUtils? = null
     private var gridInteractor: GridInteractor? = null
     private var gridSnapshotRestorer: GridSnapshotRestorer? = null
     private var gridScreenViewModelFactory: GridScreenViewModel.Factory? = null
@@ -534,6 +537,11 @@
                 .also { clockSettingsViewModelFactory = it }
     }
 
+    override fun getClockDescriptionUtils(): ClockDescriptionUtils {
+        return clockDescriptionUtils
+            ?: ThemePickerClockDescriptionUtils().also { clockDescriptionUtils = it }
+    }
+
     fun getGridScreenViewModelFactory(
         context: Context,
     ): ViewModelProvider.Factory {
diff --git a/src/com/android/customization/picker/clock/ui/binder/CarouselAccessibilityDelegate.kt b/src/com/android/customization/picker/clock/ui/binder/CarouselAccessibilityDelegate.kt
new file mode 100644
index 0000000..5e3c26e
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/binder/CarouselAccessibilityDelegate.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.content.Context
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import com.android.wallpaper.R
+
+class CarouselAccessibilityDelegate(
+    private val context: Context,
+    private val scrollForwardCallback: () -> Unit,
+    private val scrollBackwardCallback: () -> Unit
+) : View.AccessibilityDelegate() {
+
+    var contentDescriptionOfSelectedClock = ""
+
+    private val ACTION_SCROLL_BACKWARD = R.id.action_scroll_backward
+    private val ACTION_SCROLL_FORWARD = R.id.action_scroll_forward
+
+    override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) {
+        super.onInitializeAccessibilityNodeInfo(host, info)
+        info?.isScrollable = true
+        info?.addAction(
+            AccessibilityNodeInfo.AccessibilityAction(
+                ACTION_SCROLL_FORWARD,
+                context.getString(R.string.scroll_forward_and_select)
+            )
+        )
+        info?.addAction(
+            AccessibilityNodeInfo.AccessibilityAction(
+                ACTION_SCROLL_BACKWARD,
+                context.getString(R.string.scroll_backward_and_select)
+            )
+        )
+        info?.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS)
+        // We need to specifically set the content description since for some reason the talkback
+        // service does not go to children of the clock carousel in the view hierarchy
+        info?.contentDescription = contentDescriptionOfSelectedClock
+    }
+
+    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+        when (action) {
+            ACTION_SCROLL_BACKWARD -> {
+                scrollBackwardCallback.invoke()
+                return true
+            }
+            ACTION_SCROLL_FORWARD -> {
+                scrollForwardCallback.invoke()
+                return true
+            }
+        }
+        return super.performAccessibilityAction(host, action, args)
+    }
+}
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 8f964cf..89fac89 100644
--- a/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
+++ b/src/com/android/customization/picker/clock/ui/binder/ClockCarouselViewBinder.kt
@@ -15,6 +15,7 @@
  */
 package com.android.customization.picker.clock.ui.binder
 
+import android.content.Context
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.core.view.isVisible
@@ -27,6 +28,7 @@
 import com.android.customization.picker.clock.ui.view.ClockViewFactory
 import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselViewModel
 import com.android.wallpaper.R
+import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewClickView
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
@@ -34,8 +36,10 @@
 
     @JvmStatic
     fun bind(
+        context: Context,
         carouselView: ClockCarouselView,
         singleClockView: ViewGroup,
+        screenPreviewClickView: ScreenPreviewClickView,
         viewModel: ClockCarouselViewModel,
         clockViewFactory: ClockViewFactory,
         lifecycleOwner: LifecycleOwner,
@@ -43,6 +47,20 @@
     ) {
         carouselView.setClockViewFactory(clockViewFactory)
         clockViewFactory.updateRegionDarkness()
+        val carouselAccessibilityDelegate =
+            CarouselAccessibilityDelegate(
+                context,
+                scrollForwardCallback = {
+                    // Callback code for scrolling forward
+                    carouselView.transitionToNext()
+                },
+                scrollBackwardCallback = {
+                    // Callback code for scrolling backward
+                    carouselView.transitionToPrevious()
+                }
+            )
+        screenPreviewClickView.accessibilityDelegate = carouselAccessibilityDelegate
+
         val singleClockHostView =
             singleClockView.requireViewById<FrameLayout>(R.id.single_clock_host_view)
         lifecycleOwner.lifecycleScope.launch {
@@ -50,25 +68,29 @@
                 launch { viewModel.isCarouselVisible.collect { carouselView.isVisible = it } }
 
                 launch {
-                    combine(viewModel.selectedClockSize, viewModel.allClockIds, ::Pair).collect {
-                        (size, allClockIds) ->
+                    combine(viewModel.selectedClockSize, viewModel.allClocks, ::Pair).collect {
+                        (size, allClocks) ->
                         carouselView.setUpClockCarouselView(
                             clockSize = size,
-                            clockIds = allClockIds,
-                            onClockSelected = { clockId -> viewModel.setSelectedClock(clockId) },
+                            clocks = allClocks,
+                            onClockSelected = { clock ->
+                                viewModel.setSelectedClock(clock.clockId)
+                            },
                             isTwoPaneAndSmallWidth = isTwoPaneAndSmallWidth,
                         )
                     }
                 }
 
                 launch {
-                    viewModel.allClockIds.collect {
-                        it.forEach { clockId -> clockViewFactory.updateTimeFormat(clockId) }
+                    viewModel.allClocks.collect {
+                        it.forEach { clock -> clockViewFactory.updateTimeFormat(clock.clockId) }
                     }
                 }
 
                 launch {
                     viewModel.selectedIndex.collect { selectedIndex ->
+                        carouselAccessibilityDelegate.contentDescriptionOfSelectedClock =
+                            carouselView.getContentDescription(selectedIndex)
                         carouselView.setSelectedClockIndex(selectedIndex)
                     }
                 }
diff --git a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
index 56d4dea..8764e54 100644
--- a/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
+++ b/src/com/android/customization/picker/clock/ui/view/ClockCarouselView.kt
@@ -16,6 +16,7 @@
 package com.android.customization.picker.clock.ui.view
 
 import android.content.Context
+import android.content.res.Resources
 import android.util.AttributeSet
 import android.view.LayoutInflater
 import android.view.View
@@ -28,6 +29,7 @@
 import androidx.core.view.get
 import androidx.core.view.isNotEmpty
 import com.android.customization.picker.clock.shared.ClockSize
+import com.android.customization.picker.clock.ui.viewmodel.ClockCarouselItemViewModel
 import com.android.systemui.plugins.ClockController
 import com.android.wallpaper.R
 import com.android.wallpaper.picker.FixedWidthDisplayRatioFrameLayout
@@ -59,6 +61,7 @@
         val clockCarousel = LayoutInflater.from(context).inflate(R.layout.clock_carousel, this)
         carousel = clockCarousel.requireViewById(R.id.carousel)
         motionLayout = clockCarousel.requireViewById(R.id.motion_container)
+        motionLayout.contentDescription = context.getString(R.string.custom_clocks_label)
     }
 
     /**
@@ -68,17 +71,35 @@
         clockViewFactory = factory
     }
 
+    fun transitionToNext() {
+        val index = (carousel.currentIndex + 1) % carousel.count
+        if (index < carousel.count && index > 0) {
+            carousel.transitionToIndex(index, 0)
+        }
+    }
+
+    fun transitionToPrevious() {
+        val index = (carousel.currentIndex - 1) % carousel.count
+        if (index < carousel.count && index > 0) {
+            carousel.transitionToIndex(index, 0)
+        }
+    }
+
+    fun getContentDescription(index: Int): String {
+        return adapter.getContentDescription(index, resources)
+    }
+
     fun setUpClockCarouselView(
         clockSize: ClockSize,
-        clockIds: List<String>,
-        onClockSelected: (clockId: String) -> Unit,
+        clocks: List<ClockCarouselItemViewModel>,
+        onClockSelected: (clock: ClockCarouselItemViewModel) -> Unit,
         isTwoPaneAndSmallWidth: Boolean,
     ) {
         if (isTwoPaneAndSmallWidth) {
             overrideScreenPreviewWidth()
         }
 
-        adapter = ClockCarouselAdapter(clockSize, clockIds, clockViewFactory, onClockSelected)
+        adapter = ClockCarouselAdapter(clockSize, clocks, clockViewFactory, onClockSelected)
         carousel.setAdapter(adapter)
         carousel.refresh()
         motionLayout.setTransitionListener(
@@ -118,11 +139,11 @@
                 }
 
                 private fun prepareDynamicClockView(motionLayout: MotionLayout, endId: Int) {
-                    val scalingDownClockId = adapter.clockIds[carousel.currentIndex]
+                    val scalingDownClockId = adapter.clocks[carousel.currentIndex].clockId
                     val scalingUpIdx =
                         if (endId == R.id.next) (carousel.currentIndex + 1) % adapter.count()
                         else (carousel.currentIndex - 1 + adapter.count()) % adapter.count()
-                    val scalingUpClockId = adapter.clockIds[scalingUpIdx]
+                    val scalingUpClockId = adapter.clocks[scalingUpIdx].clockId
                     offCenterClockController = clockViewFactory.getController(scalingDownClockId)
                     toCenterClockController = clockViewFactory.getController(scalingUpClockId)
                     offCenterClockScaleView = motionLayout.findViewById(R.id.clock_scale_view_2)
@@ -298,13 +319,17 @@
 
     private class ClockCarouselAdapter(
         val clockSize: ClockSize,
-        val clockIds: List<String>,
+        val clocks: List<ClockCarouselItemViewModel>,
         private val clockViewFactory: ClockViewFactory,
-        private val onClockSelected: (clockId: String) -> Unit
+        private val onClockSelected: (clock: ClockCarouselItemViewModel) -> Unit
     ) : Carousel.Adapter {
 
+        fun getContentDescription(index: Int, resources: Resources): String {
+            return clocks[index].getContentDescription(resources)
+        }
+
         override fun count(): Int {
-            return clockIds.size
+            return clocks.size
         }
 
         override fun populate(view: View?, index: Int) {
@@ -318,7 +343,7 @@
             val clockHostView =
                 getClockHostViewId(viewRoot.id)?.let { viewRoot.findViewById(it) as? ClockHostView }
                     ?: return
-            val clockId = clockIds[index]
+            val clockId = clocks[index].clockId
 
             // Add the clock view to the cloc host view
             clockHostView.removeAllViews()
@@ -333,6 +358,11 @@
             clockHostView.addView(clockView)
 
             val isMiddleView = isMiddleView(viewRoot.id)
+
+            // Accessibility
+            viewRoot.contentDescription = getContentDescription(index, view.resources)
+            viewRoot.isSelected = isMiddleView
+
             when (clockSize) {
                 ClockSize.DYNAMIC ->
                     initializeDynamicClockView(
@@ -411,7 +441,7 @@
         }
 
         override fun onNewItem(index: Int) {
-            onClockSelected.invoke(clockIds[index])
+            onClockSelected.invoke(clocks[index])
         }
     }
 
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt
new file mode 100644
index 0000000..708fa2f
--- /dev/null
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselItemViewModel.kt
@@ -0,0 +1,35 @@
+/*
+ * 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 android.content.res.Resources
+import com.android.customization.module.CustomizationInjector
+import com.android.wallpaper.R
+import com.android.wallpaper.module.InjectorProvider
+
+class ClockCarouselItemViewModel(val clockId: String) {
+
+    /** Description for accessibility purposes when a clock is selected. */
+    fun getContentDescription(resources: Resources): String {
+        val clockContent =
+            (InjectorProvider.getInjector() as? CustomizationInjector)
+                ?.getClockDescriptionUtils()
+                ?.getDescriptionResId(clockId)
+                ?.let { resources.getString(it) }
+                ?: ""
+        return resources.getString(R.string.select_clock_action_description, clockContent)
+    }
+}
diff --git a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
index 781b48c..a4f9cc4 100644
--- a/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
+++ b/src/com/android/customization/picker/clock/ui/viewmodel/ClockCarouselViewModel.kt
@@ -45,12 +45,12 @@
     private val backgroundDispatcher: CoroutineDispatcher,
 ) : ViewModel() {
     @OptIn(ExperimentalCoroutinesApi::class)
-    val allClockIds: StateFlow<List<String>> =
+    val allClocks: StateFlow<List<ClockCarouselItemViewModel>> =
         interactor.allClocks
             .mapLatest { allClocks ->
                 // Delay to avoid the case that the full list of clocks is not initiated.
                 delay(CLOCKS_EVENT_UPDATE_DELAY_MILLIS)
-                allClocks.map { it.clockId }
+                allClocks.map { ClockCarouselItemViewModel(it.clockId) }
             }
             .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
 
@@ -58,14 +58,14 @@
 
     val seedColor: Flow<Int?> = interactor.seedColor
 
-    val isCarouselVisible: Flow<Boolean> = allClockIds.map { it.size > 1 }.distinctUntilChanged()
+    val isCarouselVisible: Flow<Boolean> = allClocks.map { it.size > 1 }.distinctUntilChanged()
 
     @OptIn(ExperimentalCoroutinesApi::class)
     val selectedIndex: Flow<Int> =
-        allClockIds
+        allClocks
             .flatMapLatest { allClockIds ->
                 interactor.selectedClockId.map { selectedClockId ->
-                    val index = allClockIds.indexOf(selectedClockId)
+                    val index = allClockIds.indexOfFirst { it.clockId == selectedClockId }
                     /** Making sure there is no active [setSelectedClockJob] */
                     val isSetClockIdJobActive = setSelectedClockJob?.isActive == true
                     if (index >= 0 && !isSetClockIdJobActive) {
@@ -79,11 +79,11 @@
 
     // Handle the case when there is only one clock in the carousel
     val isSingleClockViewVisible: Flow<Boolean> =
-        allClockIds.map { it.size == 1 }.distinctUntilChanged()
+        allClocks.map { it.size == 1 }.distinctUntilChanged()
 
     val clockId: Flow<String> =
-        allClockIds
-            .map { allClockIds -> if (allClockIds.size == 1) allClockIds[0] else null }
+        allClocks
+            .map { allClockIds -> if (allClockIds.size == 1) allClockIds[0].clockId else null }
             .mapNotNull { it }
 
     private var setSelectedClockJob: Job? = null
diff --git a/src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.kt b/src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.kt
new file mode 100644
index 0000000..9a0b66f
--- /dev/null
+++ b/src/com/android/customization/picker/clock/utils/ClockDescriptionUtils.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.utils
+
+import androidx.annotation.StringRes
+
+/** Provides clock description for accessibility purposes. */
+interface ClockDescriptionUtils {
+
+    /**
+     * TODO (b/287507746) : Migrate description res ID to system UI or a shared library, instead of
+     * preserving the clock description at the Wallpaper Picker side.
+     */
+    @StringRes fun getDescriptionResId(clockId: String): Int
+}
diff --git a/src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt b/src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt
new file mode 100644
index 0000000..43f19b3
--- /dev/null
+++ b/src/com/android/customization/picker/clock/utils/ThemePickerClockDescriptionUtils.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.utils
+
+import androidx.annotation.StringRes
+import com.android.wallpaper.R
+
+class ThemePickerClockDescriptionUtils : ClockDescriptionUtils {
+    @StringRes
+    override fun getDescriptionResId(clockId: String): Int {
+        return R.string.clock_title
+    }
+}
diff --git a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
index 4322009..8e91798 100644
--- a/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
+++ b/src/com/android/customization/picker/preview/ui/section/PreviewWithClockCarouselSectionController.kt
@@ -46,6 +46,7 @@
 import com.android.wallpaper.module.CurrentWallpaperInfoFactory
 import com.android.wallpaper.module.CustomizationSections
 import com.android.wallpaper.picker.customization.domain.interactor.WallpaperInteractor
+import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewClickView
 import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewSectionController
 import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewView
 import com.android.wallpaper.util.DisplayUtils
@@ -100,6 +101,8 @@
     override fun createView(context: Context): ScreenPreviewView {
         val view = super.createView(context)
         if (screen == CustomizationSections.Screen.LOCK_SCREEN) {
+            val screenPreviewClickView: ScreenPreviewClickView =
+                view.findViewById(R.id.screen_preview_click_view)
             val clockColorAndSizeButtonStub: ViewStub =
                 view.requireViewById(R.id.clock_color_and_size_button)
             clockColorAndSizeButtonStub.layoutResource = R.layout.clock_color_and_size_button
@@ -162,8 +165,10 @@
                         bindJob =
                             lifecycleOwner.lifecycleScope.launch {
                                 ClockCarouselViewBinder.bind(
+                                    context = context,
                                     carouselView = carouselView,
                                     singleClockView = singleClockView,
+                                    screenPreviewClickView = screenPreviewClickView,
                                     viewModel = viewModel,
                                     clockViewFactory = clockViewFactory,
                                     lifecycleOwner = lifecycleOwner,