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