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