diff options
| author | 2020-05-18 13:13:54 +0000 | |
|---|---|---|
| committer | 2020-05-18 13:13:54 +0000 | |
| commit | dfc677f70776fec98bf1184d8ac1cb067a79f516 (patch) | |
| tree | 413e691fc49b11e264eafbfddf90de7edf80c7f5 | |
| parent | 28e8a2c0ca7bbb75e7d7755b6aa8a54169090cb7 (diff) | |
| parent | 93a6975a9eab50ed52349f39301d4140e1417cc7 (diff) | |
Merge "a11y for controls rearranging" into rvc-dev
7 files changed, 155 insertions, 10 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index e670f1f83a52..4510b87556c7 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -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" diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 76ca385bd9d9..09918e764140 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -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> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8c10f61db7a0..8bbcfa0e7898 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt index 175ed061c714..00a406e4dbc0 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 4b283d607bb8..2f917107cc23 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -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 + } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt index 37b6d15c0afe..254395368bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt @@ -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) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt index 411170cb322c..524250134e9b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt @@ -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 } |