Merge "a11y for controls management" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4d504ec83a
@@ -2698,6 +2698,20 @@
|
||||
<item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> controls added.</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Removed control in management screen [CHAR LIMIT=20] -->
|
||||
<string name="controls_removed">Removed</string>
|
||||
|
||||
<!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_control_favorite">Favorited</string>
|
||||
<!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_control_favorite_position">Favorited, position <xliff:g id="number" example="1">%d</xliff:g></string>
|
||||
<!-- a11y state description for a control that is currently not favorited [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_control_not_favorite">Unfavorited</string>
|
||||
<!-- a11y action to favorite a control. It will read as "Double-tap to favorite" in screen readers [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_control_change_favorite">favorite</string>
|
||||
<!-- a11y action to unfavorite a control. It will read as "Double-tap to unfavorite" in screen readers [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_control_change_unfavorite">unfavorite</string>
|
||||
|
||||
<!-- Controls management controls screen default title [CHAR LIMIT=30] -->
|
||||
<string name="controls_favorite_default_title">Controls</string>
|
||||
<!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
|
||||
|
||||
@@ -23,9 +23,14 @@ import android.service.controls.DeviceTypes
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import android.widget.CheckBox
|
||||
import android.widget.ImageView
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.AccessibilityDelegateCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.systemui.R
|
||||
@@ -72,7 +77,8 @@ class ControlAdapter(
|
||||
elevation = this@ControlAdapter.elevation
|
||||
background = parent.context.getDrawable(
|
||||
R.drawable.control_background_ripple)
|
||||
}
|
||||
},
|
||||
model is FavoritesModel // Indicates that position information is needed
|
||||
) { id, favorite ->
|
||||
model?.changeFavoriteStatus(id, favorite)
|
||||
}
|
||||
@@ -175,8 +181,14 @@ private class ZoneHolder(view: View) : Holder(view) {
|
||||
*/
|
||||
internal class ControlHolder(
|
||||
view: View,
|
||||
val withPosition: Boolean,
|
||||
val favoriteCallback: ModelFavoriteChanger
|
||||
) : Holder(view) {
|
||||
private val favoriteStateDescription =
|
||||
itemView.context.getString(R.string.accessibility_control_favorite)
|
||||
private val notFavoriteStateDescription =
|
||||
itemView.context.getString(R.string.accessibility_control_not_favorite)
|
||||
|
||||
private val icon: ImageView = itemView.requireViewById(R.id.icon)
|
||||
private val title: TextView = itemView.requireViewById(R.id.title)
|
||||
private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
|
||||
@@ -185,15 +197,38 @@ internal class ControlHolder(
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private val accessibilityDelegate = ControlHolderAccessibilityDelegate(this::stateDescription)
|
||||
|
||||
init {
|
||||
ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate)
|
||||
}
|
||||
|
||||
// Determine the stateDescription based on favorite state and maybe position
|
||||
private fun stateDescription(favorite: Boolean): CharSequence? {
|
||||
if (!favorite) {
|
||||
return notFavoriteStateDescription
|
||||
} else if (!withPosition) {
|
||||
return favoriteStateDescription
|
||||
} else {
|
||||
val position = layoutPosition + 1
|
||||
return itemView.context.getString(
|
||||
R.string.accessibility_control_favorite_position, position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bindData(wrapper: ElementWrapper) {
|
||||
wrapper as ControlInterface
|
||||
val renderInfo = getRenderInfo(wrapper.component, wrapper.deviceType)
|
||||
title.text = wrapper.title
|
||||
subtitle.text = wrapper.subtitle
|
||||
favorite.isChecked = wrapper.favorite
|
||||
removed.text = if (wrapper.removed) "Removed" else ""
|
||||
updateFavorite(wrapper.favorite)
|
||||
removed.text = if (wrapper.removed) {
|
||||
itemView.context.getText(R.string.controls_removed)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
itemView.setOnClickListener {
|
||||
favorite.isChecked = !favorite.isChecked
|
||||
updateFavorite(!favorite.isChecked)
|
||||
favoriteCallback(wrapper.controlId, favorite.isChecked)
|
||||
}
|
||||
applyRenderInfo(renderInfo)
|
||||
@@ -201,6 +236,8 @@ internal class ControlHolder(
|
||||
|
||||
override fun updateFavorite(favorite: Boolean) {
|
||||
this.favorite.isChecked = favorite
|
||||
accessibilityDelegate.isFavorite = favorite
|
||||
itemView.stateDescription = stateDescription(favorite)
|
||||
}
|
||||
|
||||
private fun getRenderInfo(
|
||||
@@ -219,6 +256,36 @@ internal class ControlHolder(
|
||||
}
|
||||
}
|
||||
|
||||
private class ControlHolderAccessibilityDelegate(
|
||||
val stateRetriever: (Boolean) -> CharSequence?
|
||||
) : AccessibilityDelegateCompat() {
|
||||
|
||||
var isFavorite = false
|
||||
|
||||
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
|
||||
// Change the text for the double-tap action
|
||||
val clickActionString = if (isFavorite) {
|
||||
host.context.getString(R.string.accessibility_control_change_unfavorite)
|
||||
} else {
|
||||
host.context.getString(R.string.accessibility_control_change_favorite)
|
||||
}
|
||||
val click = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
|
||||
AccessibilityNodeInfo.ACTION_CLICK,
|
||||
// “favorite/unfavorite”
|
||||
clickActionString)
|
||||
info.addAction(click)
|
||||
|
||||
// Determine the stateDescription based on the holder information
|
||||
info.stateDescription = stateRetriever(isFavorite)
|
||||
// Remove the information at the end indicating row and column.
|
||||
info.setCollectionItemInfo(null)
|
||||
|
||||
info.className = Switch::class.java.name
|
||||
}
|
||||
}
|
||||
|
||||
class MarginItemDecorator(
|
||||
private val topMargin: Int,
|
||||
private val sideMargins: Int
|
||||
|
||||
@@ -191,7 +191,18 @@ class ControlsEditingActivity @Inject constructor(
|
||||
|
||||
recyclerView.apply {
|
||||
this.adapter = adapter
|
||||
layoutManager = GridLayoutManager(recyclerView.context, 2).apply {
|
||||
layoutManager = object : GridLayoutManager(recyclerView.context, 2) {
|
||||
|
||||
// This will remove from the announcement the row corresponding to the divider,
|
||||
// as it's not something that should be announced.
|
||||
override fun getRowCountForAccessibility(
|
||||
recycler: RecyclerView.Recycler,
|
||||
state: RecyclerView.State
|
||||
): Int {
|
||||
val initial = super.getRowCountForAccessibility(recycler, state)
|
||||
return if (initial > 0) initial - 1 else initial
|
||||
}
|
||||
}.apply {
|
||||
spanSizeLookup = adapter.spanSizeLookup
|
||||
}
|
||||
addItemDecoration(itemDecorator)
|
||||
|
||||
Reference in New Issue
Block a user