• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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