Enabled a11y for clock carousel view
This CL adds changes in order to announce correct a11y label when the
clock carousel view is in focus by the user in a11y mode.
Bug: b/278649728
Test: Tested by building wallpaper picker on local, turning on talkback
and checking if the announcement for clock carousel is made and custom
actions are available or not
Change-Id: Ibaaee3ac11f46f066e8ba531f414cb5201ad55e9
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 b7574a3..680947d 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -507,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/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 13c80c8..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 {
@@ -71,6 +89,8 @@
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 73d5508..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
@@ -60,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)
}
/**
@@ -69,6 +71,24 @@
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,
clocks: List<ClockCarouselItemViewModel>,
@@ -304,6 +324,10 @@
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 clocks.size
}
@@ -336,7 +360,7 @@
val isMiddleView = isMiddleView(viewRoot.id)
// Accessibility
- viewRoot.contentDescription = clocks[index].getContentDescription(view.resources)
+ viewRoot.contentDescription = getContentDescription(index, view.resources)
viewRoot.isSelected = isMiddleView
when (clockSize) {
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,