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

Commit 82174d6e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "a11y for controls management" into rvc-dev am: 4d504ec8

Change-Id: Ibc5637857697a01e5b99a6426b99caacf60af3bb
parents fe589526 4d504ec8
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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] -->
+71 −4
Original line number Diff line number Diff line
@@ -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
+12 −1
Original line number Diff line number Diff line
@@ -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)