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

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

Merge changes Icbcf21a3,Ifc558737 into main

* changes:
  Added Tests for custom shortcut registration logic
  Added logic for registering custom shortcut with InputManager
parents d48a9190 e575477a
Loading
Loading
Loading
Loading
+82 −0
Original line number Diff line number Diff line
@@ -18,16 +18,22 @@ package com.android.systemui.keyboard.shortcut.data.repository

import android.content.Context
import android.content.Context.INPUT_SERVICE
import android.hardware.input.InputGestureData
import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.KEYCODE_SLASH
import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CAPS_LOCK_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_FUNCTION_ON
import android.view.KeyEvent.META_META_LEFT_ON
import android.view.KeyEvent.META_META_ON
import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyEvent.META_SHIFT_RIGHT_ON
import android.view.KeyEvent.META_SYM_ON
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -40,6 +46,8 @@ import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCusto
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.kosmos.testScope
@@ -188,6 +196,69 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
        }
    }

    @Test
    fun shortcutBeingCustomized_updatedOnCustomizationRequested() {
        testScope.runTest {
            repo.onCustomizationRequested(standardCustomizationRequestInfo)

            val shortcutBeingCustomized = repo.getShortcutBeingCustomized()

            assertThat(shortcutBeingCustomized).isEqualTo(standardCustomizationRequestInfo)
        }
    }

    @Test
    fun buildInputGestureDataForShortcutBeingCustomized_noShortcutBeingCustomized_returnsNull() {
        testScope.runTest {
            helper.toggle(deviceId = 123)
            repo.updateUserKeyCombination(standardKeyCombination)

            val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()

            assertThat(inputGestureData).isNull()
        }
    }

    @Test
    fun buildInputGestureDataForShortcutBeingCustomized_noKeyCombinationSelected_returnsNull() {
        testScope.runTest {
            helper.toggle(deviceId = 123)
            repo.onCustomizationRequested(standardCustomizationRequestInfo)

            val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()

            assertThat(inputGestureData).isNull()
        }
    }

    @Test
    fun buildInputGestureDataForShortcutBeingCustomized_successfullyBuildInputGestureData() {
        testScope.runTest {
            helper.toggle(deviceId = 123)
            repo.onCustomizationRequested(standardCustomizationRequestInfo)
            repo.updateUserKeyCombination(standardKeyCombination)
            val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()

            // using toString as we're testing for only structural equality not referential.
            // inputGestureData is a java class and isEqual Tests for referential equality
            // as well which would cause this assert to fail
            assertThat(inputGestureData.toString()).isEqualTo(standardInputGestureData.toString())
        }
    }

    private val standardCustomizationRequestInfo =
        ShortcutCustomizationRequestInfo.Add(
            label = "Open apps list",
            categoryType = ShortcutCategoryType.System,
            subCategoryLabel = "System controls",
        )

    private val standardKeyCombination =
        KeyCombination(
            modifiers = META_META_ON or META_SHIFT_ON or META_META_LEFT_ON or META_SHIFT_RIGHT_ON,
            keyCode = KEYCODE_A,
        )

    private val allSupportedModifiers =
        META_META_ON or
            META_CTRL_ON or
@@ -195,4 +266,15 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
            META_SHIFT_ON or
            META_ALT_ON or
            META_SYM_ON

    private val standardInputGestureData =
        InputGestureData.Builder()
            .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS)
            .setTrigger(
                createKeyTrigger(
                    /* keycode = */ standardKeyCombination.keyCode!!,
                    /* modifierState = */ standardKeyCombination.modifiers and allSupportedModifiers,
                )
            )
            .build()
}
+6 −2
Original line number Diff line number Diff line
@@ -3833,13 +3833,17 @@
    <!-- Error message displayed when the user select a key combination that is already in use while
         assigning a new custom key combination to a shortcut in shortcut helper. The helper is a
         component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
    <string name="shortcut_helper_customize_dialog_error_message">Key combination already in use. Try another key.</string>
    <string name="shortcut_customizer_key_combination_in_use_error_message">Key combination already in use. Try another key.</string>
    <!-- Generic error message displayed when the user selected key combination cannot be used as
         custom keyboard shortcut in shortcut helper. The helper is a component that shows the user
         which keyboard shortcuts they can use and allows users to customize their keyboard
         shortcuts. [CHAR LIMIT=NONE] -->
    <string name="shortcut_customizer_generic_error_message">Shortcut cannot be set.</string>
    <!-- Plus sign, used in keyboard shortcut helper to combine keys for shortcut. E.g. Ctrl + A
         The helper is a component that shows the user which keyboard shortcuts they can use.
         [CHAR LIMIT=NONE] -->
    <string name="shortcut_helper_plus_symbol">+</string>


    <!-- Keyboard touchpad tutorial scheduler-->
    <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
    <string name="launch_keyboard_tutorial_notification_title">Navigate using your keyboard</string>
+23 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.shared.model

enum class ShortcutCustomizationRequestResult {
    SUCCESS,
    ERROR_RESERVED_COMBINATION,
    ERROR_OTHER,
}
+113 −9
Original line number Diff line number Diff line
@@ -19,23 +19,31 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
import android.content.Context.INPUT_SERVICE
import android.hardware.input.InputGestureData
import android.hardware.input.InputGestureData.Builder
import android.hardware.input.InputGestureData.KeyTrigger
import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.InputManager
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
import android.hardware.input.InputSettings
import android.hardware.input.KeyGestureEvent
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.mutableStateOf
import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -44,6 +52,8 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext

