Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt 0 → 100644 +118 −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.shortcut.data.repository import android.content.Context import android.content.Context.INPUT_SERVICE import android.hardware.input.InputGestureData import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.fakeInputManager import android.platform.test.annotations.EnableFlags 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.shortcut.customInputGesturesRepository import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData import com.android.systemui.kosmos.testScope import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.userTracker import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat 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 @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) class CustomInputGesturesRepositoryTest : SysuiTestCase() { private val mockUserContext: Context = mock() private val kosmos = testKosmos().also { it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } private val inputManager = kosmos.fakeInputManager.inputManager private val testScope = kosmos.testScope private val customInputGesturesRepository = kosmos.customInputGesturesRepository @Before fun setup(){ whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) } @Test fun customInputGestures_initialValueReturnsDataFromAPI() { testScope.runTest { val customInputGestures = listOf(allAppsInputGestureData) whenever( inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) ).then { return@then customInputGestures } val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures) assertThat(inputGestures).containsExactly(allAppsInputGestureData) } } @Test fun customInputGestures_isUpdatedToMostRecentDataAfterNewGestureIsAdded() { testScope.runTest { var customInputGestures = listOf<InputGestureData>() whenever( inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) ).then { return@then customInputGestures } whenever(inputManager.addCustomInputGesture(any())).then { invocation -> val inputGesture = invocation.getArgument<InputGestureData>(0) customInputGestures = customInputGestures + inputGesture return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS } val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures) assertThat(inputGestures).isEmpty() customInputGesturesRepository.addCustomInputGesture(allAppsInputGestureData) assertThat(inputGestures).containsExactly(allAppsInputGestureData) } } @Test fun retrieveCustomInputGestures_retrievesMostRecentData() { testScope.runTest { var customInputGestures = listOf<InputGestureData>() whenever( inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) ).then { return@then customInputGestures } assertThat(customInputGesturesRepository.retrieveCustomInputGestures()).isEmpty() customInputGestures = listOf(allAppsInputGestureData) assertThat(customInputGesturesRepository.retrieveCustomInputGestures()) .containsExactly(allAppsInputGestureData) } } } No newline at end of file packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +81 −9 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ 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.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.fakeInputManager import android.platform.test.annotations.DisableFlags Loading @@ -34,15 +36,19 @@ 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.allAppsShortcutAddRequest 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.allAppsShortcutCategory 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.allAppsShortcutDeleteRequest 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.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Add import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Delete import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.kosmos.testScope Loading @@ -55,6 +61,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.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.whenever Loading Loading @@ -187,11 +194,11 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { @Test fun shortcutBeingCustomized_updatedOnCustomizationRequested() { testScope.runTest { repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) repo.onCustomizationRequested(allAppsShortcutAddRequest) val shortcutBeingCustomized = repo.getShortcutBeingCustomized() assertThat(shortcutBeingCustomized).isEqualTo(standardAddCustomShortcutRequestInfo) assertThat(shortcutBeingCustomized).isEqualTo(allAppsShortcutAddRequest) } } Loading @@ -211,7 +218,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { fun buildInputGestureDataForShortcutBeingCustomized_noKeyCombinationSelected_returnsNull() { testScope.runTest { helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) repo.onCustomizationRequested(allAppsShortcutAddRequest) val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized() Loading @@ -223,7 +230,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { fun buildInputGestureDataForShortcutBeingCustomized_successfullyBuildInputGestureData() { testScope.runTest { helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) repo.onCustomizationRequested(allAppsShortcutAddRequest) repo.updateUserKeyCombination(standardKeyCombination) val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized() Loading @@ -242,13 +249,78 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { .thenReturn(listOf(allAppsInputGestureData, goHomeInputGestureData)) whenever(inputManager.removeCustomInputGesture(allAppsInputGestureData)) .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) helper.toggle(deviceId = 123) val result = customizeShortcut(allAppsShortcutDeleteRequest) assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS) } } @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_isUpdatedAfterCustomShortcutIsDeleted() { testScope.runTest { // TODO(b/380445594) refactor tests and move these stubbing to ShortcutHelperTestHelper var customInputGestures = listOf(allAppsInputGestureData) whenever(inputManager.getCustomInputGestures(anyOrNull())).then { return@then customInputGestures } whenever(inputManager.removeCustomInputGesture(any())).then { val inputGestureToRemove = it.getArgument<InputGestureData>(0) val containsGesture = customInputGestures.contains(inputGestureToRemove) customInputGestures = customInputGestures - inputGestureToRemove return@then if (containsGesture) CUSTOM_INPUT_GESTURE_RESULT_SUCCESS else CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST } val categories by collectLastValue(repo.categories) helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardDeleteCustomShortcutRequestInfo) val result = repo.deleteShortcutCurrentlyBeingCustomized() customizeShortcut(customizationRequest = allAppsShortcutDeleteRequest) assertThat(categories).isEmpty() } } assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS) @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_isUpdatedAfterCustomShortcutIsAdded() { testScope.runTest { // TODO(b/380445594) refactor tests and move these stubbings to ShortcutHelperTestHelper var customInputGestures = listOf<InputGestureData>() whenever(inputManager.getCustomInputGestures(anyOrNull())).then { return@then customInputGestures } whenever(inputManager.addCustomInputGesture(any())).then { val inputGestureToAdd = it.getArgument<InputGestureData>(0) customInputGestures = customInputGestures + inputGestureToAdd return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS } val categories by collectLastValue(repo.categories) helper.toggle(deviceId = 123) customizeShortcut(allAppsShortcutAddRequest, standardKeyCombination) assertThat(categories).containsExactly(allAppsShortcutCategory) } } private suspend fun customizeShortcut( customizationRequest: ShortcutCustomizationRequestInfo, keyCombination: KeyCombination? = null ): ShortcutCustomizationRequestResult{ repo.onCustomizationRequested(customizationRequest) repo.updateUserKeyCombination(keyCombination) return when (customizationRequest) { is Add -> { repo.confirmAndSetShortcutCurrentlyBeingCustomized() } is Delete -> { repo.deleteShortcutCurrentlyBeingCustomized() } else -> { ShortcutCustomizationRequestResult.ERROR_OTHER } } } } packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt +18 −2 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ 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.data.repository.ShortcutHelperKeys 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 Loading @@ -47,6 +48,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomization 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.shared.model.shortcutCategory import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.res.R Loading Loading @@ -558,14 +560,14 @@ object TestShortcuts { ), ) val standardAddCustomShortcutRequestInfo = val allAppsShortcutAddRequest = ShortcutCustomizationRequestInfo.Add( label = "Open apps list", categoryType = System, subCategoryLabel = "System controls", ) val standardDeleteCustomShortcutRequestInfo = val allAppsShortcutDeleteRequest = ShortcutCustomizationRequestInfo.Delete( label = "Open apps list", categoryType = System, Loading Loading @@ -610,6 +612,20 @@ object TestShortcuts { ) .build() val allAppsShortcutCategory = shortcutCategory(System) { subCategory("System controls") { shortcut("Open apps list") { command { isCustom(true) key(ShortcutHelperKeys.metaModifierIconResId) key("Shift") key("A") } } } } val expectedStandardDeleteShortcutUiState = ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false) Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt +7 −7 Original line number Diff line number Diff line Loading @@ -34,9 +34,9 @@ import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeIn import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddCustomShortcutRequestInfo import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddShortcutRequest import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardDeleteCustomShortcutRequestInfo import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper Loading Loading @@ -99,7 +99,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { @Test fun uiState_correctlyUpdatedWhenDeleteShortcutCustomizationIsRequested() { testScope.runTest { viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) assertThat(uiState).isEqualTo(expectedStandardDeleteShortcutUiState) Loading @@ -122,7 +122,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { fun uiState_consumedOnDeleteDialogShown() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) viewModel.onDialogShown() assertThat( Loading Loading @@ -170,7 +170,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { fun uiState_errorMessage_isEmptyByDefault() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest) viewModel.onDialogShown() assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage) Loading Loading @@ -298,7 +298,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } private suspend fun openAddShortcutDialogAndSetShortcut() { viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest) viewModel.onDialogShown() viewModel.onKeyPressed(keyDownEventWithActionKeyPressed) Loading @@ -308,7 +308,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } private suspend fun openDeleteShortcutDialogAndDeleteShortcut() { viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) viewModel.onDialogShown() viewModel.deleteShortcutCurrentlyBeingCustomized() Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt 0 → 100644 +118 −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.shortcut.data.repository import android.content.Context import android.content.Context.INPUT_SERVICE import android.hardware.input.InputGestureData 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.util.Log import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_OTHER import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.SUCCESS import com.android.systemui.settings.UserTracker import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.coroutines.CoroutineContext class CustomInputGesturesRepository @Inject constructor(private val userTracker: UserTracker, @Background private val bgCoroutineContext: CoroutineContext) { private val userContext: Context get() = userTracker.createCurrentUserContext(userTracker.userContext) // Input manager created with user context to provide correct user id when requesting custom // shortcut private val inputManager: InputManager get() = userContext.getSystemService(INPUT_SERVICE) as InputManager private val _customInputGesture = MutableStateFlow<List<InputGestureData>>(emptyList()) val customInputGestures = _customInputGesture.onStart { refreshCustomInputGestures() } private fun refreshCustomInputGestures() { _customInputGesture.value = retrieveCustomInputGestures() } fun retrieveCustomInputGestures(): List<InputGestureData> { return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) { inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) } else emptyList() } suspend fun addCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext) { when (val result = inputManager.addCustomInputGesture(inputGesture)) { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> { refreshCustomInputGestures() SUCCESS } CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS -> ERROR_RESERVED_COMBINATION CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE -> ERROR_RESERVED_COMBINATION else -> { Log.w( TAG, "Attempted to add inputGesture: $inputGesture " + "but ran into an error with code: $result", ) ERROR_OTHER } } } } suspend fun deleteCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext){ when ( val result = inputManager.removeCustomInputGesture(inputGesture) ) { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> { refreshCustomInputGestures() SUCCESS } else -> { Log.w( TAG, "Attempted to delete inputGesture: $inputGesture " + "but ran into an error with code: $result", ) ERROR_OTHER } } } } private companion object { private const val TAG = "CustomInputGesturesRepository" } } No newline at end of file Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt 0 → 100644 +118 −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.shortcut.data.repository import android.content.Context import android.content.Context.INPUT_SERVICE import android.hardware.input.InputGestureData import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.fakeInputManager import android.platform.test.annotations.EnableFlags 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.shortcut.customInputGesturesRepository import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData import com.android.systemui.kosmos.testScope import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.userTracker import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat 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 @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) class CustomInputGesturesRepositoryTest : SysuiTestCase() { private val mockUserContext: Context = mock() private val kosmos = testKosmos().also { it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } private val inputManager = kosmos.fakeInputManager.inputManager private val testScope = kosmos.testScope private val customInputGesturesRepository = kosmos.customInputGesturesRepository @Before fun setup(){ whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) } @Test fun customInputGestures_initialValueReturnsDataFromAPI() { testScope.runTest { val customInputGestures = listOf(allAppsInputGestureData) whenever( inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) ).then { return@then customInputGestures } val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures) assertThat(inputGestures).containsExactly(allAppsInputGestureData) } } @Test fun customInputGestures_isUpdatedToMostRecentDataAfterNewGestureIsAdded() { testScope.runTest { var customInputGestures = listOf<InputGestureData>() whenever( inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) ).then { return@then customInputGestures } whenever(inputManager.addCustomInputGesture(any())).then { invocation -> val inputGesture = invocation.getArgument<InputGestureData>(0) customInputGestures = customInputGestures + inputGesture return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS } val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures) assertThat(inputGestures).isEmpty() customInputGesturesRepository.addCustomInputGesture(allAppsInputGestureData) assertThat(inputGestures).containsExactly(allAppsInputGestureData) } } @Test fun retrieveCustomInputGestures_retrievesMostRecentData() { testScope.runTest { var customInputGestures = listOf<InputGestureData>() whenever( inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) ).then { return@then customInputGestures } assertThat(customInputGesturesRepository.retrieveCustomInputGestures()).isEmpty() customInputGestures = listOf(allAppsInputGestureData) assertThat(customInputGesturesRepository.retrieveCustomInputGestures()) .containsExactly(allAppsInputGestureData) } } } No newline at end of file
packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt +81 −9 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ 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.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.fakeInputManager import android.platform.test.annotations.DisableFlags Loading @@ -34,15 +36,19 @@ 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.allAppsShortcutAddRequest 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.allAppsShortcutCategory 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.allAppsShortcutDeleteRequest 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.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Add import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Delete import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.kosmos.testScope Loading @@ -55,6 +61,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.anyOrNull import org.mockito.kotlin.mock import org.mockito.kotlin.whenever Loading Loading @@ -187,11 +194,11 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { @Test fun shortcutBeingCustomized_updatedOnCustomizationRequested() { testScope.runTest { repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) repo.onCustomizationRequested(allAppsShortcutAddRequest) val shortcutBeingCustomized = repo.getShortcutBeingCustomized() assertThat(shortcutBeingCustomized).isEqualTo(standardAddCustomShortcutRequestInfo) assertThat(shortcutBeingCustomized).isEqualTo(allAppsShortcutAddRequest) } } Loading @@ -211,7 +218,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { fun buildInputGestureDataForShortcutBeingCustomized_noKeyCombinationSelected_returnsNull() { testScope.runTest { helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) repo.onCustomizationRequested(allAppsShortcutAddRequest) val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized() Loading @@ -223,7 +230,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { fun buildInputGestureDataForShortcutBeingCustomized_successfullyBuildInputGestureData() { testScope.runTest { helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo) repo.onCustomizationRequested(allAppsShortcutAddRequest) repo.updateUserKeyCombination(standardKeyCombination) val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized() Loading @@ -242,13 +249,78 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() { .thenReturn(listOf(allAppsInputGestureData, goHomeInputGestureData)) whenever(inputManager.removeCustomInputGesture(allAppsInputGestureData)) .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS) helper.toggle(deviceId = 123) val result = customizeShortcut(allAppsShortcutDeleteRequest) assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS) } } @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_isUpdatedAfterCustomShortcutIsDeleted() { testScope.runTest { // TODO(b/380445594) refactor tests and move these stubbing to ShortcutHelperTestHelper var customInputGestures = listOf(allAppsInputGestureData) whenever(inputManager.getCustomInputGestures(anyOrNull())).then { return@then customInputGestures } whenever(inputManager.removeCustomInputGesture(any())).then { val inputGestureToRemove = it.getArgument<InputGestureData>(0) val containsGesture = customInputGestures.contains(inputGestureToRemove) customInputGestures = customInputGestures - inputGestureToRemove return@then if (containsGesture) CUSTOM_INPUT_GESTURE_RESULT_SUCCESS else CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST } val categories by collectLastValue(repo.categories) helper.toggle(deviceId = 123) repo.onCustomizationRequested(standardDeleteCustomShortcutRequestInfo) val result = repo.deleteShortcutCurrentlyBeingCustomized() customizeShortcut(customizationRequest = allAppsShortcutDeleteRequest) assertThat(categories).isEmpty() } } assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS) @Test @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun categories_isUpdatedAfterCustomShortcutIsAdded() { testScope.runTest { // TODO(b/380445594) refactor tests and move these stubbings to ShortcutHelperTestHelper var customInputGestures = listOf<InputGestureData>() whenever(inputManager.getCustomInputGestures(anyOrNull())).then { return@then customInputGestures } whenever(inputManager.addCustomInputGesture(any())).then { val inputGestureToAdd = it.getArgument<InputGestureData>(0) customInputGestures = customInputGestures + inputGestureToAdd return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS } val categories by collectLastValue(repo.categories) helper.toggle(deviceId = 123) customizeShortcut(allAppsShortcutAddRequest, standardKeyCombination) assertThat(categories).containsExactly(allAppsShortcutCategory) } } private suspend fun customizeShortcut( customizationRequest: ShortcutCustomizationRequestInfo, keyCombination: KeyCombination? = null ): ShortcutCustomizationRequestResult{ repo.onCustomizationRequested(customizationRequest) repo.updateUserKeyCombination(keyCombination) return when (customizationRequest) { is Add -> { repo.confirmAndSetShortcutCurrentlyBeingCustomized() } is Delete -> { repo.deleteShortcutCurrentlyBeingCustomized() } else -> { ShortcutCustomizationRequestResult.ERROR_OTHER } } } }
packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt +18 −2 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ 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.data.repository.ShortcutHelperKeys 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 Loading @@ -47,6 +48,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomization 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.shared.model.shortcutCategory import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.res.R Loading Loading @@ -558,14 +560,14 @@ object TestShortcuts { ), ) val standardAddCustomShortcutRequestInfo = val allAppsShortcutAddRequest = ShortcutCustomizationRequestInfo.Add( label = "Open apps list", categoryType = System, subCategoryLabel = "System controls", ) val standardDeleteCustomShortcutRequestInfo = val allAppsShortcutDeleteRequest = ShortcutCustomizationRequestInfo.Delete( label = "Open apps list", categoryType = System, Loading Loading @@ -610,6 +612,20 @@ object TestShortcuts { ) .build() val allAppsShortcutCategory = shortcutCategory(System) { subCategory("System controls") { shortcut("Open apps list") { command { isCustom(true) key(ShortcutHelperKeys.metaModifierIconResId) key("Shift") key("A") } } } } val expectedStandardDeleteShortcutUiState = ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false) Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt +7 −7 Original line number Diff line number Diff line Loading @@ -34,9 +34,9 @@ import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeIn import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddCustomShortcutRequestInfo import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddShortcutRequest import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardDeleteCustomShortcutRequestInfo import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper Loading Loading @@ -99,7 +99,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { @Test fun uiState_correctlyUpdatedWhenDeleteShortcutCustomizationIsRequested() { testScope.runTest { viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) assertThat(uiState).isEqualTo(expectedStandardDeleteShortcutUiState) Loading @@ -122,7 +122,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { fun uiState_consumedOnDeleteDialogShown() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) viewModel.onDialogShown() assertThat( Loading Loading @@ -170,7 +170,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { fun uiState_errorMessage_isEmptyByDefault() { testScope.runTest { val uiState by collectLastValue(viewModel.shortcutCustomizationUiState) viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest) viewModel.onDialogShown() assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage) Loading Loading @@ -298,7 +298,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } private suspend fun openAddShortcutDialogAndSetShortcut() { viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest) viewModel.onDialogShown() viewModel.onKeyPressed(keyDownEventWithActionKeyPressed) Loading @@ -308,7 +308,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() { } private suspend fun openDeleteShortcutDialogAndDeleteShortcut() { viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo) viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest) viewModel.onDialogShown() viewModel.deleteShortcutCurrentlyBeingCustomized() Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt 0 → 100644 +118 −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.shortcut.data.repository import android.content.Context import android.content.Context.INPUT_SERVICE import android.hardware.input.InputGestureData 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.util.Log import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_OTHER import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.SUCCESS import com.android.systemui.settings.UserTracker import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.coroutines.CoroutineContext class CustomInputGesturesRepository @Inject constructor(private val userTracker: UserTracker, @Background private val bgCoroutineContext: CoroutineContext) { private val userContext: Context get() = userTracker.createCurrentUserContext(userTracker.userContext) // Input manager created with user context to provide correct user id when requesting custom // shortcut private val inputManager: InputManager get() = userContext.getSystemService(INPUT_SERVICE) as InputManager private val _customInputGesture = MutableStateFlow<List<InputGestureData>>(emptyList()) val customInputGestures = _customInputGesture.onStart { refreshCustomInputGestures() } private fun refreshCustomInputGestures() { _customInputGesture.value = retrieveCustomInputGestures() } fun retrieveCustomInputGestures(): List<InputGestureData> { return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) { inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) } else emptyList() } suspend fun addCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext) { when (val result = inputManager.addCustomInputGesture(inputGesture)) { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> { refreshCustomInputGestures() SUCCESS } CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS -> ERROR_RESERVED_COMBINATION CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE -> ERROR_RESERVED_COMBINATION else -> { Log.w( TAG, "Attempted to add inputGesture: $inputGesture " + "but ran into an error with code: $result", ) ERROR_OTHER } } } } suspend fun deleteCustomInputGesture(inputGesture: InputGestureData): ShortcutCustomizationRequestResult { return withContext(bgCoroutineContext){ when ( val result = inputManager.removeCustomInputGesture(inputGesture) ) { CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> { refreshCustomInputGestures() SUCCESS } else -> { Log.w( TAG, "Attempted to delete inputGesture: $inputGesture " + "but ran into an error with code: $result", ) ERROR_OTHER } } } } private companion object { private const val TAG = "CustomInputGesturesRepository" } } No newline at end of file