xref: /aosp_15_r20/cts/tests/input/src/android/input/cts/DrawingTabletTest.kt (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 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 
17 package android.input.cts
18 
19 import android.cts.input.EventVerifier
20 import android.graphics.Point
21 import android.graphics.PointF
22 import android.view.InputDevice
23 import android.view.MotionEvent
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.MediumTest
26 import androidx.test.platform.app.InstrumentationRegistry
27 import com.android.cts.input.UinputDrawingTablet
28 import com.android.cts.input.UinputTouchDevice
29 import com.android.cts.input.VirtualDisplayActivityScenario
30 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.DEFAULT_HEIGHT
31 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.DEFAULT_WIDTH
32 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.ORIENTATION_0
33 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.ORIENTATION_180
34 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.ORIENTATION_270
35 import com.android.cts.input.VirtualDisplayActivityScenario.Companion.ORIENTATION_90
36 import com.android.cts.input.inputeventmatchers.withCoords
37 import com.android.cts.input.inputeventmatchers.withMotionAction
38 import com.android.cts.input.inputeventmatchers.withSource
39 import com.android.cts.input.inputeventmatchers.withToolType
40 import org.hamcrest.Matchers.allOf
41 import org.junit.After
42 import org.junit.Assert.assertEquals
43 import org.junit.Before
44 import org.junit.Rule
45 import org.junit.Test
46 import org.junit.rules.TestName
47 import org.junit.runner.RunWith
48 
49 @MediumTest
50 @RunWith(AndroidJUnit4::class)
51 class DrawingTabletTest {
52     private lateinit var drawingTablet: UinputTouchDevice
53     private lateinit var verifier: EventVerifier
54 
55     @get:Rule
56     val testName = TestName()
57     @get:Rule
58     val virtualDisplayRule = VirtualDisplayActivityScenario.Rule<CaptureEventActivity>(testName)
59 
60     @Before
setUpnull61     fun setUp() {
62         drawingTablet = UinputDrawingTablet(
63             InstrumentationRegistry.getInstrumentation(), virtualDisplayRule.virtualDisplay.display)
64         verifier = EventVerifier(virtualDisplayRule.activity::getInputEvent)
65     }
66 
67     @After
tearDownnull68     fun tearDown() {
69         if (this::drawingTablet.isInitialized) {
70             drawingTablet.close()
71         }
72     }
73 
74     @Test
testCoordinateMappingOrientation0null75     fun testCoordinateMappingOrientation0() {
76         virtualDisplayRule.runInDisplayOrientation(ORIENTATION_0) {
77             verifyTaps(EXPECTED_POINTS_UNROTATED, ::transformForUnrotatedDrawingTablet)
78         }
79     }
80 
81     @Test
testCoordinateMappingOrientation90null82     fun testCoordinateMappingOrientation90() {
83         virtualDisplayRule.runInDisplayOrientation(ORIENTATION_90) {
84             verifyTaps(EXPECTED_POINTS_ROTATED, ::transformForRotatedDrawingTablet)
85         }
86     }
87 
88     @Test
testCoordinateMappingOrientation180null89     fun testCoordinateMappingOrientation180() {
90         virtualDisplayRule.runInDisplayOrientation(ORIENTATION_180) {
91             verifyTaps(EXPECTED_POINTS_UNROTATED, ::transformForUnrotatedDrawingTablet)
92         }
93     }
94 
95     @Test
testCoordinateMappingOrientation270null96     fun testCoordinateMappingOrientation270() {
97         virtualDisplayRule.runInDisplayOrientation(ORIENTATION_270) {
98             verifyTaps(EXPECTED_POINTS_ROTATED, ::transformForRotatedDrawingTablet)
99         }
100     }
101 
102     @Test
testHovernull103     fun testHover() {
104         val pointerId = 0
105         val commonMatcher =
106             allOf(
107                 withSource(InputDevice.SOURCE_STYLUS or InputDevice.SOURCE_MOUSE),
108                 withToolType(MotionEvent.TOOL_TYPE_STYLUS),
109             )
110 
111         // Inject and verify HOVER_ENTER
112         drawingTablet.sendBtnTouch(false)
113         drawingTablet.sendDown(pointerId, INJECTION_POINTS[0])
114         drawingTablet.sync()
115 
116         verifier.assertReceivedMotion(
117             allOf(
118                 withMotionAction(MotionEvent.ACTION_HOVER_ENTER),
119                 withCoords(transformForUnrotatedDrawingTablet(INJECTION_POINTS[0])!!),
120                 commonMatcher
121             )
122         )
123         verifier.assertReceivedMotion(
124             allOf(
125                 withMotionAction(MotionEvent.ACTION_HOVER_MOVE),
126                 withCoords(transformForUnrotatedDrawingTablet(INJECTION_POINTS[0])!!),
127                 commonMatcher
128             )
129         )
130 
131         // Inject and verify HOVER_MOVE
132         drawingTablet.sendMove(pointerId, INJECTION_POINTS[1])
133         drawingTablet.sync()
134 
135         verifier.assertReceivedMotion(
136             allOf(
137                 withMotionAction(MotionEvent.ACTION_HOVER_MOVE),
138                 withCoords(transformForUnrotatedDrawingTablet(INJECTION_POINTS[1])!!),
139                 commonMatcher
140             )
141         )
142 
143         // Inject and verify HOVER_EXIT
144         drawingTablet.sendUp(pointerId)
145         drawingTablet.sync()
146 
147         verifier.assertReceivedMotion(
148             allOf(
149                 withMotionAction(MotionEvent.ACTION_HOVER_EXIT),
150                 withCoords(transformForUnrotatedDrawingTablet(INJECTION_POINTS[1])!!),
151                 commonMatcher
152             )
153         )
154     }
155 
156     /**
157      * Taps at each point in [INJECTION_POINTS] and ensures that the event is received at the
158      * corresponding point in [expectedPoints].
159      */
verifyTapsnull160     private fun verifyTaps(
161         expectedPoints: Array<PointF?>,
162         transformToExpectedPoint: (Point) -> PointF?
163     ) {
164         for (i in INJECTION_POINTS.indices) {
165             val pointerId = 0
166             drawingTablet.sendBtnTouch(true)
167             drawingTablet.sendDown(pointerId, INJECTION_POINTS[i])
168             drawingTablet.sync()
169 
170             drawingTablet.sendBtnTouch(false)
171             drawingTablet.sendUp(pointerId)
172             drawingTablet.sync()
173 
174             val expected = expectedPoints[i]
175             // Ensure the hard-coded expected points and the transformation function agree for the
176             // injected point.
177             assertEquals(transformToExpectedPoint(INJECTION_POINTS[i]), expected)
178 
179             if (expected != null) {
180                 verifier.assertReceivedMotion(allOf(
181                     withMotionAction(MotionEvent.ACTION_DOWN),
182                     withSource(InputDevice.SOURCE_STYLUS or InputDevice.SOURCE_MOUSE),
183                     withToolType(MotionEvent.TOOL_TYPE_STYLUS),
184                     withCoords(expected)
185                 ))
186 
187                 verifier.assertReceivedMotion(
188                     allOf(withMotionAction(MotionEvent.ACTION_MOVE), withCoords(expected))
189                 )
190                 verifier.assertReceivedMotion(
191                     allOf(withMotionAction(MotionEvent.ACTION_UP), withCoords(expected))
192                 )
193             }
194         }
195         virtualDisplayRule.activity.assertNoEvents()
196     }
197 
198     private companion object {
199         const val EPSILON = 0.001f
200 
201         /** Coordinates in the drawing tablet injected in [verifyTaps]. */
202         val INJECTION_POINTS = arrayOf(
203             Point(0, 0), // top-left corner of drawing tablet
204             Point(DEFAULT_WIDTH - 1, 0), // top-right corner of drawing tablet
205             Point(DEFAULT_WIDTH - 1, DEFAULT_HEIGHT - 1), // bottom-right corner of drawing tablet
206             Point(0, DEFAULT_HEIGHT - 1), // bottom-left corner of drawing tablet
207             Point(200, 200), // point inside drawing tablet
208             Point(200, 600), // point inside drawing tablet
209         )
210 
211         /**
212          * The points that each of the [INJECTION_POINTS] are expected to map to when the display
213          * is rotated to 0 or 180 degrees.
214          */
215         val EXPECTED_POINTS_UNROTATED = arrayOf<PointF?>(
216             PointF(INJECTION_POINTS[0]),
217             PointF(INJECTION_POINTS[1]),
218             PointF(INJECTION_POINTS[2]),
219             PointF(INJECTION_POINTS[3]),
220             PointF(INJECTION_POINTS[4]),
221             PointF(INJECTION_POINTS[5]),
222         )
223 
transformForUnrotatedDrawingTabletnull224         fun transformForUnrotatedDrawingTablet(p: Point): PointF? {
225             return PointF(p)
226         }
227 
228         /**
229          * The points that each of the [INJECTION_POINTS] are expected to map to when the display
230          * is rotated to 90 or 270 degrees.
231          */
232         val EXPECTED_POINTS_ROTATED = arrayOf<PointF?>(
233             PointF(0f, 0f),
234             PointF(798.3333f, 0f),
235             null,
236             null,
237             PointF(333.3333f, 333.3333f),
238             null,
239         )
240 
transformForRotatedDrawingTabletnull241         fun transformForRotatedDrawingTablet(p: Point): PointF? {
242             val rotatedScale = DEFAULT_HEIGHT.toFloat() / DEFAULT_WIDTH.toFloat()
243             val scaled = PointF(p.x * rotatedScale, p.y * rotatedScale)
244             if (scaled.x < 0 || scaled.x >= DEFAULT_HEIGHT.toFloat() ||
245                 scaled.y < 0 || scaled.y >= DEFAULT_WIDTH.toFloat()) {
246                 return null
247             }
248             return scaled
249         }
250     }
251 }
252