Loading packages/SystemUI/res/values/strings.xml +14 −0 Original line number Diff line number Diff line Loading @@ -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] --> Loading packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +71 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } Loading Loading @@ -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) Loading @@ -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) Loading @@ -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( Loading @@ -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 Loading packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +12 −1 Original line number Diff line number Diff line Loading @@ -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) Loading Loading
packages/SystemUI/res/values/strings.xml +14 −0 Original line number Diff line number Diff line Loading @@ -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] --> Loading
packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +71 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } Loading Loading @@ -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) Loading @@ -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) Loading @@ -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( Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +12 −1 Original line number Diff line number Diff line Loading @@ -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) Loading