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

Commit 56178c95 authored by Helen Cheuk's avatar Helen Cheuk Committed by Android (Google) Code Review
Browse files

Merge "[Custom Key Glyph] Show function row keys according to key glyph" into main

parents e4664cee 298083ef
Loading
Loading
Loading
Loading
+40 −2
Original line number Diff line number Diff line
@@ -17,21 +17,28 @@
package com.android.systemui.keyboard.shortcut.data.source

import android.content.res.mainResources
import android.hardware.input.KeyGlyphMap
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.KeyEvent.KEYCODE_EMOJI_PICKER
import android.view.KeyboardShortcutGroup
import android.view.WindowManager.KeyboardShortcutsReceiver
import android.view.mockWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -41,7 +48,8 @@ class InputShortcutsSourceTest : SysuiTestCase() {
    private val testScope = kosmos.testScope

    private val mockWindowManager = kosmos.mockWindowManager
    private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager)
    private val inputManager = kosmos.fakeInputManager.inputManager
    private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager, inputManager)

    private var wmImeShortcutGroups: List<KeyboardShortcutGroup>? = null

@@ -88,6 +96,36 @@ class InputShortcutsSourceTest : SysuiTestCase() {
            assertThat(groups).hasSize(4)
        }

    @Test
    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
    fun shortcutGroups_flagEnabled_inputManagerReturnsKeyGlyph_returnsEmojiShortcut() =
        testScope.runTest {
            val mockKeyGlyph = mock(KeyGlyphMap::class.java)
            whenever(mockKeyGlyph.functionRowKeys).thenReturn(intArrayOf(KEYCODE_EMOJI_PICKER))
            whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyph)
            wmImeShortcutGroups = null

            val groups = source.shortcutGroups(TEST_DEVICE_ID)

            val keyCodes = groups[0].items.map { it.keycode }
            assertThat(keyCodes).contains(KEYCODE_EMOJI_PICKER)
        }

    @Test
    @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
    fun shortcutGroups_flagDisabled_inputManagerReturnsKeyGlyph_returnsNoEmojiShortcut() =
        testScope.runTest {
            val mockKeyGlyph = mock(KeyGlyphMap::class.java)
            whenever(mockKeyGlyph.functionRowKeys).thenReturn(intArrayOf(KEYCODE_EMOJI_PICKER))
            whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyph)
            wmImeShortcutGroups = null

            val groups = source.shortcutGroups(TEST_DEVICE_ID)

            val keyCodes = groups[0].items.map { it.keycode }
            assertThat(keyCodes).doesNotContain(KEYCODE_EMOJI_PICKER)
        }

    companion object {
        private const val TEST_DEVICE_ID = 1234
    }
+138 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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.data.source

