diff options
author | 2025-02-13 15:29:55 +0000 | |
---|---|---|
committer | 2025-02-14 13:09:58 +0000 | |
commit | e3483d469e70ffbdf37dc439b2bc055c54836487 (patch) | |
tree | a266fe8c588e0412dc5d5aa28aee31b6e6474356 | |
parent | 04a85b1091ddfe3b267e01fcaa73e898da314ddf (diff) |
Add logic for building content description for pressed keys
added logic to build custom content description when user is customizing
shortcut. This allows us to pass content description to our custom
text/icon box composable. Also this re-uses logic for building content
description in shortcut helper ensuring consistency across shortcut
helper.
Fix: 394480347
Flag: com.android.systemui.keyboard_shortcut_helper_shortcut_customizer
Test: ShortcutCustomizationViewModelTest
Change-Id: I409923ed219b878e62c5ccf94771ed379e89e946
6 files changed, 134 insertions, 35 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt index 6eef5eb09812..b91e259003a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt @@ -337,6 +337,43 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } } + @Test + fun uiState_pressedKeysDescription_emptyByDefault() { + testScope.runTest { + val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) + viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest) + + assertThat((uiState as AddShortcutDialog).pressedKeysDescription).isEmpty() + } + } + + @Test + fun uiState_pressedKeysDescription_updatesToNonEmptyDescriptionWhenKeyCombinationIsPressed() { + testScope.runTest { + val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) + viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest) + viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed) + viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed) + + // Note that Action Key is excluded as it's already displayed on the UI + assertThat((uiState as AddShortcutDialog).pressedKeysDescription) + .isEqualTo("Ctrl, plus A") + } + } + + @Test + fun uiState_pressedKeysDescription_resetsToEmpty_onClearSelectedShortcutKeyCombination() { + testScope.runTest { + val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) + viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest) + viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed) + viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed) + viewModel.clearSelectedKeyCombination() + + assertThat((uiState as AddShortcutDialog).pressedKeysDescription).isEmpty() + } + } + private suspend fun openAddShortcutDialogAndSetShortcut() { openAddShortcutDialogAndPressKeyCombination() viewModel.onSetShortcut() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt index 61d11f4df5e0..f89421f9b73e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt @@ -17,19 +17,16 @@ package com.android.systemui.keyboard.shortcut.domain.interactor import android.content.Context -import android.view.KeyEvent.META_META_ON import com.android.systemui.Flags.keyboardShortcutHelperShortcutCustomizer import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository -import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys -import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys.metaModifierIconResId +import com.android.systemui.keyboard.shortcut.extensions.toContentDescription import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories import com.android.systemui.keyboard.shortcut.qualifiers.DefaultShortcutCategories import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.res.R import dagger.Lazy @@ -105,8 +102,6 @@ constructor( context.getString(R.string.shortcut_helper_key_combinations_and_conjunction) val orConjunction = context.getString(R.string.shortcut_helper_key_combinations_or_separator) - val forwardSlash = - context.getString(R.string.shortcut_helper_key_combinations_forward_slash) return buildString { append("$label, $pressKey") commands.forEachIndexed { i, shortcutCommand -> @@ -117,29 +112,7 @@ constructor( if (j > 0) { append(" $andConjunction") } - if (shortcutKey is ShortcutKey.Text) { - // Special handling for "/" as TalkBack will not read punctuation by - // default. - if (shortcutKey.value.equals("/")) { - append(" $forwardSlash") - } else { - append(" ${shortcutKey.value}") - } - } else if (shortcutKey is ShortcutKey.Icon.ResIdIcon) { - val keyLabel = - if (shortcutKey.drawableResId == metaModifierIconResId) { - ShortcutHelperKeys.modifierLabels[META_META_ON] - } else { - val keyCode = - ShortcutHelperKeys.keyIcons.entries - .firstOrNull { it.value == shortcutKey.drawableResId } - ?.key - ShortcutHelperKeys.specialKeyLabels[keyCode] - } - if (keyLabel != null) { - append(" ${keyLabel.invoke(context)}") - } - } // No-Op when shortcutKey is ShortcutKey.Icon.DrawableIcon + shortcutKey.toContentDescription(context)?.let { append(" $it") } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/ShortcutKeyExtensions.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/ShortcutKeyExtensions.kt new file mode 100644 index 000000000000..5637747d5aba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/extensions/ShortcutKeyExtensions.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.extensions + +import android.content.Context +import android.view.KeyEvent.META_META_ON +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys.metaModifierIconResId +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey +import com.android.systemui.res.R + +fun ShortcutKey.toContentDescription(context: Context): String? { + val forwardSlash = context.getString(R.string.shortcut_helper_key_combinations_forward_slash) + when (this) { + is ShortcutKey.Text -> { + // Special handling for "/" as TalkBack will not read punctuation by + // default. + return if (this.value == "/") { + forwardSlash + } else { + this.value + } + } + + is ShortcutKey.Icon.ResIdIcon -> { + val keyLabel = + if (this.drawableResId == metaModifierIconResId) { + ShortcutHelperKeys.modifierLabels[META_META_ON] + } else { + val keyCode = + ShortcutHelperKeys.keyIcons.entries + .firstOrNull { it.value == this.drawableResId } + ?.key + ShortcutHelperKeys.specialKeyLabels[keyCode] + } + + if (keyLabel != null) { + return keyLabel.invoke(context) + } + } + + is ShortcutKey.Icon.DrawableIcon -> { + // No-Op when shortcutKey is ShortcutKey.Icon.DrawableIcon + } + } + + return null +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt index 66e45056989d..7e0fa2f125b0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt @@ -60,6 +60,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.LiveRegionMode import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.liveRegion import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight @@ -127,6 +128,7 @@ private fun AddShortcutDialog( shouldShowError = uiState.errorMessage.isNotEmpty(), onShortcutKeyCombinationSelected = onShortcutKeyCombinationSelected, pressedKeys = uiState.pressedKeys, + contentDescription = uiState.pressedKeysDescription, onConfirmSetShortcut = onConfirmSetShortcut, onClearSelectedKeyCombination = onClearSelectedKeyCombination, ) @@ -267,6 +269,7 @@ private fun SelectedKeyCombinationContainer( shouldShowError: Boolean, onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean, pressedKeys: List<ShortcutKey>, + contentDescription: String, onConfirmSetShortcut: () -> Unit, onClearSelectedKeyCombination: () -> Unit, ) { @@ -313,6 +316,7 @@ private fun SelectedKeyCombinationContainer( } else { null }, + contentDescription = contentDescription, ) } @@ -331,8 +335,7 @@ private fun ErrorIcon(shouldShowError: Boolean) { @Composable private fun PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) { Row( - modifier = - Modifier.semantics(mergeDescendants = true) { liveRegion = LiveRegionMode.Polite }, + modifier = Modifier.semantics { hideFromAccessibility() }, verticalAlignment = Alignment.CenterVertically, ) { pressedKeys.forEachIndexed { keyIndex, key -> @@ -495,6 +498,7 @@ private fun OutlinedInputField( trailingIcon: @Composable () -> Unit, isError: Boolean, modifier: Modifier = Modifier, + contentDescription: String, ) { OutlinedTextField( value = "", @@ -502,7 +506,10 @@ private fun OutlinedInputField( placeholder = if (content == null) placeholder else null, prefix = content, singleLine = true, - modifier = modifier, + modifier = + modifier.semantics(mergeDescendants = true) { + this.contentDescription = contentDescription + }, trailingIcon = trailingIcon, colors = OutlinedTextFieldDefaults.colors() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt index 36c5ae0717be..688573df9ee7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt @@ -24,6 +24,7 @@ sealed interface ShortcutCustomizationUiState { val errorMessage: String = "", val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon, val pressedKeys: List<ShortcutKey> = emptyList(), + val pressedKeysDescription: String = "", ) : ShortcutCustomizationUiState data object DeleteShortcutDialog : ShortcutCustomizationUiState diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt index f4ba99c6a394..aeedc4b7d618 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.input.key.nativeKeyCode import androidx.compose.ui.input.key.type import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor +import com.android.systemui.keyboard.shortcut.extensions.toContentDescription import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey @@ -185,7 +186,11 @@ constructor( _shortcutCustomizationUiState.update { uiState -> if (uiState is AddShortcutDialog) { - uiState.copy(pressedKeys = keys, errorMessage = errorMessage) + uiState.copy( + pressedKeys = keys, + errorMessage = errorMessage, + pressedKeysDescription = getAccessibilityDescForPressedKeys(keys), + ) } else { uiState } @@ -193,11 +198,25 @@ constructor( } } + private fun getAccessibilityDescForPressedKeys(keys: List<ShortcutKey>): String { + val andConjunction = + context.getString(R.string.shortcut_helper_key_combinations_and_conjunction) + return buildString { + keys.forEach { key -> + key.toContentDescription(context)?.let { + if (isNotEmpty()) { + append(", $andConjunction ") + } + append(it) + } + } + } + } + private suspend fun getErrorMessageForPressedKeys(keys: List<ShortcutKey>): String { return if (keys.isEmpty() or isSelectedKeyCombinationAvailable()) { "" - } - else { + } else { context.getString(R.string.shortcut_customizer_key_combination_in_use_error_message) } } |