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

Commit 0b5c0717 authored by Joshua Mokut's avatar Joshua Mokut Committed by Android (Google) Code Review
Browse files

Merge "Added data refresh after new shortcut is added/deleted" into main

parents 00ea92a5 c06a7fd4
Loading
Loading
Loading
Loading
+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
+81 −9
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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)
        }
    }

@@ -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()

@@ -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()

@@ -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
            }
        }
    }
}
+18 −2
Original line number Diff line number Diff line
@@ -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
@@ -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

@@ -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,
@@ -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)

+7 −7
Original line number Diff line number Diff line
@@ -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
@@ -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)
@@ -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(
@@ -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)
@@ -298,7 +298,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
    }

    private suspend fun openAddShortcutDialogAndSetShortcut() {
        viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo)
        viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
        viewModel.onDialogShown()

        viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
@@ -308,7 +308,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
    }

    private suspend fun openDeleteShortcutDialogAndDeleteShortcut() {
        viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
        viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest)
        viewModel.onDialogShown()

        viewModel.deleteShortcutCurrentlyBeingCustomized()
+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