import android.content.res.mainResources
import android.hardware.input.KeyGlyphMap
import android.hardware.input.KeyGlyphMap.KeyCombination
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.KeyEvent
import android.view.KeyEvent.KEYCODE_BACK
import android.view.KeyEvent.KEYCODE_HOME
import android.view.KeyEvent.KEYCODE_RECENT_APPS
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
class SystemShortcutsSourceTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val inputManager = kosmos.fakeInputManager.inputManager
    private val source = SystemShortcutsSource(kosmos.mainResources, inputManager)
    private val mockKeyGlyphMap = mock(KeyGlyphMap::class.java)
    private val functionRowKeyCodes = listOf(KEYCODE_HOME, KEYCODE_BACK, KEYCODE_RECENT_APPS)

    @Before
    fun setUp() {
        whenever(mockKeyGlyphMap.functionRowKeys).thenReturn(intArrayOf())
        whenever(inputManager.getKeyGlyphMap(TEST_DEVICE_ID)).thenReturn(mockKeyGlyphMap)
    }

    @Test
    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
    fun shortcutGroups_flagEnabled_inputManagerReturnsNoFunctionRowKeys_returnsNoFunctionRowShortcuts() =
        testScope.runTest {
            val groups = source.shortcutGroups(TEST_DEVICE_ID)

            val keyCodes = groups[0].items.map { it.keycode }
            assertThat(keyCodes).containsNoneIn(functionRowKeyCodes)
        }

    @Test
    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
    fun shortcutGroups_flagEnabled_inputManagerReturnsFunctionRowKeys_returnsFunctionRowShortcuts() =
        testScope.runTest {
            whenever(mockKeyGlyphMap.functionRowKeys)
                .thenReturn(intArrayOf(KEYCODE_HOME, KEYCODE_BACK, KEYCODE_RECENT_APPS))

            val groups = source.shortcutGroups(TEST_DEVICE_ID)

            val keyCodes = groups[0].items.map { it.keycode }
            assertThat(keyCodes).containsAtLeastElementsIn(functionRowKeyCodes)
        }

    @Test
    @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
    fun shortcutGroups_flagDisabled_inputManagerReturnsNoFunctionRowKeys_returnsDefaultFunctionRowShortcuts() =
        testScope.runTest {
            val groups = source.shortcutGroups(TEST_DEVICE_ID)

            val keyCodes = groups[0].items.map { it.keycode }
            assertThat(keyCodes).containsAtLeastElementsIn(functionRowKeyCodes)
        }

    @Test
    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
    fun shortcutGroups_flagEnabled_inputManagerReturnsHardwareShortcuts_returnsHardwareShortcuts() =
        testScope.runTest {
            whenever(mockKeyGlyphMap.functionRowKeys).thenReturn(intArrayOf())
            val hardwareShortcutMap =
                mapOf(Pair(KeyCombination(KeyEvent.META_META_ON, KeyEvent.KEYCODE_1), KEYCODE_BACK))
            whenever(mockKeyGlyphMap.hardwareShortcuts).thenReturn(hardwareShortcutMap)

            val groups = source.shortcutGroups(TEST_DEVICE_ID)

            val shortcuts = groups[0].items.map { c -> Triple(c.label, c.modifiers, c.keycode) }
            val hardwareShortcut =
                Triple(
                    context.getString(R.string.group_system_go_back),
                    KeyEvent.META_META_ON,
                    KeyEvent.KEYCODE_1,
                )
            assertThat(shortcuts).contains(hardwareShortcut)
        }

    @Test
    @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
    fun shortcutGroups_flagDisabled_inputManagerReturnsHardwareShortcuts_returnsNoHardwareShortcuts() =
        testScope.runTest {
            val hardwareShortcutMap =
                mapOf(Pair(KeyCombination(KeyEvent.META_META_ON, KeyEvent.KEYCODE_1), KEYCODE_BACK))
            whenever(mockKeyGlyphMap.hardwareShortcuts).thenReturn(hardwareShortcutMap)

            val groups = source.shortcutGroups(TEST_DEVICE_ID)

            val shortcuts = groups[0].items.map { c -> Triple(c.label, c.modifiers, c.keycode) }
            val hardwareShortcut =
                Triple(
                    context.getString(R.string.group_system_go_back),
                    KeyEvent.META_META_ON,
                    KeyEvent.KEYCODE_1,
                )
            assertThat(shortcuts).doesNotContain(hardwareShortcut)
        }

    companion object {
        private const val TEST_DEVICE_ID = 1234
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -116,7 +116,10 @@ constructor(
            .filter {
                // Allow KEYCODE_UNKNOWN (0) because shortcuts can have just modifiers and no
                // keycode, or they could have a baseCharacter instead of a keycode.
                it.keycode == KeyEvent.KEYCODE_UNKNOWN || supportedKeyCodes.contains(it.keycode)
                it.keycode == KeyEvent.KEYCODE_UNKNOWN ||
                    supportedKeyCodes.contains(it.keycode) ||
                    // Support keyboard function row key codes
                    keyGlyphMap?.functionRowKeys?.contains(it.keycode) ?: false
            }
            .mapNotNull { toShortcut(keyGlyphMap, keyCharacterMap, it, keepIcons) }

+9 −0
Original line number Diff line number Diff line
@@ -99,6 +99,7 @@ import android.view.KeyEvent.KEYCODE_PAGE_DOWN
import android.view.KeyEvent.KEYCODE_PAGE_UP
import android.view.KeyEvent.KEYCODE_PERIOD
import android.view.KeyEvent.KEYCODE_RECENT_APPS
import android.view.KeyEvent.KEYCODE_SCREENSHOT
import android.view.KeyEvent.KEYCODE_SCROLL_LOCK
import android.view.KeyEvent.KEYCODE_SHIFT_LEFT
import android.view.KeyEvent.KEYCODE_SHIFT_RIGHT
@@ -125,6 +126,14 @@ object ShortcutHelperKeys {
            KEYCODE_RECENT_APPS to R.drawable.ic_check_box_outline_blank,
        )

    val keyLabelResIds =
        mapOf(
            KEYCODE_BACK to R.string.group_system_go_back,
            KEYCODE_HOME to R.string.group_system_access_home_screen,
            KEYCODE_RECENT_APPS to R.string.group_system_overview_open_apps,
            KEYCODE_SCREENSHOT to R.string.group_system_full_screenshot,
        )

    val modifierLabels =
        mapOf<Int, (Context) -> String>(
            // Modifiers
+27 −6
Original line number Diff line number Diff line
@@ -17,12 +17,16 @@
package com.android.systemui.keyboard.shortcut.data.source

import android.content.res.Resources
import android.hardware.input.InputManager
import android.view.KeyEvent.KEYCODE_EMOJI_PICKER
import android.view.KeyEvent.KEYCODE_SPACE
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
import android.view.WindowManager
import android.view.WindowManager.KeyboardShortcutsReceiver
import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo
import com.android.systemui.res.R
@@ -31,16 +35,19 @@ import kotlinx.coroutines.suspendCancellableCoroutine

class InputShortcutsSource
@Inject
constructor(@Main private val resources: Resources, private val windowManager: WindowManager) :
    KeyboardShortcutGroupsSource {
constructor(
    @Main private val resources: Resources,
    private val windowManager: WindowManager,
    private val inputManager: InputManager,
) : KeyboardShortcutGroupsSource {
    override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> =
        getInputLanguageShortcutGroup() + getImeShortcutGroup(deviceId)
        getInputLanguageShortcutGroup(deviceId) + getImeShortcutGroup(deviceId)

    private fun getInputLanguageShortcutGroup() =
    private fun getInputLanguageShortcutGroup(deviceId: Int) =
        listOf(
            KeyboardShortcutGroup(
                resources.getString(R.string.shortcut_helper_category_input),
                inputLanguageShortcuts()
                inputLanguageShortcuts() + hardwareShortcuts(deviceId),
            )
        )

@@ -53,8 +60,22 @@ constructor(@Main private val resources: Resources, private val windowManager: W
            /* Switch previous language (next language): Ctrl + Shift + Space */
            shortcutInfo(resources.getString(R.string.input_switch_input_language_previous)) {
                command(META_CTRL_ON or META_SHIFT_ON, KEYCODE_SPACE)
            },
        )

    private fun hardwareShortcuts(deviceId: Int): List<KeyboardShortcutInfo> {
        if (shortcutHelperKeyGlyph()) {
            val keyGlyphMap = inputManager.getKeyGlyphMap(deviceId)
            if (keyGlyphMap != null && keyGlyphMap.functionRowKeys.contains(KEYCODE_EMOJI_PICKER)) {
                return listOf(
                    shortcutInfo(resources.getString(R.string.input_access_emoji)) {
                        command(modifiers = 0, KEYCODE_EMOJI_PICKER)
                    }
                )
            }
        }
        return emptyList()
    }

    private suspend fun getImeShortcutGroup(deviceId: Int): List<KeyboardShortcutGroup> =
        suspendCancellableCoroutine { continuation ->
Loading