1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.keyboard.shortcut.data.repository 18 19 import android.content.Context 20 import android.content.Context.INPUT_SERVICE 21 import android.hardware.input.InputGestureData 22 import android.hardware.input.InputManager 23 import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS 24 import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE 25 import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS 26 import android.hardware.input.InputSettings 27 import android.util.Log 28 import com.android.systemui.dagger.SysUISingleton 29 import com.android.systemui.dagger.qualifiers.Background 30 import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult 31 import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_OTHER 32 import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_RESERVED_COMBINATION 33 import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.SUCCESS 34 import com.android.systemui.settings.UserTracker 35 import javax.inject.Inject 36 import kotlin.coroutines.CoroutineContext 37 import kotlinx.coroutines.flow.MutableStateFlow 38 import kotlinx.coroutines.flow.onStart 39 import kotlinx.coroutines.withContext 40 41 @SysUISingleton 42 class CustomInputGesturesRepository 43 @Inject 44 constructor( 45 private val userTracker: UserTracker, 46 @Background private val bgCoroutineContext: CoroutineContext, 47 ) { 48 49 private val userContext: Context 50 get() = userTracker.createCurrentUserContext(userTracker.userContext) 51 52 // Input manager created with user context to provide correct user id when requesting custom 53 // shortcut 54 private val inputManager: InputManager 55 get() = userContext.getSystemService(INPUT_SERVICE) as InputManager 56 57 private val _customInputGesture = MutableStateFlow<List<InputGestureData>>(emptyList()) 58 <lambda>null59 val customInputGestures = _customInputGesture.onStart { refreshCustomInputGestures() } 60 refreshCustomInputGesturesnull61 fun refreshCustomInputGestures() { 62 setCustomInputGestures(inputGestures = retrieveCustomInputGestures()) 63 } 64 setCustomInputGesturesnull65 private fun setCustomInputGestures(inputGestures: List<InputGestureData>) { 66 _customInputGesture.value = inputGestures 67 } 68 retrieveCustomInputGesturesnull69 fun retrieveCustomInputGestures(): List<InputGestureData> { 70 return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) { 71 inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) 72 } else emptyList() 73 } 74 addCustomInputGesturenull75 suspend fun addCustomInputGesture( 76 inputGesture: InputGestureData 77 ): ShortcutCustomizationRequestResult { 78 return withContext(bgCoroutineContext) { 79 when (val result = inputManager.addCustomInputGesture(inputGesture)) { 80 CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> { 81 refreshCustomInputGestures() 82 SUCCESS 83 } 84 CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS -> ERROR_RESERVED_COMBINATION 85 86 CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE -> ERROR_RESERVED_COMBINATION 87 88 else -> { 89 Log.w( 90 TAG, 91 "Attempted to add inputGesture: $inputGesture " + 92 "but ran into an error with code: $result", 93 ) 94 ERROR_OTHER 95 } 96 } 97 } 98 } 99 deleteCustomInputGesturenull100 suspend fun deleteCustomInputGesture( 101 inputGesture: InputGestureData 102 ): ShortcutCustomizationRequestResult { 103 return withContext(bgCoroutineContext) { 104 when (val result = inputManager.removeCustomInputGesture(inputGesture)) { 105 CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> { 106 refreshCustomInputGestures() 107 SUCCESS 108 } 109 else -> { 110 Log.w( 111 TAG, 112 "Attempted to delete inputGesture: $inputGesture " + 113 "but ran into an error with code: $result", 114 ) 115 ERROR_OTHER 116 } 117 } 118 } 119 } 120 resetAllCustomInputGesturesnull121 suspend fun resetAllCustomInputGestures(): ShortcutCustomizationRequestResult { 122 return withContext(bgCoroutineContext) { 123 try { 124 inputManager.removeAllCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) 125 setCustomInputGestures(emptyList()) 126 SUCCESS 127 } catch (e: Exception) { 128 Log.w( 129 TAG, 130 "Attempted to remove all custom shortcut but ran into a remote error: $e", 131 ) 132 ERROR_OTHER 133 } 134 } 135 } 136 getInputGestureByTriggernull137 suspend fun getInputGestureByTrigger(trigger: InputGestureData.Trigger): InputGestureData? = 138 withContext(bgCoroutineContext) { inputManager.getInputGesture(trigger) } 139 140 private companion object { 141 private const val TAG = "CustomInputGesturesRepository" 142 } 143 } 144