@SysUISingleton
class CustomShortcutCategoriesRepository
@@ -52,9 +62,10 @@ constructor(
    stateRepository: ShortcutHelperStateRepository,
    private val userTracker: UserTracker,
    @Background private val backgroundScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    @Background private val bgCoroutineContext: CoroutineContext,
    private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
    private val context: Context,
    private val inputGestureMaps: InputGestureMaps
) : ShortcutCategoriesRepository {

    private val userContext: Context
@@ -66,11 +77,12 @@ constructor(
        get() = userContext.getSystemService(INPUT_SERVICE) as InputManager

    private val _selectedKeyCombination = MutableStateFlow<KeyCombination?>(null)
    private val _shortcutBeingCustomized = mutableStateOf<ShortcutCustomizationRequestInfo?>(null)

    private val activeInputDevice =
        stateRepository.state.map {
            if (it is Active) {
                withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
                withContext(bgCoroutineContext) { inputManager.getInputDevice(it.deviceId) }
            } else {
                null
            }
@@ -150,6 +162,92 @@ constructor(
        _selectedKeyCombination.value = keyCombination
    }

    fun onCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo?) {
        _shortcutBeingCustomized.value = requestInfo
    }

    @VisibleForTesting
    fun buildInputGestureDataForShortcutBeingCustomized(): InputGestureData? {
        try {
            return Builder()
                .addKeyGestureTypeFromShortcutLabel()
                .addTriggerFromSelectedKeyCombination()
                .build()
            // TODO(b/379648200) add app launch data for application categories shortcut after
            // dynamic
            // label/icon mapping implementation
        } catch (e: IllegalArgumentException) {
            Log.w(TAG, "could not add custom shortcut: $e")
            return null
        }
    }

    suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
        return withContext(bgCoroutineContext) {
            val inputGestureData =
                buildInputGestureDataForShortcutBeingCustomized()
                    ?: return@withContext ShortcutCustomizationRequestResult.ERROR_OTHER

            return@withContext when (inputManager.addCustomInputGesture(inputGestureData)) {
                CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> ShortcutCustomizationRequestResult.SUCCESS
                CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS ->
                    ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION

                CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE ->
                    ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION

                else -> ShortcutCustomizationRequestResult.ERROR_OTHER
            }
        }
    }

    private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder {
        val shortcutBeingCustomized =
            getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Add

        if (shortcutBeingCustomized == null) {
            Log.w(TAG, "User requested to set shortcut but shortcut being customized is null")
            return this
        }

        val keyGestureType =
            inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]

        if (keyGestureType == null) {
            Log.w(TAG, "Could not find KeyGestureType for shortcut $shortcutBeingCustomized")
            return this
        }

        return setKeyGestureType(keyGestureType)
    }

    private fun Builder.addTriggerFromSelectedKeyCombination(): Builder {
        val selectedKeyCombination = _selectedKeyCombination.value
        if (selectedKeyCombination?.keyCode == null) {
            Log.w(
                TAG,
                "User requested to set shortcut but selected key combination is " +
                        "$selectedKeyCombination",
            )
            return this
        }

        return setTrigger(
            createKeyTrigger(
                /* keycode = */ selectedKeyCombination.keyCode,
                /* modifierState = */
                shortcutCategoriesUtils.removeUnsupportedModifiers(
                    selectedKeyCombination.modifiers
                ),
            )
        )
    }

    @VisibleForTesting
    fun getShortcutBeingCustomized(): ShortcutCustomizationRequestInfo? {
        return _shortcutBeingCustomized.value
    }

    private fun toInternalGroupSources(
        inputGestures: List<InputGestureData>
    ): List<InternalGroupsSource> {
@@ -158,8 +256,10 @@ constructor(
                val keyTrigger = gestureData.trigger as KeyTrigger
                val keyGestureType = gestureData.action.keyGestureType()
                fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
                    toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger)?.let {
                        internalKeyboardShortcutInfo ->
                    toInternalKeyboardShortcutInfo(
                        keyGestureType,
                        keyTrigger
                    )?.let { internalKeyboardShortcutInfo ->
                        val group =
                            InternalKeyboardShortcutGroup(
                                label = groupLabel,
@@ -194,7 +294,7 @@ constructor(
    private fun fetchGroupLabelByGestureType(
        @KeyGestureEvent.KeyGestureType keyGestureType: Int
    ): String? {
        InputGestures.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
        inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
            return context.getString(it)
        } ?: return null
    }
@@ -202,7 +302,7 @@ constructor(
    private fun fetchShortcutInfoLabelByGestureType(
        @KeyGestureEvent.KeyGestureType keyGestureType: Int
    ): String? {
        InputGestures.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
        inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
            return context.getString(it)
        } ?: return null
    }
@@ -210,11 +310,15 @@ constructor(
    private fun fetchShortcutCategoryTypeByGestureType(
        @KeyGestureEvent.KeyGestureType keyGestureType: Int
    ): ShortcutCategoryType? {
        return InputGestures.gestureToShortcutCategoryTypeMap[keyGestureType]
        return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType]
    }

    private data class InternalGroupsSource(
        val groups: List<InternalKeyboardShortcutGroup>,
        val type: ShortcutCategoryType,
    )

    private companion object {
        private const val TAG = "CustomShortcutCategoriesRepository"
    }
}
+12 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.keyboard.shortcut.data.repository

import android.content.Context
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_BACK
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
@@ -45,8 +46,11 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.res.R
import javax.inject.Inject

object InputGestures {
class InputGestureMaps
@Inject
constructor(private val context: Context) {
    val gestureToShortcutCategoryTypeMap =
        mapOf(
            // System Category
@@ -174,4 +178,11 @@ object InputGestures {
            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to
                R.string.keyboard_shortcut_group_applications_sms,
        )

    val shortcutLabelToKeyGestureTypeMap: Map<String, Int>
        get() = gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({
            context.getString(it.value)
        }) {
            it.key
        }
}
Loading