xref: /aosp_15_r20/frameworks/base/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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