Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +36 −62 Original line number Diff line number Diff line Loading @@ -18,36 +18,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.createKeyTrigger import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS 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 import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.ALL_SUPPORTED_MODIFIERS import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations 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.data.source.TestShortcuts.goHomeInputGestureData import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddCustomShortcutRequestInfo import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardDeleteCustomShortcutRequestInfo import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination 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 @@ -74,24 +69,21 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } private val fakeInputManager = kosmos.fakeInputManager private val inputManager = kosmos.fakeInputManager.inputManager private val testScope = kosmos.testScope private val helper = kosmos.shortcutHelperTestHelper private val repo = kosmos.customShortcutCategoriesRepository @Before fun setup() { whenever(mockUserContext.getSystemService(INPUT_SERVICE)) .thenReturn(fakeInputManager.inputManager) whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) } @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_emitsCorrectlyConvertedShortcutCategories() { testScope.runTest { whenever( fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) ) whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull())) .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations) helper.toggle(deviceId = 123) Loading @@ -106,9 +98,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { @DisableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_emitsEmptyListWhenFlagIsDisabled() { testScope.runTest { whenever( fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) ) whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull())) .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations) helper.toggle(deviceId = 123) Loading @@ -122,9 +112,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_ignoresUnknownKeyGestureTypes() { testScope.runTest { whenever( fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) ) whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull())) .thenReturn(customizableInputGestureWithUnknownKeyGestureType) helper.toggle(deviceId = 123) Loading @@ -151,7 +139,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { helper.toggle(deviceId = 123) val pressedKeys by collectLastValue(repo.pressedKeys) repo.updateUserKeyCombination( KeyCombination(modifiers = allSupportedModifiers, keyCode = null) KeyCombination(modifiers = ALL_SUPPORTED_MODIFIERS, keyCode = null) ) assertThat(pressedKeys) Loading Loading @@ -199,11 +187,11 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { @Test fun shortcutBeingCustomized_updatedOnCustomizationRequested() { testScope.runTest { repo.onCustomizationRequested(standardCustomizationRequestInfo) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) val shortcutBeingCustomized = repo.getShortcutBeingCustomized() assertThat(shortcutBeingCustomized).isEqualTo(standardCustomizationRequestInfo) assertThat(shortcutBeingCustomized).isEqualTo(standardAddCustomShortcutRequestInfo) } } Loading @@ -223,7 +211,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { fun buildInputGestureDataForShortcutBeingCustomized_noKeyCombinationSelected_returnsNull() { testScope.runTest { helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardCustomizationRequestInfo) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized() Loading @@ -235,46 +223,32 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { fun buildInputGestureDataForShortcutBeingCustomized_successfullyBuildInputGestureData() { testScope.runTest { helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardCustomizationRequestInfo) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) 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()) assertThat(inputGestureData.toString()).isEqualTo(allAppsInputGestureData.toString()) } } private val standardCustomizationRequestInfo = ShortcutCustomizationRequestInfo.Add( label = "Open apps list", categoryType = ShortcutCategoryType.System, subCategoryLabel = "System controls", ) @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun deleteShortcut_successfullyRetrievesGestureDataAndDeletesShortcut() { testScope.runTest { whenever(inputManager.getCustomInputGestures(anyOrNull())) .thenReturn(listOf(allAppsInputGestureData, goHomeInputGestureData)) whenever(inputManager.removeCustomInputGesture(allAppsInputGestureData)) .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) 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, ) helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardDeleteCustomShortcutRequestInfo) private val allSupportedModifiers = META_META_ON or META_CTRL_ON or META_FUNCTION_ON or 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() val result = repo.deleteShortcutCurrentlyBeingCustomized() assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS) } } } packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt +122 −0 Original line number Diff line number Diff line Loading @@ -19,9 +19,23 @@ package com.android.systemui.keyboard.shortcut.data.source import android.hardware.input.InputGestureData import android.hardware.input.InputGestureData.createKeyTrigger import android.hardware.input.KeyGestureEvent import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME import android.os.SystemClock import android.view.KeyEvent import android.view.KeyEvent.ACTION_DOWN import android.view.KeyEvent.KEYCODE_A import android.view.KeyEvent.META_ALT_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 android.view.KeyboardShortcutGroup import android.view.KeyboardShortcutInfo import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType Loading @@ -29,9 +43,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.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.shared.model.shortcut import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.res.R object TestShortcuts { Loading Loading @@ -525,4 +541,110 @@ object TestShortcuts { keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER ), ) val standardAddCustomShortcutRequestInfo = ShortcutCustomizationRequestInfo.Add( label = "Open apps list", categoryType = System, subCategoryLabel = "System controls", ) val standardDeleteCustomShortcutRequestInfo = ShortcutCustomizationRequestInfo.Delete( label = "Open apps list", categoryType = System, subCategoryLabel = "System controls", ) val standardKeyCombination = KeyCombination( modifiers = META_META_ON or META_SHIFT_ON or META_META_LEFT_ON or META_SHIFT_RIGHT_ON, keyCode = KEYCODE_A, ) const val ALL_SUPPORTED_MODIFIERS = META_META_ON or META_CTRL_ON or META_FUNCTION_ON or META_SHIFT_ON or META_ALT_ON or META_SYM_ON val allAppsInputGestureData: InputGestureData = InputGestureData.Builder() .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS) .setTrigger( createKeyTrigger( /* keycode = */ standardKeyCombination.keyCode!!, /* modifierState = */ standardKeyCombination.modifiers and ALL_SUPPORTED_MODIFIERS, ) ) .build() val goHomeInputGestureData: InputGestureData = InputGestureData.Builder() .setKeyGestureType(KEY_GESTURE_TYPE_HOME) .setTrigger( createKeyTrigger( /* keycode = */ standardKeyCombination.keyCode!!, /* modifierState = */ standardKeyCombination.modifiers and ALL_SUPPORTED_MODIFIERS, ) ) .build() val expectedStandardDeleteShortcutUiState = ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false) val keyDownEventWithoutActionKeyPressed = androidx.compose.ui.input.key.KeyEvent( android.view.KeyEvent( /* downTime = */ SystemClock.uptimeMillis(), /* eventTime = */ SystemClock.uptimeMillis(), /* action = */ ACTION_DOWN, /* code = */ KEYCODE_A, /* repeat = */ 0, /* metaState = */ META_CTRL_ON, ) ) val keyDownEventWithActionKeyPressed = androidx.compose.ui.input.key.KeyEvent( android.view.KeyEvent( /* downTime = */ SystemClock.uptimeMillis(), /* eventTime = */ SystemClock.uptimeMillis(), /* action = */ ACTION_DOWN, /* code = */ KEYCODE_A, /* repeat = */ 0, /* metaState = */ META_CTRL_ON or META_META_ON, ) ) val keyUpEventWithActionKeyPressed = androidx.compose.ui.input.key.KeyEvent( android.view.KeyEvent( /* downTime = */ SystemClock.uptimeMillis(), /* eventTime = */ SystemClock.uptimeMillis(), /* action = */ ACTION_DOWN, /* code = */ KEYCODE_A, /* repeat = */ 0, /* metaState = */ 0, ) ) val standardAddShortcutRequest = ShortcutCustomizationRequestInfo.Add( label = "Standard shortcut", categoryType = ShortcutCategoryType.System, subCategoryLabel = "Standard subcategory", ) val expectedStandardAddShortcutUiState = ShortcutCustomizationUiState.AddShortcutDialog( shortcutLabel = "Standard shortcut", defaultCustomShortcutModifierKey = ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta), isDialogShowing = false, ) } packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt +80 −25 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RES import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.InputSettings import android.hardware.input.KeyGestureEvent import android.hardware.input.KeyGestureEvent.KeyGestureType import android.util.Log import androidx.annotation.VisibleForTesting import androidx.compose.runtime.mutableStateOf Loading @@ -44,6 +45,8 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomization 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 kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow Loading @@ -52,8 +55,6 @@ 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 @@ -65,7 +66,7 @@ constructor( @Background private val bgCoroutineContext: CoroutineContext, private val shortcutCategoriesUtils: ShortcutCategoriesUtils, private val context: Context, private val inputGestureMaps: InputGestureMaps private val inputGestureMaps: InputGestureMaps, ) : ShortcutCategoriesRepository { private val userContext: Context Loading Loading @@ -130,9 +131,7 @@ constructor( emptyList() } else { val customInputGesturesForUser: List<InputGestureData> = if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) { inputManager.getCustomInputGestures(/* filter= */ null) } else emptyList() getCustomInputGestures() val sources = toInternalGroupSources(customInputGesturesForUser) val supportedKeyCodes = shortcutCategoriesUtils.fetchSupportedKeyCodes( Loading Loading @@ -173,16 +172,20 @@ constructor( .addKeyGestureTypeFromShortcutLabel() .addTriggerFromSelectedKeyCombination() .build() // TODO(b/379648200) add app launch data for application categories shortcut after // dynamic // label/icon mapping implementation // TODO(b/379648200) add app launch data after dynamic label/icon mapping implementation } catch (e: IllegalArgumentException) { Log.w(TAG, "could not add custom shortcut: $e") return null } } suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult { private fun retrieveInputGestureDataForShortcutBeingDeleted(): InputGestureData? { val keyGestureType = getKeyGestureTypeFromShortcutBeingDeletedLabel() return getCustomInputGestures().firstOrNull { it.action.keyGestureType() == keyGestureType } } suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext) { val inputGestureData = buildInputGestureDataForShortcutBeingCustomized() Loading @@ -201,24 +204,79 @@ constructor( } } suspend fun deleteShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext) { val inputGestureData = retrieveInputGestureDataForShortcutBeingDeleted() ?: return@withContext ShortcutCustomizationRequestResult.ERROR_OTHER return@withContext when ( val result = inputManager.removeCustomInputGesture(inputGestureData) ) { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> ShortcutCustomizationRequestResult.SUCCESS else -> { Log.w( TAG, "Attempted to delete shortcut being customized " + "${_shortcutBeingCustomized.value} but ran into an error. InputGestureData" + " = $inputGestureData, error code: $result", ) ShortcutCustomizationRequestResult.ERROR_OTHER } } } } private fun getCustomInputGestures(): List<InputGestureData> { return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) { inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) } else emptyList() } private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder { val keyGestureType = getKeyGestureTypeFromShortcutBeingCustomizedLabel() if (keyGestureType == null) { Log.w( TAG, "Could not find KeyGestureType for shortcut ${_shortcutBeingCustomized.value}", ) return this } return setKeyGestureType(keyGestureType) } @KeyGestureType private fun getKeyGestureTypeFromShortcutBeingCustomizedLabel(): Int? { 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 Log.w( TAG, "Requested key gesture type from label but shortcut being customized is null", ) return null } val keyGestureType = inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label] return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label] } if (keyGestureType == null) { Log.w(TAG, "Could not find KeyGestureType for shortcut $shortcutBeingCustomized") return this @KeyGestureType private fun getKeyGestureTypeFromShortcutBeingDeletedLabel(): Int? { val shortcutBeingCustomized = getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Delete if (shortcutBeingCustomized == null) { Log.w( TAG, "Requested key gesture type from label but shortcut being customized is null", ) return null } return setKeyGestureType(keyGestureType) return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label] } private fun Builder.addTriggerFromSelectedKeyCombination(): Builder { Loading @@ -235,8 +293,7 @@ constructor( return setTrigger( createKeyTrigger( /* keycode = */ selectedKeyCombination.keyCode, /* modifierState = */ shortcutCategoriesUtils.removeUnsupportedModifiers( /* modifierState = */ shortcutCategoriesUtils.removeUnsupportedModifiers( selectedKeyCombination.modifiers ), ) Loading @@ -256,10 +313,8 @@ 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 packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt +7 −8 Original line number Diff line number Diff line Loading @@ -48,9 +48,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType. import com.android.systemui.res.R import javax.inject.Inject class InputGestureMaps @Inject constructor(private val context: Context) { class InputGestureMaps @Inject constructor(private val context: Context) { val gestureToShortcutCategoryTypeMap = mapOf( // System Category Loading Loading @@ -180,7 +178,8 @@ constructor(private val context: Context) { ) val shortcutLabelToKeyGestureTypeMap: Map<String, Int> get() = gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({ get() = gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({ context.getString(it.value) }) { it.key Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +36 −62 Original line number Diff line number Diff line Loading @@ -18,36 +18,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.createKeyTrigger import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS 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 import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.ALL_SUPPORTED_MODIFIERS import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations 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.data.source.TestShortcuts.goHomeInputGestureData import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddCustomShortcutRequestInfo import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardDeleteCustomShortcutRequestInfo import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination 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 @@ -74,24 +69,21 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } private val fakeInputManager = kosmos.fakeInputManager private val inputManager = kosmos.fakeInputManager.inputManager private val testScope = kosmos.testScope private val helper = kosmos.shortcutHelperTestHelper private val repo = kosmos.customShortcutCategoriesRepository @Before fun setup() { whenever(mockUserContext.getSystemService(INPUT_SERVICE)) .thenReturn(fakeInputManager.inputManager) whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) } @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_emitsCorrectlyConvertedShortcutCategories() { testScope.runTest { whenever( fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) ) whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull())) .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations) helper.toggle(deviceId = 123) Loading @@ -106,9 +98,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { @DisableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_emitsEmptyListWhenFlagIsDisabled() { testScope.runTest { whenever( fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) ) whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull())) .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations) helper.toggle(deviceId = 123) Loading @@ -122,9 +112,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_ignoresUnknownKeyGestureTypes() { testScope.runTest { whenever( fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()) ) whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull())) .thenReturn(customizableInputGestureWithUnknownKeyGestureType) helper.toggle(deviceId = 123) Loading @@ -151,7 +139,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { helper.toggle(deviceId = 123) val pressedKeys by collectLastValue(repo.pressedKeys) repo.updateUserKeyCombination( KeyCombination(modifiers = allSupportedModifiers, keyCode = null) KeyCombination(modifiers = ALL_SUPPORTED_MODIFIERS, keyCode = null) ) assertThat(pressedKeys) Loading Loading @@ -199,11 +187,11 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { @Test fun shortcutBeingCustomized_updatedOnCustomizationRequested() { testScope.runTest { repo.onCustomizationRequested(standardCustomizationRequestInfo) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) val shortcutBeingCustomized = repo.getShortcutBeingCustomized() assertThat(shortcutBeingCustomized).isEqualTo(standardCustomizationRequestInfo) assertThat(shortcutBeingCustomized).isEqualTo(standardAddCustomShortcutRequestInfo) } } Loading @@ -223,7 +211,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { fun buildInputGestureDataForShortcutBeingCustomized_noKeyCombinationSelected_returnsNull() { testScope.runTest { helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardCustomizationRequestInfo) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized() Loading @@ -235,46 +223,32 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { fun buildInputGestureDataForShortcutBeingCustomized_successfullyBuildInputGestureData() { testScope.runTest { helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardCustomizationRequestInfo) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) 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()) assertThat(inputGestureData.toString()).isEqualTo(allAppsInputGestureData.toString()) } } private val standardCustomizationRequestInfo = ShortcutCustomizationRequestInfo.Add( label = "Open apps list", categoryType = ShortcutCategoryType.System, subCategoryLabel = "System controls", ) @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun deleteShortcut_successfullyRetrievesGestureDataAndDeletesShortcut() { testScope.runTest { whenever(inputManager.getCustomInputGestures(anyOrNull())) .thenReturn(listOf(allAppsInputGestureData, goHomeInputGestureData)) whenever(inputManager.removeCustomInputGesture(allAppsInputGestureData)) .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) 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, ) helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardDeleteCustomShortcutRequestInfo) private val allSupportedModifiers = META_META_ON or META_CTRL_ON or META_FUNCTION_ON or 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() val result = repo.deleteShortcutCurrentlyBeingCustomized() assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS) } } }
packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt +122 −0 Original line number Diff line number Diff line Loading @@ -19,9 +19,23 @@ package com.android.systemui.keyboard.shortcut.data.source import android.hardware.input.InputGestureData import android.hardware.input.InputGestureData.createKeyTrigger import android.hardware.input.KeyGestureEvent import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME import android.os.SystemClock import android.view.KeyEvent import android.view.KeyEvent.ACTION_DOWN import android.view.KeyEvent.KEYCODE_A import android.view.KeyEvent.META_ALT_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 android.view.KeyboardShortcutGroup import android.view.KeyboardShortcutInfo import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType Loading @@ -29,9 +43,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.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.shared.model.shortcut import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.res.R object TestShortcuts { Loading Loading @@ -525,4 +541,110 @@ object TestShortcuts { keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER ), ) val standardAddCustomShortcutRequestInfo = ShortcutCustomizationRequestInfo.Add( label = "Open apps list", categoryType = System, subCategoryLabel = "System controls", ) val standardDeleteCustomShortcutRequestInfo = ShortcutCustomizationRequestInfo.Delete( label = "Open apps list", categoryType = System, subCategoryLabel = "System controls", ) val standardKeyCombination = KeyCombination( modifiers = META_META_ON or META_SHIFT_ON or META_META_LEFT_ON or META_SHIFT_RIGHT_ON, keyCode = KEYCODE_A, ) const val ALL_SUPPORTED_MODIFIERS = META_META_ON or META_CTRL_ON or META_FUNCTION_ON or META_SHIFT_ON or META_ALT_ON or META_SYM_ON val allAppsInputGestureData: InputGestureData = InputGestureData.Builder() .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS) .setTrigger( createKeyTrigger( /* keycode = */ standardKeyCombination.keyCode!!, /* modifierState = */ standardKeyCombination.modifiers and ALL_SUPPORTED_MODIFIERS, ) ) .build() val goHomeInputGestureData: InputGestureData = InputGestureData.Builder() .setKeyGestureType(KEY_GESTURE_TYPE_HOME) .setTrigger( createKeyTrigger( /* keycode = */ standardKeyCombination.keyCode!!, /* modifierState = */ standardKeyCombination.modifiers and ALL_SUPPORTED_MODIFIERS, ) ) .build() val expectedStandardDeleteShortcutUiState = ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false) val keyDownEventWithoutActionKeyPressed = androidx.compose.ui.input.key.KeyEvent( android.view.KeyEvent( /* downTime = */ SystemClock.uptimeMillis(), /* eventTime = */ SystemClock.uptimeMillis(), /* action = */ ACTION_DOWN, /* code = */ KEYCODE_A, /* repeat = */ 0, /* metaState = */ META_CTRL_ON, ) ) val keyDownEventWithActionKeyPressed = androidx.compose.ui.input.key.KeyEvent( android.view.KeyEvent( /* downTime = */ SystemClock.uptimeMillis(), /* eventTime = */ SystemClock.uptimeMillis(), /* action = */ ACTION_DOWN, /* code = */ KEYCODE_A, /* repeat = */ 0, /* metaState = */ META_CTRL_ON or META_META_ON, ) ) val keyUpEventWithActionKeyPressed = androidx.compose.ui.input.key.KeyEvent( android.view.KeyEvent( /* downTime = */ SystemClock.uptimeMillis(), /* eventTime = */ SystemClock.uptimeMillis(), /* action = */ ACTION_DOWN, /* code = */ KEYCODE_A, /* repeat = */ 0, /* metaState = */ 0, ) ) val standardAddShortcutRequest = ShortcutCustomizationRequestInfo.Add( label = "Standard shortcut", categoryType = ShortcutCategoryType.System, subCategoryLabel = "Standard subcategory", ) val expectedStandardAddShortcutUiState = ShortcutCustomizationUiState.AddShortcutDialog( shortcutLabel = "Standard shortcut", defaultCustomShortcutModifierKey = ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta), isDialogShowing = false, ) }
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt +80 −25 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RES import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.InputSettings import android.hardware.input.KeyGestureEvent import android.hardware.input.KeyGestureEvent.KeyGestureType import android.util.Log import androidx.annotation.VisibleForTesting import androidx.compose.runtime.mutableStateOf Loading @@ -44,6 +45,8 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomization 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 kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow Loading @@ -52,8 +55,6 @@ 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 @@ -65,7 +66,7 @@ constructor( @Background private val bgCoroutineContext: CoroutineContext, private val shortcutCategoriesUtils: ShortcutCategoriesUtils, private val context: Context, private val inputGestureMaps: InputGestureMaps private val inputGestureMaps: InputGestureMaps, ) : ShortcutCategoriesRepository { private val userContext: Context Loading Loading @@ -130,9 +131,7 @@ constructor( emptyList() } else { val customInputGesturesForUser: List<InputGestureData> = if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) { inputManager.getCustomInputGestures(/* filter= */ null) } else emptyList() getCustomInputGestures() val sources = toInternalGroupSources(customInputGesturesForUser) val supportedKeyCodes = shortcutCategoriesUtils.fetchSupportedKeyCodes( Loading Loading @@ -173,16 +172,20 @@ constructor( .addKeyGestureTypeFromShortcutLabel() .addTriggerFromSelectedKeyCombination() .build() // TODO(b/379648200) add app launch data for application categories shortcut after // dynamic // label/icon mapping implementation // TODO(b/379648200) add app launch data after dynamic label/icon mapping implementation } catch (e: IllegalArgumentException) { Log.w(TAG, "could not add custom shortcut: $e") return null } } suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult { private fun retrieveInputGestureDataForShortcutBeingDeleted(): InputGestureData? { val keyGestureType = getKeyGestureTypeFromShortcutBeingDeletedLabel() return getCustomInputGestures().firstOrNull { it.action.keyGestureType() == keyGestureType } } suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext) { val inputGestureData = buildInputGestureDataForShortcutBeingCustomized() Loading @@ -201,24 +204,79 @@ constructor( } } suspend fun deleteShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext) { val inputGestureData = retrieveInputGestureDataForShortcutBeingDeleted() ?: return@withContext ShortcutCustomizationRequestResult.ERROR_OTHER return@withContext when ( val result = inputManager.removeCustomInputGesture(inputGestureData) ) { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> ShortcutCustomizationRequestResult.SUCCESS else -> { Log.w( TAG, "Attempted to delete shortcut being customized " + "${_shortcutBeingCustomized.value} but ran into an error. InputGestureData" + " = $inputGestureData, error code: $result", ) ShortcutCustomizationRequestResult.ERROR_OTHER } } } } private fun getCustomInputGestures(): List<InputGestureData> { return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) { inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) } else emptyList() } private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder { val keyGestureType = getKeyGestureTypeFromShortcutBeingCustomizedLabel() if (keyGestureType == null) { Log.w( TAG, "Could not find KeyGestureType for shortcut ${_shortcutBeingCustomized.value}", ) return this } return setKeyGestureType(keyGestureType) } @KeyGestureType private fun getKeyGestureTypeFromShortcutBeingCustomizedLabel(): Int? { 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 Log.w( TAG, "Requested key gesture type from label but shortcut being customized is null", ) return null } val keyGestureType = inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label] return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label] } if (keyGestureType == null) { Log.w(TAG, "Could not find KeyGestureType for shortcut $shortcutBeingCustomized") return this @KeyGestureType private fun getKeyGestureTypeFromShortcutBeingDeletedLabel(): Int? { val shortcutBeingCustomized = getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Delete if (shortcutBeingCustomized == null) { Log.w( TAG, "Requested key gesture type from label but shortcut being customized is null", ) return null } return setKeyGestureType(keyGestureType) return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label] } private fun Builder.addTriggerFromSelectedKeyCombination(): Builder { Loading @@ -235,8 +293,7 @@ constructor( return setTrigger( createKeyTrigger( /* keycode = */ selectedKeyCombination.keyCode, /* modifierState = */ shortcutCategoriesUtils.removeUnsupportedModifiers( /* modifierState = */ shortcutCategoriesUtils.removeUnsupportedModifiers( selectedKeyCombination.modifiers ), ) Loading @@ -256,10 +313,8 @@ 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
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt +7 −8 Original line number Diff line number Diff line Loading @@ -48,9 +48,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType. import com.android.systemui.res.R import javax.inject.Inject class InputGestureMaps @Inject constructor(private val context: Context) { class InputGestureMaps @Inject constructor(private val context: Context) { val gestureToShortcutCategoryTypeMap = mapOf( // System Category Loading Loading @@ -180,7 +178,8 @@ constructor(private val context: Context) { ) val shortcutLabelToKeyGestureTypeMap: Map<String, Int> get() = gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({ get() = gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({ context.getString(it.value) }) { it.key Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading