Merge "Add logic for building content description for pressed keys" into main
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 6eef5eb..b91e259 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 @@
         }
     }
 
+    @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 61d11f4..f89421f 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 @@
             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 @@
                     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 0000000..5637747
--- /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 66e4505..7e0fa2f 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.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 @@
             shouldShowError = uiState.errorMessage.isNotEmpty(),
             onShortcutKeyCombinationSelected = onShortcutKeyCombinationSelected,
             pressedKeys = uiState.pressedKeys,
+            contentDescription = uiState.pressedKeysDescription,
             onConfirmSetShortcut = onConfirmSetShortcut,
             onClearSelectedKeyCombination = onClearSelectedKeyCombination,
         )
@@ -267,6 +269,7 @@
     shouldShowError: Boolean,
     onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
     pressedKeys: List<ShortcutKey>,
+    contentDescription: String,
     onConfirmSetShortcut: () -> Unit,
     onClearSelectedKeyCombination: () -> Unit,
 ) {
@@ -313,6 +316,7 @@
             } else {
                 null
             },
+        contentDescription = contentDescription,
     )
 }
 
@@ -331,8 +335,7 @@
 @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 @@
     trailingIcon: @Composable () -> Unit,
     isError: Boolean,
     modifier: Modifier = Modifier,
+    contentDescription: String,
 ) {
     OutlinedTextField(
         value = "",
@@ -502,7 +506,10 @@
         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 36c5ae0..688573d 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 @@
         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 f4ba99c..aeedc4b 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.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 @@
 
             _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 @@
         }
     }
 
+    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)
         }
     }