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

Commit 298083ef authored by helencheuk's avatar helencheuk
Browse files

[Custom Key Glyph] Show function row keys according to key glyph

1. Get shortcut sources from keyboard key glyph map, only show function row keys if the map contains it
2. Add hardware defined shortcuts to the Shortcut source so they will be displayed in shortcut helper.

Testing result:
No function row key for keyboard that does not have key glyph: https://screenshot.googleplex.com/Ak6oZGzSWJDSRXr
Show hardware defined shortcut if any: https://screenshot.googleplex.com/5TBevsyNDtoLjaX

Bug: 376433516
Test: SystemShortcutsSourceTest
Test: InputShortcutsSourceTest
Flag: com.android.systemui.shortcut_helper_key_glyph
Change-Id: I8991a9ff33fa6bd06fef4781cc3474c75d3d0bdf
parent b0ec3323
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