Merge "a11y for controls rearranging" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
dfc677f707
@@ -685,6 +685,7 @@
|
||||
</activity>
|
||||
|
||||
<activity android:name=".controls.management.ControlsEditingActivity"
|
||||
android:label="@string/controls_menu_edit"
|
||||
android:theme="@style/Theme.ControlsManagement"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true"
|
||||
|
||||
@@ -169,5 +169,8 @@
|
||||
<item type="id" name="screen_recording_options" />
|
||||
<item type="id" name="screen_recording_dialog_source_text" />
|
||||
<item type="id" name="screen_recording_dialog_source_description" />
|
||||
|
||||
<item type="id" name="accessibility_action_controls_move_before" />
|
||||
<item type="id" name="accessibility_action_controls_move_after" />
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -2711,6 +2711,8 @@
|
||||
<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>
|
||||
<!-- a11y action to move a control to the position specified by the parameter [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_control_move">Move to position <xliff:g id="number" example="1">%d</xliff:g></string>
|
||||
|
||||
<!-- Controls management controls screen default title [CHAR LIMIT=30] -->
|
||||
<string name="controls_favorite_default_title">Controls</string>
|
||||
|
||||
@@ -48,6 +48,8 @@ class AllModel(
|
||||
|
||||
private var modified = false
|
||||
|
||||
override val moveHelper = null
|
||||
|
||||
override val favorites: List<ControlInfo>
|
||||
get() = favoriteIds.mapNotNull { id ->
|
||||
val control = controls.firstOrNull { it.control.controlId == id }?.control
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.systemui.controls.management
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.DeviceTypes
|
||||
import android.view.LayoutInflater
|
||||
@@ -78,7 +79,7 @@ class ControlAdapter(
|
||||
background = parent.context.getDrawable(
|
||||
R.drawable.control_background_ripple)
|
||||
},
|
||||
model is FavoritesModel // Indicates that position information is needed
|
||||
model?.moveHelper // Indicates that position information is needed
|
||||
) { id, favorite ->
|
||||
model?.changeFavoriteStatus(id, favorite)
|
||||
}
|
||||
@@ -176,12 +177,14 @@ private class ZoneHolder(view: View) : Holder(view) {
|
||||
|
||||
/**
|
||||
* Holder for using with [ControlStatusWrapper] to display names of zones.
|
||||
* @param moveHelper a helper interface to facilitate a11y rearranging. Null indicates no
|
||||
* rearranging
|
||||
* @param favoriteCallback this callback will be called whenever the favorite state of the
|
||||
* [Control] this view represents changes.
|
||||
*/
|
||||
internal class ControlHolder(
|
||||
view: View,
|
||||
val withPosition: Boolean,
|
||||
val moveHelper: ControlsModel.MoveHelper?,
|
||||
val favoriteCallback: ModelFavoriteChanger
|
||||
) : Holder(view) {
|
||||
private val favoriteStateDescription =
|
||||
@@ -197,7 +200,11 @@ internal class ControlHolder(
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private val accessibilityDelegate = ControlHolderAccessibilityDelegate(this::stateDescription)
|
||||
private val accessibilityDelegate = ControlHolderAccessibilityDelegate(
|
||||
this::stateDescription,
|
||||
this::getLayoutPosition,
|
||||
moveHelper
|
||||
)
|
||||
|
||||
init {
|
||||
ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate)
|
||||
@@ -207,7 +214,7 @@ internal class ControlHolder(
|
||||
private fun stateDescription(favorite: Boolean): CharSequence? {
|
||||
if (!favorite) {
|
||||
return notFavoriteStateDescription
|
||||
} else if (!withPosition) {
|
||||
} else if (moveHelper == null) {
|
||||
return favoriteStateDescription
|
||||
} else {
|
||||
val position = layoutPosition + 1
|
||||
@@ -256,15 +263,67 @@ internal class ControlHolder(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessibility delegate for [ControlHolder].
|
||||
*
|
||||
* Provides the following functionality:
|
||||
* * Sets the state description indicating whether the controls is Favorited or Unfavorited
|
||||
* * Adds the position to the state description if necessary.
|
||||
* * Adds context action for moving (rearranging) a control.
|
||||
*
|
||||
* @param stateRetriever function to determine the state description based on the favorite state
|
||||
* @param positionRetriever function to obtain the position of this control. It only has to be
|
||||
* correct in controls that are currently favorites (and therefore can
|
||||
* be moved).
|
||||
* @param moveHelper helper interface to determine if a control can be moved and actually move it.
|
||||
*/
|
||||
private class ControlHolderAccessibilityDelegate(
|
||||
val stateRetriever: (Boolean) -> CharSequence?
|
||||
val stateRetriever: (Boolean) -> CharSequence?,
|
||||
val positionRetriever: () -> Int,
|
||||
val moveHelper: ControlsModel.MoveHelper?
|
||||
) : AccessibilityDelegateCompat() {
|
||||
|
||||
var isFavorite = false
|
||||
|
||||
companion object {
|
||||
private val MOVE_BEFORE_ID = R.id.accessibility_action_controls_move_before
|
||||
private val MOVE_AFTER_ID = R.id.accessibility_action_controls_move_after
|
||||
}
|
||||
|
||||
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
|
||||
info.isContextClickable = false
|
||||
addClickAction(host, info)
|
||||
maybeAddMoveBeforeAction(host, info)
|
||||
maybeAddMoveAfterAction(host, info)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
|
||||
if (super.performAccessibilityAction(host, action, args)) {
|
||||
return true
|
||||
}
|
||||
return when (action) {
|
||||
MOVE_BEFORE_ID -> {
|
||||
moveHelper?.moveBefore(positionRetriever())
|
||||
true
|
||||
}
|
||||
MOVE_AFTER_ID -> {
|
||||
moveHelper?.moveAfter(positionRetriever())
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun addClickAction(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
// Change the text for the double-tap action
|
||||
val clickActionString = if (isFavorite) {
|
||||
host.context.getString(R.string.accessibility_control_change_unfavorite)
|
||||
@@ -276,13 +335,30 @@ private class ControlHolderAccessibilityDelegate(
|
||||
// “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)
|
||||
private fun maybeAddMoveBeforeAction(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
if (moveHelper?.canMoveBefore(positionRetriever()) ?: false) {
|
||||
val newPosition = positionRetriever() + 1 - 1
|
||||
val moveBefore = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
|
||||
MOVE_BEFORE_ID,
|
||||
host.context.getString(R.string.accessibility_control_move, newPosition)
|
||||
)
|
||||
info.addAction(moveBefore)
|
||||
info.isContextClickable = true
|
||||
}
|
||||
}
|
||||
|
||||
info.className = Switch::class.java.name
|
||||
private fun maybeAddMoveAfterAction(host: View, info: AccessibilityNodeInfoCompat) {
|
||||
if (moveHelper?.canMoveAfter(positionRetriever()) ?: false) {
|
||||
val newPosition = positionRetriever() + 1 + 1
|
||||
val moveAfter = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
|
||||
MOVE_AFTER_ID,
|
||||
host.context.getString(R.string.accessibility_control_move, newPosition)
|
||||
)
|
||||
info.addAction(moveAfter)
|
||||
info.isContextClickable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ interface ControlsModel {
|
||||
*/
|
||||
val elements: List<ElementWrapper>
|
||||
|
||||
val moveHelper: MoveHelper?
|
||||
|
||||
/**
|
||||
* Change the favorite status of a particular control.
|
||||
*/
|
||||
@@ -69,6 +71,34 @@ interface ControlsModel {
|
||||
*/
|
||||
fun onFirstChange()
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to facilitate moving controls from an [AccessibilityDelegate].
|
||||
*
|
||||
* All positions should be 0 based.
|
||||
*/
|
||||
interface MoveHelper {
|
||||
|
||||
/**
|
||||
* Whether the control in `position` can be moved to the position before it.
|
||||
*/
|
||||
fun canMoveBefore(position: Int): Boolean
|
||||
|
||||
/**
|
||||
* Whether the control in `position` can be moved to the position after it.
|
||||
*/
|
||||
fun canMoveAfter(position: Int): Boolean
|
||||
|
||||
/**
|
||||
* Move the control in `position` to the position before it.
|
||||
*/
|
||||
fun moveBefore(position: Int)
|
||||
|
||||
/**
|
||||
* Move the control in `position` to the position after it.
|
||||
*/
|
||||
fun moveAfter(position: Int)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.controls.management
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.util.Log
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.systemui.controls.ControlInterface
|
||||
@@ -39,9 +40,39 @@ class FavoritesModel(
|
||||
private val favoritesModelCallback: FavoritesModelCallback
|
||||
) : ControlsModel {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FavoritesModel"
|
||||
}
|
||||
|
||||
private var adapter: RecyclerView.Adapter<*>? = null
|
||||
private var modified = false
|
||||
|
||||
override val moveHelper = object : ControlsModel.MoveHelper {
|
||||
override fun canMoveBefore(position: Int): Boolean {
|
||||
return position > 0 && position < dividerPosition
|
||||
}
|
||||
|
||||
override fun canMoveAfter(position: Int): Boolean {
|
||||
return position >= 0 && position < dividerPosition - 1
|
||||
}
|
||||
|
||||
override fun moveBefore(position: Int) {
|
||||
if (!canMoveBefore(position)) {
|
||||
Log.w(TAG, "Cannot move position $position before")
|
||||
} else {
|
||||
onMoveItem(position, position - 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun moveAfter(position: Int) {
|
||||
if (!canMoveAfter(position)) {
|
||||
Log.w(TAG, "Cannot move position $position after")
|
||||
} else {
|
||||
onMoveItem(position, position + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachAdapter(adapter: RecyclerView.Adapter<*>) {
|
||||
this.adapter = adapter
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user