summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt82
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shared/model/ShortcutCustomizationRequestResult.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt (renamed from packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt)13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt98
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt12
13 files changed, 437 insertions, 38 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index 27b8c59a076d..f04540426fc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -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()
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 372f6a506287..3dd49bfed385 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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>
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shared/model/ShortcutCustomizationRequestResult.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shared/model/ShortcutCustomizationRequestResult.kt
new file mode 100644
index 000000000000..bb563b1f6561
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shared/model/ShortcutCustomizationRequestResult.kt
@@ -0,0 +1,23 @@
+/*
+ * 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,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index da5590ae27fa..99cafd3daacb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -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"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index 7bb294df98cd..d228a15e51b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -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
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
index 3988d1f155bd..95bc9f66618c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
@@ -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>,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
index aad55dc11c16..f4e2f05379bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -16,9 +16,11 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import javax.inject.Inject
@@ -34,4 +36,12 @@ constructor(private val customShortcutRepository: CustomShortcutCategoriesReposi
fun getDefaultCustomShortcutModifierKey(): ShortcutKey.Icon.ResIdIcon {
return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.metaModifierIconResId)
}
+
+ fun onCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo?) {
+ customShortcutRepository.onCustomizationRequested(requestInfo)
+ }
+
+ suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+ return customShortcutRepository.confirmAndSetShortcutCurrentlyBeingCustomized()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index 2cb822eb0609..310078a647e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -35,6 +36,7 @@ import com.android.systemui.statusbar.phone.create
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
class ShortcutCustomizationDialogStarter
@AssistedInject
@@ -70,14 +72,21 @@ constructor(
return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog
->
val uiState by
- viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle(
- initialValue = ShortcutCustomizationUiState.Inactive
- )
+ viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle(
+ initialValue = ShortcutCustomizationUiState.Inactive
+ )
+ val coroutineScope = rememberCoroutineScope()
AssignNewShortcutDialog(
uiState = uiState,
- modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp),
+ modifier = Modifier
+ .width(364.dp)
+ .wrapContentHeight()
+ .padding(vertical = 24.dp),
onKeyPress = { viewModel.onKeyPressed(it) },
onCancel = { dialog.dismiss() },
+ onConfirmSetShortcut = {
+ coroutineScope.launch { viewModel.onSetShortcut() }
+ },
)
dialog.setOnDismissListener { viewModel.onDialogDismissed() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index d7229336c0d7..c9b778e96dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -66,6 +66,7 @@ fun AssignNewShortcutDialog(
modifier: Modifier = Modifier,
onKeyPress: (KeyEvent) -> Boolean,
onCancel: () -> Unit,
+ onConfirmSetShortcut: () -> Unit,
) {
if (uiState is ShortcutCustomizationUiState.AddShortcutDialog) {
Column(modifier = modifier) {
@@ -84,18 +85,26 @@ fun AssignNewShortcutDialog(
defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
)
SelectedKeyCombinationContainer(
- shouldShowErrorMessage = uiState.shouldShowErrorMessage,
+ shouldShowError = uiState.errorMessage.isNotEmpty(),
onKeyPress = onKeyPress,
pressedKeys = uiState.pressedKeys,
)
- KeyCombinationAlreadyInUseErrorMessage(uiState.shouldShowErrorMessage)
- DialogButtons(onCancel, isSetShortcutButtonEnabled = uiState.pressedKeys.isNotEmpty())
+ ErrorMessageContainer(uiState.errorMessage)
+ DialogButtons(
+ onCancel,
+ isSetShortcutButtonEnabled = uiState.pressedKeys.isNotEmpty(),
+ onConfirm = onConfirmSetShortcut,
+ )
}
}
}
@Composable
-fun DialogButtons(onCancel: () -> Unit, isSetShortcutButtonEnabled: Boolean) {
+fun DialogButtons(
+ onCancel: () -> Unit,
+ isSetShortcutButtonEnabled: Boolean,
+ onConfirm: () -> Unit,
+) {
Row(
modifier =
Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp)
@@ -113,7 +122,7 @@ fun DialogButtons(onCancel: () -> Unit, isSetShortcutButtonEnabled: Boolean) {
)
Spacer(modifier = Modifier.width(8.dp))
ShortcutHelperButton(
- onClick = {},
+ onClick = onConfirm,
color = MaterialTheme.colorScheme.primary,
width = 116.dp,
contentColor = MaterialTheme.colorScheme.onPrimary,
@@ -125,11 +134,11 @@ fun DialogButtons(onCancel: () -> Unit, isSetShortcutButtonEnabled: Boolean) {
}
@Composable
-fun KeyCombinationAlreadyInUseErrorMessage(shouldShowErrorMessage: Boolean) {
- if (shouldShowErrorMessage) {
+fun ErrorMessageContainer(errorMessage: String) {
+ if (errorMessage.isNotEmpty()) {
Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) {
Text(
- text = stringResource(R.string.shortcut_helper_customize_dialog_error_message),
+ text = errorMessage,
style = MaterialTheme.typography.bodyMedium,
fontSize = 14.sp,
lineHeight = 20.sp,
@@ -143,7 +152,7 @@ fun KeyCombinationAlreadyInUseErrorMessage(shouldShowErrorMessage: Boolean) {
@Composable
fun SelectedKeyCombinationContainer(
- shouldShowErrorMessage: Boolean,
+ shouldShowError: Boolean,
onKeyPress: (KeyEvent) -> Boolean,
pressedKeys: List<ShortcutKey>,
) {
@@ -151,7 +160,7 @@ fun SelectedKeyCombinationContainer(
val isFocused by interactionSource.collectIsFocusedAsState()
val outlineColor =
if (!isFocused) MaterialTheme.colorScheme.outline
- else if (shouldShowErrorMessage) MaterialTheme.colorScheme.error
+ else if (shouldShowError) MaterialTheme.colorScheme.error
else MaterialTheme.colorScheme.primary
val focusRequester = remember { FocusRequester() }
@@ -179,7 +188,7 @@ fun SelectedKeyCombinationContainer(
PressedKeysTextContainer(pressedKeys)
}
Spacer(modifier = Modifier.weight(1f))
- if (shouldShowErrorMessage) {
+ if (shouldShowError) {
Icon(
imageVector = Icons.Default.ErrorOutline,
contentDescription = null,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
index 552c53dff6a0..adadeebd0769 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
@@ -21,7 +21,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
sealed interface ShortcutCustomizationUiState {
data class AddShortcutDialog(
val shortcutLabel: String,
- val shouldShowErrorMessage: Boolean,
+ val errorMessage: String = "",
val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon,
val isDialogShowing: Boolean,
val pressedKeys: List<ShortcutKey> = emptyList(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index 2455ce49033d..8178c6a1c705 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
-import androidx.compose.runtime.mutableStateOf
+import android.content.Context
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
@@ -24,10 +24,12 @@ import androidx.compose.ui.input.key.isMetaPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.nativeKeyCode
import androidx.compose.ui.input.key.type
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.res.R
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,9 +39,10 @@ import kotlinx.coroutines.flow.update
class ShortcutCustomizationViewModel
@AssistedInject
-constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor) {
- private val _shortcutBeingCustomized = mutableStateOf<ShortcutCustomizationRequestInfo?>(null)
-
+constructor(
+ private val context: Context,
+ private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor,
+) {
private val _shortcutCustomizationUiState =
MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive)
@@ -65,13 +68,12 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn
_shortcutCustomizationUiState.value =
ShortcutCustomizationUiState.AddShortcutDialog(
shortcutLabel = requestInfo.label,
- shouldShowErrorMessage = false,
defaultCustomShortcutModifierKey =
shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
isDialogShowing = false,
pressedKeys = emptyList(),
)
- _shortcutBeingCustomized.value = requestInfo
+ shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
}
}
}
@@ -85,8 +87,8 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn
}
fun onDialogDismissed() {
- _shortcutBeingCustomized.value = null
_shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive
+ shortcutCustomizationInteractor.onCustomizationRequested(null)
shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null)
}
@@ -98,6 +100,40 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn
return false
}
+ suspend fun onSetShortcut() {
+ val result = shortcutCustomizationInteractor.confirmAndSetShortcutCurrentlyBeingCustomized()
+
+ _shortcutCustomizationUiState.update { uiState ->
+ when (result) {
+ ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive
+ ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION -> {
+ getUiStateWithErrorMessage(
+ uiState = uiState,
+ errorMessage =
+ context.getString(
+ R.string.shortcut_customizer_key_combination_in_use_error_message
+ ),
+ )
+ }
+ ShortcutCustomizationRequestResult.ERROR_OTHER ->
+ getUiStateWithErrorMessage(
+ uiState = uiState,
+ errorMessage =
+ context.getString(R.string.shortcut_customizer_generic_error_message),
+ )
+ }
+ }
+ }
+
+ private fun getUiStateWithErrorMessage(
+ uiState: ShortcutCustomizationUiState,
+ errorMessage: String,
+ ): ShortcutCustomizationUiState {
+ return (uiState as? ShortcutCustomizationUiState.AddShortcutDialog)?.copy(
+ errorMessage = errorMessage
+ ) ?: uiState
+ }
+
private fun updatePressedKeys(keyEvent: KeyEvent) {
val isModifier = SUPPORTED_MODIFIERS.contains(keyEvent.key)
val keyCombination =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index c6f01ea9af19..85ab746211d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -18,6 +18,10 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel
import android.content.Context
import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER
+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.fakeInputManager
import android.os.SystemClock
import android.view.KeyEvent.ACTION_DOWN
@@ -46,6 +50,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -123,6 +128,81 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
@Test
+ fun uiState_becomeInactiveAfterSuccessfullySettingShortcut() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ whenever(inputManager.addCustomInputGesture(any()))
+ .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
+
+ openAddShortcutDialogAndSetShortcut()
+
+ assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+ }
+ }
+
+ @Test
+ fun uiState_errorMessage_isEmptyByDefault() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ viewModel.onShortcutCustomizationRequested(allAppsShortcutCustomizationRequest)
+ viewModel.onAddShortcutDialogShown()
+
+ assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ .isEmpty()
+ }
+ }
+
+ @Test
+ fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationAlreadyExists() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ whenever(inputManager.addCustomInputGesture(any()))
+ .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS)
+
+ openAddShortcutDialogAndSetShortcut()
+
+ assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ .isEqualTo(
+ context.getString(
+ R.string.shortcut_customizer_key_combination_in_use_error_message
+ )
+ )
+ }
+ }
+
+ @Test
+ fun uiState_errorMessage_isKeyCombinationInUse_whenKeyCombinationIsReserved() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ whenever(inputManager.addCustomInputGesture(any()))
+ .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE)
+
+ openAddShortcutDialogAndSetShortcut()
+
+ assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ .isEqualTo(
+ context.getString(
+ R.string.shortcut_customizer_key_combination_in_use_error_message
+ )
+ )
+ }
+ }
+
+ @Test
+ fun uiState_errorMessage_isGenericError_whenErrorIsUnknown() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ whenever(inputManager.addCustomInputGesture(any()))
+ .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER)
+
+ openAddShortcutDialogAndSetShortcut()
+
+ assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ .isEqualTo(context.getString(R.string.shortcut_customizer_generic_error_message))
+ }
+ }
+
+ @Test
fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
testScope.runTest {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
@@ -176,6 +256,16 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
}
+ private suspend fun openAddShortcutDialogAndSetShortcut() {
+ viewModel.onShortcutCustomizationRequested(allAppsShortcutCustomizationRequest)
+ viewModel.onAddShortcutDialogShown()
+
+ viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
+ viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+
+ viewModel.onSetShortcut()
+ }
+
private val keyDownEventWithoutActionKeyPressed =
KeyEvent(
android.view.KeyEvent(
@@ -219,10 +309,16 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
subCategoryLabel = "Standard subcategory",
)
+ private val allAppsShortcutCustomizationRequest =
+ ShortcutCustomizationRequestInfo.Add(
+ label = "Open apps list",
+ categoryType = ShortcutCategoryType.System,
+ subCategoryLabel = "System controls",
+ )
+
private val expectedStandardAddShortcutUiState =
ShortcutCustomizationUiState.AddShortcutDialog(
shortcutLabel = "Standard shortcut",
- shouldShowErrorMessage = false,
defaultCustomShortcutModifierKey =
ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
isDialogShowing = false,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 9cb15c5b816d..721c0b8339db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -24,6 +24,7 @@ import android.view.windowManager
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.InputGestureMaps
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
@@ -101,6 +102,11 @@ val Kosmos.defaultShortcutCategoriesRepository by
)
}
+val Kosmos.inputGestureMaps by
+ Kosmos.Fixture {
+ InputGestureMaps(applicationContext)
+ }
+
val Kosmos.customShortcutCategoriesRepository by
Kosmos.Fixture {
CustomShortcutCategoriesRepository(
@@ -110,6 +116,7 @@ val Kosmos.customShortcutCategoriesRepository by
testDispatcher,
shortcutCategoriesUtils,
applicationContext,
+ inputGestureMaps
)
}
@@ -173,7 +180,10 @@ val Kosmos.shortcutCustomizationViewModelFactory by
Kosmos.Fixture {
object : ShortcutCustomizationViewModel.Factory {
override fun create(): ShortcutCustomizationViewModel {
- return ShortcutCustomizationViewModel(shortcutCustomizationInteractor)
+ return ShortcutCustomizationViewModel(
+ applicationContext,
+ shortcutCustomizationInteractor,
+ )
}
}
}