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

Commit 3ae4fe33 authored by Joshua Mokut's avatar Joshua Mokut Committed by Android (Google) Code Review
Browse files

Merge "Add logic for building content description for pressed keys" into main

parents b18297e3 e3483d46
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