Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +82 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading @@ -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() } packages/SystemUI/res/values/strings.xml +6 −2 Original line number Diff line number Diff line Loading @@ -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> Loading packages/SystemUI/src/com/android/systemui/keyboard/shared/model/ShortcutCustomizationRequestResult.kt 0 → 100644 +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, } packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt +113 −9 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 } Loading Loading @@ -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> { Loading @@ -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, Loading Loading @@ -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 } Loading @@ -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 } Loading @@ -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" } } packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt→packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt +12 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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
packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +82 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading @@ -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() }
packages/SystemUI/res/values/strings.xml +6 −2 Original line number Diff line number Diff line Loading @@ -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> Loading
packages/SystemUI/src/com/android/systemui/keyboard/shared/model/ShortcutCustomizationRequestResult.kt 0 → 100644 +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, }
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt +113 −9 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 } Loading Loading @@ -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> { Loading @@ -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, Loading Loading @@ -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 } Loading @@ -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 } Loading @@ -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" } }
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt→packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt +12 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 } }