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