xref: /aosp_15_r20/cts/tests/input/src/android/input/cts/ModifierKeyRemappingTest.kt (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
<lambda>null2  * Copyright (C) 2022 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.input.cts
18 
19 import android.Manifest
20 import android.cts.input.EventVerifier
21 import android.hardware.input.InputManager
22 import android.provider.Settings
23 import android.view.KeyEvent
24 import androidx.test.ext.junit.rules.ActivityScenarioRule
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.MediumTest
27 import androidx.test.platform.app.InstrumentationRegistry
28 import com.android.compatibility.common.util.PollingCheck
29 import com.android.compatibility.common.util.SystemUtil
30 import com.android.compatibility.common.util.ThrowingSupplier
31 import com.android.cts.input.UinputKeyboard
32 import com.android.cts.input.inputeventmatchers.withKeyAction
33 import com.android.cts.input.inputeventmatchers.withKeyCode
34 import com.android.cts.input.inputeventmatchers.withModifierState
35 import org.hamcrest.Matchers.allOf
36 import org.junit.After
37 import org.junit.Before
38 import org.junit.Rule
39 import org.junit.Test
40 import org.junit.runner.RunWith
41 
42 /**
43  * Create virtual keyboard devices and inject a 'hardware' key event after remapping keys. Ensure
44  * that the event keys are correctly remapped.
45  */
46 @MediumTest
47 @RunWith(AndroidJUnit4::class)
48 class ModifierKeyRemappingTest {
49 
50     companion object {
51         // Linux keycode defined in the "linux/input-event-codes.h" header.
52         val KEY_LEFTALT = 56
53     }
54 
55     private val instrumentation = InstrumentationRegistry.getInstrumentation()
56 
57     @get:Rule
58     val rule = ActivityScenarioRule<CaptureEventActivity>(CaptureEventActivity::class.java)
59 
60     private lateinit var activity: CaptureEventActivity
61     private lateinit var verifier: EventVerifier
62     private lateinit var inputManager: InputManager
63     private lateinit var existingRemappings: Map<Int, Int>
64 
65     @Before
66     fun setUp() {
67         rule.getScenario().onActivity {
68             inputManager = it.getSystemService(InputManager::class.java)
69             activity = it
70             verifier = EventVerifier(activity::getInputEvent)
71         }
72         inputManager.resetLockedModifierState()
73         PollingCheck.waitFor { activity.hasWindowFocus() }
74 
75         // Save existing remappings
76         existingRemappings = getModifierKeyRemapping()
77         clearAllModifierKeyRemappings()
78     }
79 
80     @After
81     fun tearDown() {
82         if (this::existingRemappings.isInitialized) {
83             clearAllModifierKeyRemappings()
84             existingRemappings.forEach { entry ->
85                 remapModifierKey(entry.key, entry.value)
86             }
87         }
88         if (this::inputManager.isInitialized) {
89             inputManager.resetLockedModifierState()
90         }
91     }
92 
93     @Test
94     fun testHardwareKeyEventsWithRemapping_afterKeyboardAdded() {
95         ModifierRemappingFlag(true).use {
96             UinputKeyboard(instrumentation).use { keyboardDevice ->
97                 val inputDevice = inputManager.getInputDevice(keyboardDevice.deviceId)
98 
99                 // Add remapping after device is already added
100                 remapModifierKey(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_SHIFT_LEFT)
101                 PollingCheck.waitFor {
102                     KeyEvent.KEYCODE_SHIFT_LEFT ==
103                             inputDevice?.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_ALT_LEFT)
104                 }
105 
106                 injectKeyDown(keyboardDevice, KEY_LEFTALT)
107                 verifier.assertReceivedKey(
108                     allOf(
109                         withKeyCode(KeyEvent.KEYCODE_SHIFT_LEFT),
110                         withKeyAction(KeyEvent.ACTION_DOWN),
111                         withModifierState(KeyEvent.META_SHIFT_LEFT_ON or KeyEvent.META_SHIFT_ON)
112                     )
113                 )
114 
115                 injectKeyUp(keyboardDevice, KEY_LEFTALT)
116                 verifier.assertReceivedKey(withKeyCode(KeyEvent.KEYCODE_SHIFT_LEFT))
117 
118                 clearAllModifierKeyRemappings()
119                 PollingCheck.waitFor {
120                     KeyEvent.KEYCODE_ALT_LEFT ==
121                             inputDevice?.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_ALT_LEFT)
122                 }
123 
124                 injectKeyDown(keyboardDevice, KEY_LEFTALT)
125                 verifier.assertReceivedKey(
126                     allOf(
127                         withKeyCode(KeyEvent.KEYCODE_ALT_LEFT),
128                         withKeyAction(KeyEvent.ACTION_DOWN),
129                         withModifierState(KeyEvent.META_ALT_LEFT_ON or KeyEvent.META_ALT_ON)
130                     )
131                 )
132 
133                 injectKeyUp(keyboardDevice, KEY_LEFTALT)
134                 verifier.assertReceivedKey(withKeyCode(KeyEvent.KEYCODE_ALT_LEFT))
135 
136                 activity.assertNoEvents()
137             }
138         }
139     }
140 
141     @Test
142     fun testHardwareKeyEventsWithRemapping_beforeKeyboardAdded() {
143         ModifierRemappingFlag(true).use {
144             // Add remapping before device is added
145             remapModifierKey(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_SHIFT_LEFT)
146             PollingCheck.waitFor { getModifierKeyRemapping().size == 1 }
147 
148             UinputKeyboard(instrumentation).use { keyboardDevice ->
149                 val inputDevice = inputManager.getInputDevice(keyboardDevice.deviceId)
150                 PollingCheck.waitFor {
151                     KeyEvent.KEYCODE_SHIFT_LEFT ==
152                             inputDevice?.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_ALT_LEFT)
153                 }
154 
155                 injectKeyDown(keyboardDevice, KEY_LEFTALT)
156                 verifier.assertReceivedKey(
157                     allOf(
158                         withKeyCode(KeyEvent.KEYCODE_SHIFT_LEFT),
159                         withKeyAction(KeyEvent.ACTION_DOWN),
160                         withModifierState(KeyEvent.META_SHIFT_LEFT_ON or KeyEvent.META_SHIFT_ON)
161                     )
162                 )
163 
164                 injectKeyUp(keyboardDevice, KEY_LEFTALT)
165                 verifier.assertReceivedKey(withKeyCode(KeyEvent.KEYCODE_SHIFT_LEFT))
166 
167                 clearAllModifierKeyRemappings()
168                 PollingCheck.waitFor {
169                     KeyEvent.KEYCODE_ALT_LEFT ==
170                             inputDevice?.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_ALT_LEFT)
171                 }
172 
173                 injectKeyDown(keyboardDevice, KEY_LEFTALT)
174                 verifier.assertReceivedKey(
175                     allOf(
176                         withKeyCode(KeyEvent.KEYCODE_ALT_LEFT),
177                         withKeyAction(KeyEvent.ACTION_DOWN),
178                         withModifierState(KeyEvent.META_ALT_LEFT_ON or KeyEvent.META_ALT_ON)
179                     )
180                 )
181 
182                 injectKeyUp(keyboardDevice, KEY_LEFTALT)
183                 verifier.assertReceivedKey(withKeyCode(KeyEvent.KEYCODE_ALT_LEFT))
184 
185                 activity.assertNoEvents()
186             }
187         }
188     }
189 
190     @Test
191     fun testAltToCapsLockRemapping_forKeyboardWithNoCapsLockKey() {
192         ModifierRemappingFlag(true).use {
193             UinputKeyboard(instrumentation, listOf("KEY_Q", "KEY_LEFTALT")).use { keyboardDevice ->
194                 val inputDevice = inputManager.getInputDevice(keyboardDevice.deviceId)
195                 remapModifierKey(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_CAPS_LOCK)
196                 PollingCheck.waitFor {
197                     KeyEvent.KEYCODE_CAPS_LOCK ==
198                             inputDevice?.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_ALT_LEFT)
199                 }
200 
201                 injectKeyDown(keyboardDevice, KEY_LEFTALT)
202                 verifier.assertReceivedKey(withKeyCode(KeyEvent.KEYCODE_CAPS_LOCK))
203 
204                 injectKeyUp(keyboardDevice, KEY_LEFTALT)
205                 verifier.assertReceivedKey(
206                     allOf(
207                         withKeyCode(KeyEvent.KEYCODE_CAPS_LOCK),
208                         withModifierState(KeyEvent.META_CAPS_LOCK_ON),
209                     )
210                 )
211 
212                 // Send second pair of key presses to reset caps lock state
213                 injectKeyDown(keyboardDevice, KEY_LEFTALT)
214                 verifier.assertReceivedKey(
215                     allOf(
216                         withKeyCode(KeyEvent.KEYCODE_CAPS_LOCK),
217                         withModifierState(KeyEvent.META_CAPS_LOCK_ON),
218                     )
219                 )
220 
221                 injectKeyUp(keyboardDevice, KEY_LEFTALT)
222                 verifier.assertReceivedKey(withKeyCode(KeyEvent.KEYCODE_CAPS_LOCK))
223 
224                 clearAllModifierKeyRemappings()
225                 PollingCheck.waitFor {
226                     KeyEvent.KEYCODE_ALT_LEFT ==
227                             inputDevice?.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_ALT_LEFT)
228                 }
229 
230                 injectKeyDown(keyboardDevice, KEY_LEFTALT)
231                 verifier.assertReceivedKey(
232                     allOf(
233                         withKeyCode(KeyEvent.KEYCODE_ALT_LEFT),
234                         withKeyAction(KeyEvent.ACTION_DOWN),
235                         withModifierState(KeyEvent.META_ALT_LEFT_ON or KeyEvent.META_ALT_ON)
236                     )
237                 )
238 
239                 injectKeyUp(keyboardDevice, KEY_LEFTALT)
240                 verifier.assertReceivedKey(withKeyCode(KeyEvent.KEYCODE_ALT_LEFT))
241 
242                 activity.assertNoEvents()
243             }
244         }
245     }
246 
247     /**
248      * Remaps a modifier key to another modifier key
249      *
250      * @param fromKey modifier key getting remapped
251      * @param toKey   modifier key that it is getting remapped to
252      */
253     private fun remapModifierKey(fromKey: Int, toKey: Int) {
254         SystemUtil.runWithShellPermissionIdentity(
255             { inputManager.remapModifierKey(fromKey, toKey) },
256             Manifest.permission.REMAP_MODIFIER_KEYS
257         )
258     }
259 
260     /**
261      * Clears remapping for a modifier key
262      */
263     private fun clearAllModifierKeyRemappings() {
264         SystemUtil.runWithShellPermissionIdentity(
265             { inputManager.clearAllModifierKeyRemappings() },
266             Manifest.permission.REMAP_MODIFIER_KEYS
267         )
268         PollingCheck.waitFor { getModifierKeyRemapping().isEmpty() }
269     }
270 
271     private fun getModifierKeyRemapping(): Map<Int, Int> {
272         return SystemUtil.runWithShellPermissionIdentity(
273             ThrowingSupplier<Map<Int, Int>> { inputManager.modifierKeyRemapping },
274             Manifest.permission.REMAP_MODIFIER_KEYS
275         )
276     }
277 
278     private inner class ModifierRemappingFlag constructor(enabled: Boolean) : AutoCloseable {
279         init {
280             Settings.Global.putString(
281                 activity.contentResolver,
282                 "settings_new_keyboard_modifier_key",
283                 enabled.toString()
284             )
285         }
286 
287         override fun close() {
288             Settings.Global.putString(
289                 activity.contentResolver,
290                 "settings_new_keyboard_modifier_key",
291                 ""
292             )
293         }
294     }
295 }
296