1 /*
2  * Copyright (C) 2022 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.systemui.biometrics.udfps
18 
19 import android.graphics.Rect
20 import android.view.MotionEvent
21 import android.view.MotionEvent.INVALID_POINTER_ID
22 import android.view.MotionEvent.PointerProperties
23 import android.view.Surface
24 import android.view.Surface.Rotation
25 import androidx.test.filters.SmallTest
26 import com.android.settingslib.udfps.UdfpsOverlayParams
27 import com.android.systemui.SysuiTestCase
28 import com.google.common.truth.Truth.assertThat
29 import org.junit.Test
30 import org.junit.runner.RunWith
31 import org.junit.runners.Parameterized
32 import org.junit.runners.Parameterized.Parameters
33 
34 @SmallTest
35 @RunWith(Parameterized::class)
36 class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() {
37     private val overlapDetector = FakeOverlapDetector()
38     private val underTest = SinglePointerTouchProcessor(overlapDetector)
39 
40     @Test
41     fun processTouch() {
42         overlapDetector.shouldReturn =
43             testCase.currentPointers.associate { pointer -> pointer.id to pointer.onSensor }
44 
45         val actual =
46             underTest.processTouch(
47                 testCase.event,
48                 testCase.previousPointerOnSensorId,
49                 testCase.overlayParams,
50             )
51 
52         assertThat(actual).isInstanceOf(testCase.expected.javaClass)
53         if (actual is TouchProcessorResult.ProcessedTouch) {
54             assertThat(actual).isEqualTo(testCase.expected)
55         }
56     }
57 
58     data class TestCase(
59         val event: MotionEvent,
60         val currentPointers: List<TestPointer>,
61         val previousPointerOnSensorId: Int,
62         val overlayParams: UdfpsOverlayParams,
63         val expected: TouchProcessorResult,
64     ) {
65         override fun toString(): String {
66             val expectedOutput =
67                 if (expected is TouchProcessorResult.ProcessedTouch) {
68                     expected.event.toString() +
69                         ", (x: ${expected.touchData.x}, y: ${expected.touchData.y})" +
70                         ", pointerOnSensorId: ${expected.pointerOnSensorId}" +
71                         ", ..."
72                 } else {
73                     TouchProcessorResult.Failure().toString()
74                 }
75             return "{" +
76                 MotionEvent.actionToString(event.action) +
77                 ", (x: ${event.x}, y: ${event.y})" +
78                 ", scale: ${overlayParams.scaleFactor}" +
79                 ", rotation: " +
80                 Surface.rotationToString(overlayParams.rotation) +
81                 ", previousPointerOnSensorId: $previousPointerOnSensorId" +
82                 ", ...} expected: {$expectedOutput}"
83         }
84     }
85 
86     companion object {
87         @Parameters(name = "{0}")
88         @JvmStatic
89         fun data(): List<TestCase> =
90             listOf(
91                     // MotionEvent.ACTION_DOWN
92                     genPositiveTestCases(
93                         motionEventAction = MotionEvent.ACTION_DOWN,
94                         previousPointerOnSensorId = INVALID_POINTER_ID,
95                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
96                         expectedInteractionEvent = InteractionEvent.DOWN,
97                         expectedPointerOnSensorId = POINTER_ID_1,
98                     ),
99                     genPositiveTestCases(
100                         motionEventAction = MotionEvent.ACTION_DOWN,
101                         previousPointerOnSensorId = INVALID_POINTER_ID,
102                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
103                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
104                         expectedPointerOnSensorId = INVALID_POINTER_ID,
105                     ),
106                     genPositiveTestCases(
107                         motionEventAction = MotionEvent.ACTION_DOWN,
108                         previousPointerOnSensorId = POINTER_ID_1,
109                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
110                         expectedInteractionEvent = InteractionEvent.UP,
111                         expectedPointerOnSensorId = INVALID_POINTER_ID,
112                     ),
113                     // MotionEvent.ACTION_HOVER_ENTER
114                     genPositiveTestCases(
115                         motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
116                         previousPointerOnSensorId = INVALID_POINTER_ID,
117                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
118                         expectedInteractionEvent = InteractionEvent.DOWN,
119                         expectedPointerOnSensorId = POINTER_ID_1,
120                     ),
121                     genPositiveTestCases(
122                         motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
123                         previousPointerOnSensorId = INVALID_POINTER_ID,
124                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
125                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
126                         expectedPointerOnSensorId = INVALID_POINTER_ID,
127                     ),
128                     genPositiveTestCases(
129                         motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
130                         previousPointerOnSensorId = POINTER_ID_1,
131                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
132                         expectedInteractionEvent = InteractionEvent.UP,
133                         expectedPointerOnSensorId = INVALID_POINTER_ID,
134                     ),
135                     // MotionEvent.ACTION_MOVE
136                     genPositiveTestCases(
137                         motionEventAction = MotionEvent.ACTION_MOVE,
138                         previousPointerOnSensorId = INVALID_POINTER_ID,
139                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
140                         expectedInteractionEvent = InteractionEvent.DOWN,
141                         expectedPointerOnSensorId = POINTER_ID_1,
142                     ),
143                     genPositiveTestCases(
144                         motionEventAction = MotionEvent.ACTION_MOVE,
145                         previousPointerOnSensorId = POINTER_ID_1,
146                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
147                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
148                         expectedPointerOnSensorId = POINTER_ID_1,
149                     ),
150                     genPositiveTestCases(
151                         motionEventAction = MotionEvent.ACTION_MOVE,
152                         previousPointerOnSensorId = INVALID_POINTER_ID,
153                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
154                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
155                         expectedPointerOnSensorId = INVALID_POINTER_ID,
156                     ),
157                     genPositiveTestCases(
158                         motionEventAction = MotionEvent.ACTION_MOVE,
159                         previousPointerOnSensorId = POINTER_ID_1,
160                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
161                         expectedInteractionEvent = InteractionEvent.UP,
162                         expectedPointerOnSensorId = INVALID_POINTER_ID,
163                     ),
164                     genPositiveTestCases(
165                         motionEventAction = MotionEvent.ACTION_MOVE,
166                         previousPointerOnSensorId = INVALID_POINTER_ID,
167                         currentPointers =
168                             listOf(
169                                 TestPointer(id = POINTER_ID_1, onSensor = false),
170                                 TestPointer(id = POINTER_ID_2, onSensor = true)
171                             ),
172                         expectedInteractionEvent = InteractionEvent.DOWN,
173                         expectedPointerOnSensorId = POINTER_ID_2,
174                     ),
175                     genPositiveTestCases(
176                         motionEventAction = MotionEvent.ACTION_MOVE,
177                         previousPointerOnSensorId = POINTER_ID_1,
178                         currentPointers =
179                             listOf(
180                                 TestPointer(id = POINTER_ID_1, onSensor = false),
181                                 TestPointer(id = POINTER_ID_2, onSensor = true)
182                             ),
183                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
184                         expectedPointerOnSensorId = POINTER_ID_2,
185                     ),
186                     // MotionEvent.ACTION_HOVER_MOVE
187                     genPositiveTestCases(
188                         motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
189                         previousPointerOnSensorId = INVALID_POINTER_ID,
190                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
191                         expectedInteractionEvent = InteractionEvent.DOWN,
192                         expectedPointerOnSensorId = POINTER_ID_1,
193                     ),
194                     genPositiveTestCases(
195                         motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
196                         previousPointerOnSensorId = POINTER_ID_1,
197                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
198                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
199                         expectedPointerOnSensorId = POINTER_ID_1,
200                     ),
201                     genPositiveTestCases(
202                         motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
203                         previousPointerOnSensorId = INVALID_POINTER_ID,
204                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
205                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
206                         expectedPointerOnSensorId = INVALID_POINTER_ID,
207                     ),
208                     genPositiveTestCases(
209                         motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
210                         previousPointerOnSensorId = POINTER_ID_1,
211                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
212                         expectedInteractionEvent = InteractionEvent.UP,
213                         expectedPointerOnSensorId = INVALID_POINTER_ID,
214                     ),
215                     // MotionEvent.ACTION_UP
216                     genPositiveTestCases(
217                         motionEventAction = MotionEvent.ACTION_UP,
218                         previousPointerOnSensorId = INVALID_POINTER_ID,
219                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
220                         expectedInteractionEvent = InteractionEvent.UP,
221                         expectedPointerOnSensorId = INVALID_POINTER_ID,
222                     ),
223                     genPositiveTestCases(
224                         motionEventAction = MotionEvent.ACTION_UP,
225                         previousPointerOnSensorId = POINTER_ID_1,
226                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
227                         expectedInteractionEvent = InteractionEvent.UP,
228                         expectedPointerOnSensorId = INVALID_POINTER_ID,
229                     ),
230                     genPositiveTestCases(
231                         motionEventAction = MotionEvent.ACTION_UP,
232                         previousPointerOnSensorId = INVALID_POINTER_ID,
233                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
234                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
235                         expectedPointerOnSensorId = INVALID_POINTER_ID,
236                     ),
237                     // MotionEvent.ACTION_HOVER_EXIT
238                     genPositiveTestCases(
239                         motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
240                         previousPointerOnSensorId = INVALID_POINTER_ID,
241                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
242                         expectedInteractionEvent = InteractionEvent.UP,
243                         expectedPointerOnSensorId = INVALID_POINTER_ID,
244                     ),
245                     genPositiveTestCases(
246                         motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
247                         previousPointerOnSensorId = POINTER_ID_1,
248                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
249                         expectedInteractionEvent = InteractionEvent.UP,
250                         expectedPointerOnSensorId = INVALID_POINTER_ID,
251                     ),
252                     genPositiveTestCases(
253                         motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
254                         previousPointerOnSensorId = INVALID_POINTER_ID,
255                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
256                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
257                         expectedPointerOnSensorId = INVALID_POINTER_ID,
258                     ),
259                     // MotionEvent.ACTION_CANCEL
260                     genPositiveTestCases(
261                         motionEventAction = MotionEvent.ACTION_CANCEL,
262                         previousPointerOnSensorId = INVALID_POINTER_ID,
263                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
264                         expectedInteractionEvent = InteractionEvent.CANCEL,
265                         expectedPointerOnSensorId = INVALID_POINTER_ID,
266                     ),
267                     genPositiveTestCases(
268                         motionEventAction = MotionEvent.ACTION_CANCEL,
269                         previousPointerOnSensorId = POINTER_ID_1,
270                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
271                         expectedInteractionEvent = InteractionEvent.CANCEL,
272                         expectedPointerOnSensorId = INVALID_POINTER_ID,
273                     ),
274                     genPositiveTestCases(
275                         motionEventAction = MotionEvent.ACTION_CANCEL,
276                         previousPointerOnSensorId = INVALID_POINTER_ID,
277                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
278                         expectedInteractionEvent = InteractionEvent.CANCEL,
279                         expectedPointerOnSensorId = INVALID_POINTER_ID,
280                     ),
281                     genPositiveTestCases(
282                         motionEventAction = MotionEvent.ACTION_CANCEL,
283                         previousPointerOnSensorId = POINTER_ID_1,
284                         currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
285                         expectedInteractionEvent = InteractionEvent.CANCEL,
286                         expectedPointerOnSensorId = INVALID_POINTER_ID,
287                     ),
288                     // MotionEvent.ACTION_POINTER_DOWN
289                     genPositiveTestCases(
290                         motionEventAction =
291                             MotionEvent.ACTION_POINTER_DOWN +
292                                 (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
293                         previousPointerOnSensorId = INVALID_POINTER_ID,
294                         currentPointers =
295                             listOf(
296                                 TestPointer(id = POINTER_ID_1, onSensor = true),
297                                 TestPointer(id = POINTER_ID_2, onSensor = false)
298                             ),
299                         expectedInteractionEvent = InteractionEvent.DOWN,
300                         expectedPointerOnSensorId = POINTER_ID_1,
301                     ),
302                     genPositiveTestCases(
303                         motionEventAction =
304                             MotionEvent.ACTION_POINTER_DOWN +
305                                 (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
306                         previousPointerOnSensorId = INVALID_POINTER_ID,
307                         currentPointers =
308                             listOf(
309                                 TestPointer(id = POINTER_ID_1, onSensor = false),
310                                 TestPointer(id = POINTER_ID_2, onSensor = true)
311                             ),
312                         expectedInteractionEvent = InteractionEvent.DOWN,
313                         expectedPointerOnSensorId = POINTER_ID_2
314                     ),
315                     genPositiveTestCases(
316                         motionEventAction =
317                             MotionEvent.ACTION_POINTER_DOWN +
318                                 (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
319                         previousPointerOnSensorId = POINTER_ID_1,
320                         currentPointers =
321                             listOf(
322                                 TestPointer(id = POINTER_ID_1, onSensor = true),
323                                 TestPointer(id = POINTER_ID_2, onSensor = false)
324                             ),
325                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
326                         expectedPointerOnSensorId = POINTER_ID_1,
327                     ),
328                     // MotionEvent.ACTION_POINTER_UP
329                     genPositiveTestCases(
330                         motionEventAction =
331                             MotionEvent.ACTION_POINTER_UP +
332                                 (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
333                         previousPointerOnSensorId = INVALID_POINTER_ID,
334                         currentPointers =
335                             listOf(
336                                 TestPointer(id = POINTER_ID_1, onSensor = false),
337                                 TestPointer(id = POINTER_ID_2, onSensor = false)
338                             ),
339                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
340                         expectedPointerOnSensorId = INVALID_POINTER_ID
341                     ),
342                     genPositiveTestCases(
343                         motionEventAction =
344                             MotionEvent.ACTION_POINTER_UP +
345                                 (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
346                         previousPointerOnSensorId = POINTER_ID_2,
347                         currentPointers =
348                             listOf(
349                                 TestPointer(id = POINTER_ID_1, onSensor = false),
350                                 TestPointer(id = POINTER_ID_2, onSensor = true)
351                             ),
352                         expectedInteractionEvent = InteractionEvent.UP,
353                         expectedPointerOnSensorId = INVALID_POINTER_ID
354                     ),
355                     genPositiveTestCases(
356                         motionEventAction = MotionEvent.ACTION_POINTER_UP,
357                         previousPointerOnSensorId = POINTER_ID_1,
358                         currentPointers =
359                             listOf(
360                                 TestPointer(id = POINTER_ID_1, onSensor = true),
361                                 TestPointer(id = POINTER_ID_2, onSensor = false)
362                             ),
363                         expectedInteractionEvent = InteractionEvent.UP,
364                         expectedPointerOnSensorId = INVALID_POINTER_ID
365                     ),
366                     genPositiveTestCases(
367                         motionEventAction =
368                             MotionEvent.ACTION_POINTER_UP +
369                                 (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
370                         previousPointerOnSensorId = POINTER_ID_1,
371                         currentPointers =
372                             listOf(
373                                 TestPointer(id = POINTER_ID_1, onSensor = true),
374                                 TestPointer(id = POINTER_ID_2, onSensor = false)
375                             ),
376                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
377                         expectedPointerOnSensorId = POINTER_ID_1
378                     ),
379                     genPositiveTestCases(
380                         motionEventAction = MotionEvent.ACTION_POINTER_UP,
381                         previousPointerOnSensorId = POINTER_ID_2,
382                         currentPointers =
383                             listOf(
384                                 TestPointer(id = POINTER_ID_1, onSensor = false),
385                                 TestPointer(id = POINTER_ID_2, onSensor = true)
386                             ),
387                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
388                         expectedPointerOnSensorId = POINTER_ID_2
389                     )
390                 )
391                 .flatten()
392     }
393 }
394 
395 data class TestPointer(val id: Int, val onSensor: Boolean)
396 
397 /* Display dimensions in native resolution and natural orientation. */
398 private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400
399 private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600
400 
401 /* Placeholder touch parameters. */
402 private const val POINTER_ID_1 = 42
403 private const val POINTER_ID_2 = 43
404 private const val NATIVE_MINOR = 2.71828f
405 private const val NATIVE_MAJOR = 3.14f
406 private const val ORIENTATION = 1.2345f
407 private const val TIME = 12345699L
408 private const val GESTURE_START = 12345600L
409 
410 /*
411  * ROTATION_0 map:
412  * _ _ _ _
413  * _ _ O _
414  * _ _ _ _
415  * _ S _ _
416  * _ S _ _
417  * _ _ _ _
418  *
419  * (_) empty space
420  * (S) sensor
421  * (O) touch outside of the sensor
422  */
423 private val ROTATION_0_NATIVE_SENSOR_BOUNDS =
424     Rect(
425         100, /* left */
426         300, /* top */
427         200, /* right */
428         500, /* bottom */
429     )
430 private val ROTATION_0_INPUTS =
431     OrientationBasedInputs(
432         rotation = Surface.ROTATION_0,
433         nativeOrientation = ORIENTATION,
434         nativeXWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterX(),
435         nativeYWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterY(),
436         nativeXOutsideSensor = 250f,
437         nativeYOutsideSensor = 150f,
438     )
439 
440 /*
441  * ROTATION_90 map:
442  * _ _ _ _ _ _
443  * _ O _ _ _ _
444  * _ _ _ S S _
445  * _ _ _ _ _ _
446  *
447  * (_) empty space
448  * (S) sensor
449  * (O) touch outside of the sensor
450  */
451 private val ROTATION_90_NATIVE_SENSOR_BOUNDS =
452     Rect(
453         300, /* left */
454         200, /* top */
455         500, /* right */
456         300, /* bottom */
457     )
458 private val ROTATION_90_INPUTS =
459     OrientationBasedInputs(
460         rotation = Surface.ROTATION_90,
461         nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2),
462         nativeXWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterX(),
463         nativeYWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterY(),
464         nativeXOutsideSensor = 150f,
465         nativeYOutsideSensor = 150f,
466     )
467 
468 /* ROTATION_180 is not supported. It's treated the same as ROTATION_0. */
469 private val ROTATION_180_INPUTS =
470     ROTATION_0_INPUTS.copy(
471         rotation = Surface.ROTATION_180,
472     )
473 
474 /*
475  * ROTATION_270 map:
476  * _ _ _ _ _ _
477  * _ S S _ _ _
478  * _ _ _ _ O _
479  * _ _ _ _ _ _
480  *
481  * (_) empty space
482  * (S) sensor
483  * (O) touch outside of the sensor
484  */
485 private val ROTATION_270_NATIVE_SENSOR_BOUNDS =
486     Rect(
487         100, /* left */
488         100, /* top */
489         300, /* right */
490         200, /* bottom */
491     )
492 private val ROTATION_270_INPUTS =
493     OrientationBasedInputs(
494         rotation = Surface.ROTATION_270,
495         nativeOrientation = (ORIENTATION + Math.PI.toFloat() / 2),
496         nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(),
497         nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(),
498         nativeXOutsideSensor = 450f,
499         nativeYOutsideSensor = 250f,
500     )
501 
502 /* Template [MotionEvent]. */
503 private val MOTION_EVENT =
504     obtainMotionEvent(
505         action = 0,
506         pointerId = POINTER_ID_1,
507         x = 0f,
508         y = 0f,
509         minor = 0f,
510         major = 0f,
511         orientation = ORIENTATION,
512         time = TIME,
513         gestureStart = GESTURE_START,
514     )
515 
516 /* Template [NormalizedTouchData]. */
517 private val NORMALIZED_TOUCH_DATA =
518     NormalizedTouchData(
519         POINTER_ID_1,
520         x = 0f,
521         y = 0f,
522         NATIVE_MINOR,
523         NATIVE_MAJOR,
524         ORIENTATION,
525         TIME,
526         GESTURE_START
527     )
528 
529 /*
530  * Contains test inputs that are tied to a particular device orientation.
531  *
532  * "native" means in native resolution (not scaled).
533  */
534 private data class OrientationBasedInputs(
535     @Rotation val rotation: Int,
536     val nativeOrientation: Float,
537     val nativeXWithinSensor: Float,
538     val nativeYWithinSensor: Float,
539     val nativeXOutsideSensor: Float,
540     val nativeYOutsideSensor: Float,
541 ) {
542 
543     fun toOverlayParams(scaleFactor: Float): UdfpsOverlayParams =
544         UdfpsOverlayParams(
545             sensorBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
546             overlayBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
547             naturalDisplayHeight = (ROTATION_0_NATIVE_DISPLAY_HEIGHT * scaleFactor).toInt(),
548             naturalDisplayWidth = (ROTATION_0_NATIVE_DISPLAY_WIDTH * scaleFactor).toInt(),
549             scaleFactor = scaleFactor,
550             rotation = rotation
551         )
552 
553     fun getNativeX(isWithinSensor: Boolean): Float {
554         return if (isWithinSensor) nativeXWithinSensor else nativeXOutsideSensor
555     }
556 
557     fun getNativeY(isWithinSensor: Boolean): Float {
558         return if (isWithinSensor) nativeYWithinSensor else nativeYOutsideSensor
559     }
560 }
561 
562 private fun genPositiveTestCases(
563     motionEventAction: Int,
564     previousPointerOnSensorId: Int,
565     currentPointers: List<TestPointer>,
566     expectedInteractionEvent: InteractionEvent,
567     expectedPointerOnSensorId: Int
568 ): List<SinglePointerTouchProcessorTest.TestCase> {
569     val scaleFactors = listOf(0.75f, 1f, 1.5f)
570     val orientations =
571         listOf(
572             ROTATION_0_INPUTS,
573             ROTATION_90_INPUTS,
574             ROTATION_180_INPUTS,
575             ROTATION_270_INPUTS,
576         )
577     return scaleFactors.flatMap { scaleFactor ->
578         orientations.map { orientation ->
579             val overlayParams = orientation.toOverlayParams(scaleFactor)
580 
581             val pointerProperties =
582                 currentPointers
583                     .map { pointer ->
584                         val pp = MotionEvent.PointerProperties()
585                         pp.id = pointer.id
586                         pp
587                     }
588                     .toList()
589 
590             val pointerCoords =
591                 currentPointers
592                     .map { pointer ->
593                         val pc = MotionEvent.PointerCoords()
594                         pc.x = orientation.getNativeX(pointer.onSensor) * scaleFactor
595                         pc.y = orientation.getNativeY(pointer.onSensor) * scaleFactor
596                         pc.touchMinor = NATIVE_MINOR * scaleFactor
597                         pc.touchMajor = NATIVE_MAJOR * scaleFactor
598                         pc.orientation = orientation.nativeOrientation
599                         pc
600                     }
601                     .toList()
602 
603             val event =
604                 MOTION_EVENT.copy(
605                     action = motionEventAction,
606                     pointerProperties = pointerProperties,
607                     pointerCoords = pointerCoords
608                 )
609 
610             val expectedTouchDataPointer =
611                 currentPointers.find { it.id == expectedPointerOnSensorId }
612                     ?: currentPointers.find { it.id == previousPointerOnSensorId }
613                         ?: currentPointers[0]
614             val expectedTouchData =
615                 if (motionEventAction != MotionEvent.ACTION_CANCEL) {
616                     NORMALIZED_TOUCH_DATA.copy(
617                         pointerId = expectedTouchDataPointer.id,
618                         x = ROTATION_0_INPUTS.getNativeX(expectedTouchDataPointer.onSensor),
619                         y = ROTATION_0_INPUTS.getNativeY(expectedTouchDataPointer.onSensor)
620                     )
621                 } else {
622                     NormalizedTouchData()
623                 }
624 
625             val expected =
626                 TouchProcessorResult.ProcessedTouch(
627                     event = expectedInteractionEvent,
628                     pointerOnSensorId = expectedPointerOnSensorId,
629                     touchData = expectedTouchData,
630                 )
631             SinglePointerTouchProcessorTest.TestCase(
632                 event = event,
633                 currentPointers = currentPointers,
634                 previousPointerOnSensorId = previousPointerOnSensorId,
635                 overlayParams = overlayParams,
636                 expected = expected,
637             )
638         }
639     }
640 }
641 
642 private fun genTestCasesForUnsupportedAction(
643     motionEventAction: Int
644 ): List<SinglePointerTouchProcessorTest.TestCase> {
645     val isGoodOverlap = true
646     val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID_1)
647     return previousPointerOnSensorIds.map { previousPointerOnSensorId ->
648         val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f)
649         val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap)
650         val nativeY = ROTATION_0_INPUTS.getNativeY(isGoodOverlap)
651         val event =
652             MOTION_EVENT.copy(
653                 action = motionEventAction,
654                 x = nativeX,
655                 y = nativeY,
656                 minor = NATIVE_MINOR,
657                 major = NATIVE_MAJOR,
658             )
659         SinglePointerTouchProcessorTest.TestCase(
660             event = event,
661             currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = isGoodOverlap)),
662             previousPointerOnSensorId = previousPointerOnSensorId,
663             overlayParams = overlayParams,
664             expected = TouchProcessorResult.Failure(),
665         )
666     }
667 }
668 
669 private fun obtainMotionEvent(
670     action: Int,
671     pointerId: Int,
672     x: Float,
673     y: Float,
674     minor: Float,
675     major: Float,
676     orientation: Float,
677     time: Long,
678     gestureStart: Long,
679 ): MotionEvent {
680     val pp = PointerProperties()
681     pp.id = pointerId
682     val pc = MotionEvent.PointerCoords()
683     pc.x = x
684     pc.y = y
685     pc.touchMinor = minor
686     pc.touchMajor = major
687     pc.orientation = orientation
688     return obtainMotionEvent(action, arrayOf(pp), arrayOf(pc), time, gestureStart)
689 }
690 
691 private fun obtainMotionEvent(
692     action: Int,
693     pointerProperties: Array<MotionEvent.PointerProperties>,
694     pointerCoords: Array<MotionEvent.PointerCoords>,
695     time: Long,
696     gestureStart: Long,
697 ): MotionEvent {
698     return MotionEvent.obtain(
699         gestureStart /* downTime */,
700         time /* eventTime */,
701         action /* action */,
702         pointerCoords.size /* pointerCount */,
703         pointerProperties /* pointerProperties */,
704         pointerCoords /* pointerCoords */,
705         0 /* metaState */,
706         0 /* buttonState */,
707         1f /* xPrecision */,
708         1f /* yPrecision */,
709         0 /* deviceId */,
710         0 /* edgeFlags */,
711         0 /* source */,
712         0 /* flags */
713     )
714 }
715 
716 private fun MotionEvent.copy(
717     action: Int = this.action,
718     pointerId: Int = this.getPointerId(0),
719     x: Float = this.rawX,
720     y: Float = this.rawY,
721     minor: Float = this.touchMinor,
722     major: Float = this.touchMajor,
723     orientation: Float = this.orientation,
724     time: Long = this.eventTime,
725     gestureStart: Long = this.downTime,
726 ) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart)
727 
728 private fun MotionEvent.copy(
729     action: Int = this.action,
730     pointerProperties: List<MotionEvent.PointerProperties>,
731     pointerCoords: List<MotionEvent.PointerCoords>,
732     time: Long = this.eventTime,
733     gestureStart: Long = this.downTime
734 ) =
735     obtainMotionEvent(
736         action,
737         pointerProperties.toTypedArray(),
738         pointerCoords.toTypedArray(),
739         time,
740         gestureStart
741     )
742 
743 private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) }
744