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