Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 93a6975a authored by Fabian Kozynski's avatar Fabian Kozynski
Browse files

a11y for controls rearranging

This CL adds local context actions for rearranging controls. The actions
allow to move each current favorites one position at a time.

Test: manual
Fixes: 155997794

Change-Id: I3dbace5c11bb93830315ca57513744464595cc16
parent a6019674
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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"
+3 −0
Original line number Diff line number Diff line
@@ -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>
+2 −0
Original line number Diff line number Diff line
@@ -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>
+2 −0
Original line number Diff line number Diff line
@@ -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
+86 −10
Original line number Diff line number Diff line
@@ -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
        }
    }
}

Loading