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

Commit e3483d46 authored by Josh's avatar Josh
Browse files

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
parent 04a85b10
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -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()
+2 −29
Original line number Diff line number Diff line
@@ -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") }
                }
            }
        }
+62 −0
Original line number Diff line number Diff line
/*
 * 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
}
+10 −3
Original line number Diff line number Diff line
@@ -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()
+1 −0
Original line number Diff line number Diff line
@@ -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
Loading