xref: /aosp_15_r20/cts/libs/input/src/com/android/cts/input/InputEventMatchers.kt (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright 2024 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 com.android.cts.input.inputeventmatchers
18 
19 import android.graphics.Point
20 import android.graphics.PointF
21 import android.view.KeyEvent
22 import android.view.KeyEvent.keyCodeToString
23 import android.view.MotionEvent
24 import android.view.MotionEvent.PointerCoords
25 import kotlin.math.abs
26 import org.hamcrest.Description
27 import org.hamcrest.Matcher
28 import org.hamcrest.TypeSafeMatcher
29 
30 const val EPSILON = 0.001f
31 
withCoordsnull32 fun withCoords(pt: PointF, epsilon: Float = EPSILON):
33         Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
34     override fun describeTo(description: Description) {
35         description.appendText("With coords = $pt")
36     }
37 
38     override fun matchesSafely(event: MotionEvent): Boolean {
39         return (abs(event.x - pt.x) < epsilon) &&
40                 (abs(event.y - pt.y) < epsilon)
41     }
42 }
43 
withPointerCountnull44 fun withPointerCount(count: Int): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
45     override fun describeTo(description: Description) {
46         description.appendText("With pointer count = $count")
47     }
48 
49     override fun matchesSafely(event: MotionEvent): Boolean {
50         return event.pointerCount == count
51     }
52 }
53 
withCoordsnull54 fun withCoords(pt: Point, epsilon: Float = EPSILON): Matcher<MotionEvent> = withCoords(
55     PointF(pt),
56     epsilon
57 )
58 
59 fun withCoordsForPointerIndex(index: Int, pt: PointF, epsilon: Float = EPSILON):
60         Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
61     override fun describeTo(description: Description) {
62         description.appendText("With coords = $pt for pointer index = $index")
63     }
64 
65     override fun matchesSafely(event: MotionEvent): Boolean {
66         return (abs(event.getX(index)) - pt.x < epsilon) &&
67                 (abs(event.getY(index)) - pt.y < epsilon)
68     }
69 }
70 
withRawCoordsnull71 fun withRawCoords(pt: PointF, epsilon: Float = EPSILON):
72         Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
73     override fun describeTo(description: Description) {
74         description.appendText("With coords = $pt")
75     }
76 
77     override fun matchesSafely(event: MotionEvent): Boolean {
78         return (abs(event.rawX) - pt.x < epsilon) &&
79                 (abs(event.rawY) - pt.y < epsilon)
80     }
81 
82     override fun describeMismatchSafely(event: MotionEvent, mismatchDescription: Description) {
83         mismatchDescription.appendText("Got raw coords = {${event.rawX}, ${event.rawY}}")
84     }
85 }
86 
hasResampledSamplesnull87 private fun hasResampledSamples(event: MotionEvent): Boolean {
88     for (h in 0 until event.historySize) {
89         for (p in 0 until event.pointerCount) {
90             if (PointerCoords().also { event.getHistoricalPointerCoords(p, h, it) }.isResampled()) {
91                 return true
92             }
93         }
94     }
95     for (p in 0 until event.pointerCount) {
96         if (PointerCoords().also { event.getPointerCoords(p, it) }.isResampled()) {
97             return true
98         }
99     }
100     return false
101 }
102 
103 // A MotionEvent is treated as "resampled" if it contains any resampled pointers.
isResamplednull104 fun isResampled(isResampled: Boolean): Matcher<MotionEvent> =
105     object : TypeSafeMatcher<MotionEvent>() {
106         override fun describeTo(description: Description) {
107             description.appendText("Is resampled = $isResampled")
108         }
109 
110         override fun matchesSafely(event: MotionEvent): Boolean {
111             return hasResampledSamples(event) == isResampled
112         }
113 
114         override fun describeMismatchSafely(event: MotionEvent, mismatchDescription: Description) {
115             mismatchDescription.appendText(
116                 "Event was " + if (hasResampledSamples(event)) "" else "not" + " resampled"
117             )
118         }
119     }
120 
withMotionActionnull121 fun withMotionAction(action: Int): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
122     override fun describeTo(description: Description) {
123         description.appendText("With action = ${MotionEvent.actionToString(action)}")
124     }
125 
126     override fun matchesSafely(event: MotionEvent): Boolean {
127         if (action == MotionEvent.ACTION_CANCEL) {
128             if (event.flags and MotionEvent.FLAG_CANCELED != MotionEvent.FLAG_CANCELED) {
129                 return false
130             }
131         }
132         return event.action == action
133     }
134 }
135 
withMotionActionnull136 fun withMotionAction(action: Int, index: Int): Matcher<MotionEvent> =
137         object : TypeSafeMatcher<MotionEvent>() {
138     override fun describeTo(description: Description) {
139         description.appendText(
140             "With action = ${MotionEvent.actionToString(action)}, index = $index"
141         )
142     }
143 
144     override fun matchesSafely(event: MotionEvent): Boolean {
145         if (action != MotionEvent.ACTION_POINTER_DOWN && action != MotionEvent.ACTION_POINTER_UP) {
146             throw Exception(
147                 "Matcher should only be used with ACTION_POINTER_DOWN or ACTION_POINTER_UP"
148             )
149         }
150         return event.actionMasked == action && event.actionIndex == index
151     }
152 }
153 
withRawCoordsnull154 fun withRawCoords(pt: Point, epsilon: Float = EPSILON): Matcher<MotionEvent> {
155     return withRawCoords(PointF(pt), epsilon)
156 }
157 
withSourcenull158 fun withSource(source: Int): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
159     override fun describeTo(description: Description) {
160         description.appendText("With source = $source")
161     }
162 
163     override fun matchesSafely(event: MotionEvent): Boolean {
164         return event.source == source
165     }
166 }
167 
withButtonStatenull168 fun withButtonState(buttonState: Int): Matcher<MotionEvent> =
169     object : TypeSafeMatcher<MotionEvent>() {
170         override fun describeTo(description: Description) {
171             description.appendText("With buttonState = $buttonState")
172         }
173 
174         override fun matchesSafely(event: MotionEvent): Boolean {
175             return event.buttonState == buttonState
176         }
177     }
178 
withDeviceIdnull179 fun withDeviceId(deviceId: Int): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
180     override fun describeTo(description: Description) {
181         description.appendText("With deviceId = $deviceId")
182     }
183 
184     override fun matchesSafely(event: MotionEvent): Boolean {
185         return event.deviceId == deviceId
186     }
187 }
188 
withEventTimenull189 fun withEventTime(eventTime: Long): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
190     override fun describeTo(description: Description) {
191         description.appendText("With eventTime = $eventTime")
192     }
193 
194     override fun matchesSafely(event: MotionEvent): Boolean {
195         return event.eventTime == eventTime
196     }
197 }
198 
withFlagsnull199 fun withFlags(flags: Int): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
200     override fun describeTo(description: Description) {
201         description.appendText("With flags = $flags")
202     }
203 
204     override fun matchesSafely(event: MotionEvent): Boolean {
205         return event.flags and flags == flags
206     }
207 }
208 
withToolTypenull209 fun withToolType(toolType: Int): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
210     override fun describeTo(description: Description) {
211         description.appendText("With tool type = $toolType")
212     }
213 
214     override fun matchesSafely(event: MotionEvent): Boolean {
215         for (p in 0..<event.pointerCount) {
216             if (event.getToolType(p) != toolType) {
217                 return false
218             }
219         }
220         return true
221     }
222 }
223 
withKeyCodenull224 fun withKeyCode(keyCode: Int): Matcher<KeyEvent> = object : TypeSafeMatcher<KeyEvent>() {
225     override fun describeTo(description: Description) {
226         description.appendText("With key code = " + keyCodeToString(keyCode))
227     }
228 
229     override fun matchesSafely(event: KeyEvent): Boolean {
230         return event.keyCode == keyCode
231     }
232 }
233 
withModifierStatenull234 fun withModifierState(modifierState: Int): Matcher<KeyEvent> =
235     object : TypeSafeMatcher<KeyEvent>() {
236     override fun describeTo(description: Description) {
237         description.appendText("With modifier state = $modifierState")
238     }
239 
240     override fun matchesSafely(event: KeyEvent): Boolean {
241         return (event.metaState and modifierState) == modifierState
242     }
243 }
244 
withKeyActionnull245 fun withKeyAction(keyAction: Int): Matcher<KeyEvent> = object : TypeSafeMatcher<KeyEvent>() {
246     override fun describeTo(description: Description) {
247         description.appendText("With key action = $keyAction")
248     }
249 
250     override fun matchesSafely(event: KeyEvent): Boolean {
251         return event.action == keyAction
252     }
253 }
254 
withEdgeFlagsnull255 fun withEdgeFlags(edgeFlags: Int): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
256     override fun describeTo(description: Description) {
257         description.appendText("With edge flags = 0x${edgeFlags.toString(16)}")
258     }
259 
260     override fun matchesSafely(event: MotionEvent): Boolean {
261         return (event.edgeFlags and edgeFlags) == edgeFlags
262     }
263 }
264 
withDownTimenull265 fun withDownTime(downTime: Long): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
266     override fun describeTo(description: Description) {
267         description.appendText("With down time = $downTime")
268     }
269 
270     override fun matchesSafely(event: MotionEvent): Boolean {
271         return event.downTime == downTime
272     }
273 }
274 
withMetaStatenull275 fun withMetaState(metaState: Int): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
276     override fun describeTo(description: Description) {
277         description.appendText("With meta state = 0x${metaState.toString(16)}")
278     }
279 
280     override fun matchesSafely(event: MotionEvent): Boolean {
281         return event.metaState == metaState
282     }
283 }
284 
withPressurenull285 fun withPressure(pressure: Float): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
286     override fun describeTo(description: Description) {
287         description.appendText("With pressure = $pressure")
288     }
289 
290     override fun matchesSafely(event: MotionEvent): Boolean {
291         return abs(event.pressure - pressure) < EPSILON
292     }
293 
294     override fun describeMismatchSafely(event: MotionEvent, mismatchDescription: Description) {
295         mismatchDescription.appendText("Got pressure ${event.pressure}")
296     }
297 }
298 
withHistorySizenull299 fun withHistorySize(historySize: Int): Matcher<MotionEvent> =
300     object : TypeSafeMatcher<MotionEvent>() {
301     override fun describeTo(description: Description) {
302         description.appendText("With history size = $historySize")
303     }
304 
305     override fun matchesSafely(event: MotionEvent): Boolean {
306         return event.historySize == historySize
307     }
308 
309     override fun describeMismatchSafely(event: MotionEvent, mismatchDescription: Description) {
310         mismatchDescription.appendText("Got history size ${event.historySize}")
311     }
312 }
313 
withSizenull314 fun withSize(size: Float): Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
315     override fun describeTo(description: Description) {
316         description.appendText("With size = $size")
317     }
318 
319     override fun matchesSafely(event: MotionEvent): Boolean {
320         return abs(event.size - size) < EPSILON
321     }
322 }
323 
withXPrecisionnull324 fun withXPrecision(xPrecision: Float):
325         Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
326     override fun describeTo(description: Description) {
327         description.appendText("With xPrecision = $xPrecision")
328     }
329 
330     override fun matchesSafely(event: MotionEvent): Boolean {
331         return abs(event.xPrecision - xPrecision) < EPSILON
332     }
333 }
334 
withYPrecisionnull335 fun withYPrecision(yPrecision: Float):
336         Matcher<MotionEvent> = object : TypeSafeMatcher<MotionEvent>() {
337     override fun describeTo(description: Description) {
338         description.appendText("With yPrecision = $yPrecision")
339     }
340 
341     override fun matchesSafely(event: MotionEvent): Boolean {
342         return abs(event.yPrecision - yPrecision) < EPSILON
343     }
344 }
345