1 /* <lambda>null2 * Copyright 2023 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 package android.input.cts.hostside.app 17 18 import android.app.Activity 19 import android.content.Context 20 import android.graphics.Point 21 import android.util.DisplayMetrics 22 import android.util.Size 23 import androidx.test.ext.junit.rules.ActivityScenarioRule 24 import androidx.test.ext.junit.runners.AndroidJUnit4 25 import androidx.test.platform.app.InstrumentationRegistry 26 import com.android.cts.input.UinputDevice 27 import com.android.cts.input.UinputKeyboard 28 import com.android.cts.input.UinputTouchDevice 29 import com.android.cts.input.UinputTouchPad 30 import com.android.cts.input.UinputTouchScreen 31 import org.junit.After 32 import org.junit.Before 33 import org.junit.Rule 34 import org.junit.Test 35 import org.junit.runner.RunWith 36 37 /** 38 * This class contains device-side parts of input host tests. In particular, it is used to 39 * emulate input device connections and interactions for host tests. 40 */ 41 @RunWith(AndroidJUnit4::class) 42 class EmulateInputDevice { 43 private val instrumentation = InstrumentationRegistry.getInstrumentation() 44 private lateinit var context: Context 45 private lateinit var screenSize: Size 46 47 @get:Rule 48 val activityRule = ActivityScenarioRule(Activity::class.java) 49 50 @Suppress("DEPRECATION") 51 @Before 52 fun setUp() { 53 activityRule.scenario.onActivity { context = it } 54 val dm = DisplayMetrics().also { context.display.getRealMetrics(it) } 55 screenSize = Size(dm.widthPixels, dm.heightPixels) 56 } 57 58 @After 59 fun tearDown() { 60 } 61 62 /** 63 * Registers a USB touchscreen through uinput, interacts with it for at least 64 * five seconds, and disconnects the device. 65 */ 66 @Test 67 fun useTouchscreenForFiveSeconds() { 68 UinputTouchScreen(instrumentation, context.display).use { touchscreen -> 69 // Start the usage session. 70 touchscreen.tapOnScreen() 71 72 // Continue using the touchscreen for at least five more seconds. 73 for (i in 0 until 5) { 74 Thread.sleep(1000) 75 touchscreen.tapOnScreen() 76 } 77 78 Thread.sleep(UINPUT_POST_EVENT_DELAY_MILLIS) 79 } 80 } 81 82 private fun UinputTouchDevice.tapOnScreen() { 83 val pointer = Point(screenSize.width / 2, screenSize.height / 2) 84 val pointerId = 0 85 86 // Down 87 sendBtnTouch(true) 88 sendDown(pointerId, pointer) 89 sync() 90 91 // Move 92 pointer.offset(1, 1) 93 sendMove(pointerId, pointer) 94 sync() 95 96 // Up 97 sendBtnTouch(false) 98 sendUp(pointerId) 99 sync() 100 } 101 102 @Test 103 fun useTouchpadWithFingersAndPalms() { 104 UinputTouchPad(instrumentation, context.display).use { touchpad -> 105 for (i in 0 until 3) { 106 val pointer = Point(100, 200) 107 touchpad.sendBtnTouch(true) 108 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_FINGER, true) 109 touchpad.sendDown(0, pointer, UinputTouchDevice.MT_TOOL_FINGER) 110 touchpad.sync() 111 112 touchpad.sendBtnTouch(false) 113 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_FINGER, false) 114 touchpad.sendUp(0) 115 touchpad.sync() 116 } 117 for (i in 0 until 2) { 118 val pointer = Point(100, 200) 119 touchpad.sendBtnTouch(true) 120 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_FINGER, true) 121 touchpad.sendDown(0, pointer, UinputTouchDevice.MT_TOOL_PALM) 122 touchpad.sync() 123 124 touchpad.sendBtnTouch(false) 125 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_FINGER, false) 126 touchpad.sendUp(0) 127 touchpad.sync() 128 } 129 Thread.sleep(UINPUT_POST_EVENT_DELAY_MILLIS) 130 } 131 } 132 133 @Test 134 fun pinchOnTouchpad() { 135 UinputTouchPad(instrumentation, context.display).use { touchpad -> 136 val pointer0 = Point(500, 500) 137 val pointer1 = Point(700, 700) 138 touchpad.sendBtnTouch(true) 139 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_FINGER, true) 140 touchpad.sendDown(0, pointer0) 141 touchpad.sync() 142 143 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_FINGER, false) 144 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_DOUBLETAP, true) 145 touchpad.sendDown(1, pointer1) 146 touchpad.sync() 147 Thread.sleep(TOUCHPAD_SCAN_DELAY_MILLIS) 148 149 for (rep in 0 until 10) { 150 pointer0.offset(-20, -20) 151 touchpad.sendMove(0, pointer0) 152 pointer1.offset(20, 20) 153 touchpad.sendMove(1, pointer1) 154 touchpad.sync() 155 Thread.sleep(TOUCHPAD_SCAN_DELAY_MILLIS) 156 } 157 158 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_DOUBLETAP, false) 159 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_FINGER, true) 160 touchpad.sendUp(0) 161 touchpad.sync() 162 163 touchpad.sendBtn(UinputTouchDevice.BTN_TOOL_FINGER, false) 164 touchpad.sendBtnTouch(false) 165 touchpad.sendUp(1) 166 touchpad.sync() 167 Thread.sleep(UINPUT_POST_EVENT_DELAY_MILLIS) 168 } 169 } 170 171 @Test 172 fun twoFingerSwipeOnTouchpad() { 173 multiFingerSwipe(2) 174 } 175 176 @Test 177 fun threeFingerSwipeOnTouchpad() { 178 multiFingerSwipe(3) 179 } 180 181 @Test 182 fun fourFingerSwipeOnTouchpad() { 183 multiFingerSwipe(4) 184 } 185 186 private fun multiFingerSwipe(numFingers: Int) { 187 UinputTouchPad(instrumentation, context.display).use { touchpad -> 188 val pointers = Array(numFingers) { i -> Point(500 + i * 200, 500) } 189 touchpad.sendBtnTouch(true) 190 touchpad.sendBtn(UinputTouchDevice.toolBtnForFingerCount(numFingers), true) 191 for (i in pointers.indices) { 192 touchpad.sendDown(i, pointers[i]) 193 } 194 touchpad.sync() 195 Thread.sleep(TOUCHPAD_SCAN_DELAY_MILLIS) 196 197 for (rep in 0 until 10) { 198 for (i in pointers.indices) { 199 pointers[i].offset(0, 40) 200 touchpad.sendMove(i, pointers[i]) 201 } 202 touchpad.sync() 203 Thread.sleep(TOUCHPAD_SCAN_DELAY_MILLIS) 204 } 205 206 for (i in pointers.indices) { 207 touchpad.sendUp(i) 208 } 209 touchpad.sendBtn(UinputTouchDevice.toolBtnForFingerCount(numFingers), false) 210 touchpad.sendBtnTouch(false) 211 touchpad.sync() 212 Thread.sleep(UINPUT_POST_EVENT_DELAY_MILLIS) 213 } 214 } 215 216 @Test 217 fun createKeyboardDevice() { 218 UinputKeyboard(instrumentation).use { 219 // Do nothing: Adding a device should trigger the logging logic. 220 // Wait until the Input device created is sent to KeyboardLayoutManager to trigger 221 // logging logic 222 Thread.sleep(UINPUT_POST_EVENT_DELAY_MILLIS) 223 } 224 } 225 226 @Test 227 fun createKeyboardDeviceAndSendCapsLockKey() { 228 UinputKeyboard(instrumentation).use { keyboard -> 229 // Wait for device to be added 230 injectEvents(keyboard, intArrayOf(EV_KEY, KEY_CAPSLOCK, KEY_PRESS, 0, 0, 0)) 231 injectEvents(keyboard, intArrayOf(EV_KEY, KEY_CAPSLOCK, KEY_RELEASE, 0, 0, 0)) 232 Thread.sleep(UINPUT_POST_EVENT_DELAY_MILLIS) 233 } 234 } 235 236 private fun injectEvents(device: UinputDevice, events: IntArray) { 237 device.injectEvents(events.joinToString(prefix = "[", postfix = "]", separator = ",")) 238 } 239 240 companion object { 241 const val TOUCHPAD_SCAN_DELAY_MILLIS: Long = 5 242 const val KEY_CAPSLOCK: Int = 58 243 const val EV_KEY: Int = 1 244 const val KEY_PRESS: Int = 1 245 const val KEY_RELEASE: Int = 0 246 247 // When a uinput device is closed, there's a race between InputReader picking up the final 248 // events from the device's buffer (specifically, the buffer in struct evdev_client in the 249 // kernel) and the device being torn down. If the device is torn down first, one or more 250 // frames of data get lost. To prevent flakes due to this race, we delay closing the device 251 // for a while after sending the last event, so InputReader has time to read them all. 252 const val UINPUT_POST_EVENT_DELAY_MILLIS: Long = 500 253 } 254 } 255