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

Commit 373ae848 authored by Josh's avatar Josh
Browse files

Added logic for registering custom shortcut with InputManager

Test: ShortcutCustomizationViewModelTest
Test: CustomShortcutCategoriesRepositoryTest
Fix: 373631227
Flag: com.android.systemui.keyboard_shortcut_helper_shortcut_customizer

Change-Id: Ifc5587376f199aa90f7825ff9eb7a35b84832048
parent 482cb781
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -3818,6 +3818,11 @@
         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>
    <!-- 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_helper_customize_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] -->
+25 −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_ALREADY_EXISTS,
    ERROR_DOES_NOT_EXIST,
    ERROR_RESERVED_SHORTCUT,
    ERROR_OTHER,
}
+104 −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_DOES_NOT_EXIST
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.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,83 @@ constructor(
        _selectedKeyCombination.value = keyCombination
    }

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

    suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
        return withContext(bgCoroutineContext) {
            val builder =
                Builder().addKeyGestureTypeFromShortcutLabel()
                    .addTriggerFromSelectedKeyCombination()

            // TODO(b/379648200) add app launch data for application categories shortcut after dynamic
            // label mapping implementation
            try {
                val inputGestureData = builder.build()
                val result = inputManager.addCustomInputGesture(inputGestureData)
                return@withContext when (result) {
                    CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> ShortcutCustomizationRequestResult.SUCCESS
                    CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS ->
                        ShortcutCustomizationRequestResult.ERROR_ALREADY_EXISTS

                    CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST ->
                        ShortcutCustomizationRequestResult.ERROR_DOES_NOT_EXIST

                    CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE ->
                        ShortcutCustomizationRequestResult.ERROR_RESERVED_SHORTCUT

                    else -> ShortcutCustomizationRequestResult.ERROR_OTHER
                }
            } catch (e: IllegalArgumentException) {
                Log.w(TAG, "could not add custom shortcut: $e")
                return@withContext ShortcutCustomizationRequestResult.ERROR_OTHER
            }
        }
    }

    private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder {
        val shortcutBeingCustomized =
            _shortcutBeingCustomized.value 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
                ),
            )
        )
    }

    private fun toInternalGroupSources(
        inputGestures: List<InputGestureData>
    ): List<InternalGroupsSource> {
@@ -158,8 +247,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 +285,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 +293,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 +301,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
        }
}
+6 −1
Original line number Diff line number Diff line
@@ -36,9 +36,9 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext

class ShortcutCategoriesUtils
@Inject
@@ -47,6 +47,11 @@ constructor(
    @Background private val backgroundCoroutineContext: CoroutineContext,
    private val inputManager: InputManager,
) {

    fun removeUnsupportedModifiers(modifierMask: Int): Int {
        return SUPPORTED_MODIFIERS.reduce { acc, modifier -> acc or modifier } and modifierMask
    }

    fun fetchShortcutCategory(
        type: ShortcutCategoryType?,
        groups: List<InternalKeyboardShortcutGroup>,
Loading