1 /* <lambda>null2 * 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 android.hardware.input 18 19 import android.hardware.input.InputGestureData.Trigger 20 import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS 21 import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST 22 import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS 23 import android.hardware.input.InputManager.InputDeviceListener 24 import android.view.InputDevice 25 import android.view.KeyCharacterMap 26 import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD 27 import android.view.KeyEvent 28 import org.mockito.ArgumentMatchers.anyInt 29 import org.mockito.invocation.InvocationOnMock 30 import org.mockito.kotlin.any 31 import org.mockito.kotlin.mock 32 33 class FakeInputManager { 34 35 private val keyCharacterMap = KeyCharacterMap.load(VIRTUAL_KEYBOARD) 36 37 private val virtualKeyboard = 38 InputDevice.Builder() 39 .setId(VIRTUAL_KEYBOARD) 40 .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC) 41 .setSources(InputDevice.SOURCE_KEYBOARD) 42 .setEnabled(true) 43 .setKeyCharacterMap(keyCharacterMap) 44 .build() 45 46 private val devices = mutableMapOf<Int, InputDevice>(VIRTUAL_KEYBOARD to virtualKeyboard) 47 private val allKeyCodes = (0..KeyEvent.MAX_KEYCODE) 48 private val supportedKeyCodesByDeviceId = 49 mutableMapOf( 50 // Mark all keys supported by default 51 VIRTUAL_KEYBOARD to allKeyCodes.toMutableSet() 52 ) 53 54 private var inputDeviceListener: InputDeviceListener? = null 55 private val customInputGestures: MutableMap<Trigger, InputGestureData> = mutableMapOf() 56 var addCustomInputGestureErrorCode = CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS 57 58 val inputManager: InputManager = mock { 59 on { getCustomInputGestures(any()) }.then { customInputGestures.values.toList() } 60 61 on { addCustomInputGesture(any()) } 62 .then { 63 val inputGestureData = it.getArgument<InputGestureData>(0) 64 val trigger = inputGestureData.trigger 65 66 if (customInputGestures.containsKey(trigger)) { 67 addCustomInputGestureErrorCode 68 } else { 69 customInputGestures[trigger] = inputGestureData 70 CUSTOM_INPUT_GESTURE_RESULT_SUCCESS 71 } 72 } 73 74 on { removeCustomInputGesture(any()) } 75 .then { 76 val inputGestureData = it.getArgument<InputGestureData>(0) 77 val trigger = inputGestureData.trigger 78 79 if (customInputGestures.containsKey(trigger)) { 80 customInputGestures.remove(trigger) 81 CUSTOM_INPUT_GESTURE_RESULT_SUCCESS 82 } else { 83 CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST 84 } 85 } 86 87 on { removeAllCustomInputGestures(any()) }.then { customInputGestures.clear() } 88 89 on { getInputGesture(any()) } 90 .then { 91 val trigger = it.getArgument<Trigger>(0) 92 customInputGestures[trigger] 93 } 94 95 on { getInputDevice(anyInt()) } 96 .thenAnswer { invocation -> 97 val deviceId = invocation.arguments[0] as Int 98 return@thenAnswer devices[deviceId] 99 } 100 on { inputDeviceIds } 101 .thenAnswer { 102 return@thenAnswer devices.keys.toIntArray() 103 } 104 105 fun setDeviceEnabled(invocation: InvocationOnMock, enabled: Boolean) { 106 val deviceId = invocation.arguments[0] as Int 107 val device = devices[deviceId] ?: return 108 devices[deviceId] = device.copy(enabled = enabled) 109 } 110 111 on { disableInputDevice(anyInt()) } 112 .thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = false) } 113 on { enableInputDevice(anyInt()) } 114 .thenAnswer { invocation -> setDeviceEnabled(invocation, enabled = true) } 115 on { deviceHasKeys(any(), any()) } 116 .thenAnswer { invocation -> 117 val deviceId = invocation.arguments[0] as Int 118 val keyCodes = invocation.arguments[1] as IntArray 119 val supportedKeyCodes = supportedKeyCodesByDeviceId[deviceId]!! 120 return@thenAnswer keyCodes.map { supportedKeyCodes.contains(it) }.toBooleanArray() 121 } 122 } 123 124 fun resetCustomInputGestures() { 125 customInputGestures.clear() 126 addCustomInputGestureErrorCode = CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS 127 } 128 129 fun addPhysicalKeyboardIfNotPresent(deviceId: Int, enabled: Boolean = true) { 130 if (devices.containsKey(deviceId)) { 131 return 132 } 133 addPhysicalKeyboard(deviceId, enabled = enabled) 134 } 135 136 fun registerInputDeviceListener(listener: InputDeviceListener) { 137 // TODO (b/355422259): handle this by listening to inputManager.registerInputDeviceListener 138 inputDeviceListener = listener 139 } 140 141 fun addPhysicalKeyboard( 142 id: Int, 143 vendorId: Int = 0, 144 productId: Int = 0, 145 isFullKeyboard: Boolean = true, 146 enabled: Boolean = true, 147 ) { 148 check(id > 0) { "Physical keyboard ids have to be > 0" } 149 addKeyboard(id, vendorId, productId, isFullKeyboard, enabled) 150 } 151 152 fun removeKeysFromKeyboard(deviceId: Int, vararg keyCodes: Int) { 153 addPhysicalKeyboardIfNotPresent(deviceId) 154 supportedKeyCodesByDeviceId[deviceId]!!.removeAll(keyCodes.asList()) 155 } 156 157 private fun addKeyboard( 158 id: Int, 159 vendorId: Int = 0, 160 productId: Int = 0, 161 isFullKeyboard: Boolean = true, 162 enabled: Boolean = true, 163 ) { 164 val keyboardType = 165 if (isFullKeyboard) InputDevice.KEYBOARD_TYPE_ALPHABETIC 166 else InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC 167 // VendorId and productId are set to 0 if not specified, which is the same as the default 168 // values used in InputDevice.Builder 169 val builder = 170 InputDevice.Builder() 171 .setId(id) 172 .setVendorId(vendorId) 173 .setProductId(productId) 174 .setKeyboardType(keyboardType) 175 .setSources(InputDevice.SOURCE_KEYBOARD) 176 .setEnabled(enabled) 177 .setKeyCharacterMap(keyCharacterMap) 178 devices[id] = builder.build() 179 inputDeviceListener?.onInputDeviceAdded(id) 180 supportedKeyCodesByDeviceId[id] = allKeyCodes.toMutableSet() 181 } 182 183 fun addDevice(id: Int, sources: Int, isNotFound: Boolean = false) { 184 // there's not way of differentiate device connection vs registry in current implementation. 185 // If the device isNotFound, it means that we connect an unregistered device. 186 if (!isNotFound) { 187 devices[id] = InputDevice.Builder().setId(id).setSources(sources).build() 188 } 189 inputDeviceListener?.onInputDeviceAdded(id) 190 } 191 192 fun removeDevice(id: Int) { 193 devices.remove(id) 194 inputDeviceListener?.onInputDeviceRemoved(id) 195 } 196 197 private fun InputDevice.copy( 198 id: Int = getId(), 199 type: Int = keyboardType, 200 sources: Int = getSources(), 201 enabled: Boolean = isEnabled, 202 ) = 203 InputDevice.Builder() 204 .setId(id) 205 .setKeyboardType(type) 206 .setSources(sources) 207 .setEnabled(enabled) 208 .build() 209 } 210