1 /*
<lambda>null2  * 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.systemui.SysuiTestCase
27 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
28 import com.google.common.truth.Truth.assertThat
29 import org.junit.Test
30 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
31 import platform.test.runner.parameterized.Parameters
32 import org.junit.runner.RunWith
33 
34 @SmallTest
35 @RunWith(ParameterizedAndroidJunit4::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 /*
469  * ROTATION_180 map:
470  * _ _ _ _
471  * _ _ s _
472  * _ _ s _
473  * _ _ _ _
474  * _ O _ _
475  * _ _ _ _
476  *
477  * (_) empty space
478  * (S) sensor
479  * (O) touch outside of the sensor
480  */
481 private val ROTATION_180_NATIVE_SENSOR_BOUNDS =
482     Rect(
483         200, /* left */
484         100, /* top */
485         300, /* right */
486         300, /* bottom */
487     )
488 private val ROTATION_180_INPUTS =
489     OrientationBasedInputs(
490         rotation = Surface.ROTATION_180,
491         nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2),
492         nativeXWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterX(),
493         nativeYWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterY(),
494         nativeXOutsideSensor = 150f,
495         nativeYOutsideSensor = 450f,
496     )
497 
498 /*
499  * ROTATION_270 map:
500  * _ _ _ _ _ _
501  * _ S S _ _ _
502  * _ _ _ _ O _
503  * _ _ _ _ _ _
504  *
505  * (_) empty space
506  * (S) sensor
507  * (O) touch outside of the sensor
508  */
509 private val ROTATION_270_NATIVE_SENSOR_BOUNDS =
510     Rect(
511         100, /* left */
512         100, /* top */
513         300, /* right */
514         200, /* bottom */
515     )
516 private val ROTATION_270_INPUTS =
517     OrientationBasedInputs(
518         rotation = Surface.ROTATION_270,
519         nativeOrientation = (ORIENTATION + Math.PI.toFloat() / 2),
520         nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(),
521         nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(),
522         nativeXOutsideSensor = 450f,
523         nativeYOutsideSensor = 250f,
524     )
525 
526 /* Template [MotionEvent]. */
527 private val MOTION_EVENT =
528     obtainMotionEvent(
529         action = 0,
530         pointerId = POINTER_ID_1,
531         x = 0f,
532         y = 0f,
533         minor = 0f,
534         major = 0f,
535         orientation = ORIENTATION,
536         time = TIME,
537         gestureStart = GESTURE_START,
538     )
539 
540 /* Template [NormalizedTouchData]. */
541 private val NORMALIZED_TOUCH_DATA =
542     NormalizedTouchData(
543         POINTER_ID_1,
544         x = 0f,
545         y = 0f,
546         NATIVE_MINOR,
547         NATIVE_MAJOR,
548         ORIENTATION,
549         TIME,
550         GESTURE_START
551     )
552 
553 /*
554  * Contains test inputs that are tied to a particular device orientation.
555  *
556  * "native" means in native resolution (not scaled).
557  */
558 private data class OrientationBasedInputs(
559     @Rotation val rotation: Int,
560     val nativeOrientation: Float,
561     val nativeXWithinSensor: Float,
562     val nativeYWithinSensor: Float,
563     val nativeXOutsideSensor: Float,
564     val nativeYOutsideSensor: Float,
565 ) {
566 
toOverlayParamsnull567     fun toOverlayParams(scaleFactor: Float): UdfpsOverlayParams =
568         UdfpsOverlayParams(
569             sensorBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
570             overlayBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
571             naturalDisplayHeight = (ROTATION_0_NATIVE_DISPLAY_HEIGHT * scaleFactor).toInt(),
572             naturalDisplayWidth = (ROTATION_0_NATIVE_DISPLAY_WIDTH * scaleFactor).toInt(),
573             scaleFactor = scaleFactor,
574             rotation = rotation
575         )
576 
577     fun getNativeX(isWithinSensor: Boolean): Float {
578         return if (isWithinSensor) nativeXWithinSensor else nativeXOutsideSensor
579     }
580 
getNativeYnull581     fun getNativeY(isWithinSensor: Boolean): Float {
582         return if (isWithinSensor) nativeYWithinSensor else nativeYOutsideSensor
583     }
584 }
585 
genPositiveTestCasesnull586 private fun genPositiveTestCases(
587     motionEventAction: Int,
588     previousPointerOnSensorId: Int,
589     currentPointers: List<TestPointer>,
590     expectedInteractionEvent: InteractionEvent,
591     expectedPointerOnSensorId: Int
592 ): List<SinglePointerTouchProcessorTest.TestCase> {
593     val scaleFactors = listOf(0.75f, 1f, 1.5f)
594     val orientations =
595         listOf(
596             ROTATION_0_INPUTS,
597             ROTATION_90_INPUTS,
598             ROTATION_180_INPUTS,
599             ROTATION_270_INPUTS,
600         )
601     return scaleFactors.flatMap { scaleFactor ->
602         orientations.map { orientation ->
603             val overlayParams = orientation.toOverlayParams(scaleFactor)
604 
605             val pointerProperties =
606                 currentPointers
607                     .map { pointer ->
608                         val pp = MotionEvent.PointerProperties()
609                         pp.id = pointer.id
610                         pp
611                     }
612                     .toList()
613 
614             val pointerCoords =
615                 currentPointers
616                     .map { pointer ->
617                         val pc = MotionEvent.PointerCoords()
618                         pc.x = orientation.getNativeX(pointer.onSensor) * scaleFactor
619                         pc.y = orientation.getNativeY(pointer.onSensor) * scaleFactor
620                         pc.touchMinor = NATIVE_MINOR * scaleFactor
621                         pc.touchMajor = NATIVE_MAJOR * scaleFactor
622                         pc.orientation = orientation.nativeOrientation
623                         pc
624                     }
625                     .toList()
626 
627             val event =
628                 MOTION_EVENT.copy(
629                     action = motionEventAction,
630                     pointerProperties = pointerProperties,
631                     pointerCoords = pointerCoords
632                 )
633 
634             val expectedTouchDataPointer =
635                 currentPointers.find { it.id == expectedPointerOnSensorId }
636                     ?: currentPointers.find { it.id == previousPointerOnSensorId }
637                         ?: currentPointers[0]
638             val expectedTouchData =
639                 if (motionEventAction != MotionEvent.ACTION_CANCEL) {
640                     NORMALIZED_TOUCH_DATA.copy(
641                         pointerId = expectedTouchDataPointer.id,
642                         x = ROTATION_0_INPUTS.getNativeX(expectedTouchDataPointer.onSensor),
643                         y = ROTATION_0_INPUTS.getNativeY(expectedTouchDataPointer.onSensor)
644                     )
645                 } else {
646                     NormalizedTouchData()
647                 }
648 
649             val expected =
650                 TouchProcessorResult.ProcessedTouch(
651                     event = expectedInteractionEvent,
652                     pointerOnSensorId = expectedPointerOnSensorId,
653                     touchData = expectedTouchData,
654                 )
655             SinglePointerTouchProcessorTest.TestCase(
656                 event = event,
657                 currentPointers = currentPointers,
658                 previousPointerOnSensorId = previousPointerOnSensorId,
659                 overlayParams = overlayParams,
660                 expected = expected,
661             )
662         }
663     }
664 }
665 
obtainMotionEventnull666 private fun obtainMotionEvent(
667     action: Int,
668     pointerId: Int,
669     x: Float,
670     y: Float,
671     minor: Float,
672     major: Float,
673     orientation: Float,
674     time: Long,
675     gestureStart: Long,
676 ): MotionEvent {
677     val pp = PointerProperties()
678     pp.id = pointerId
679     val pc = MotionEvent.PointerCoords()
680     pc.x = x
681     pc.y = y
682     pc.touchMinor = minor
683     pc.touchMajor = major
684     pc.orientation = orientation
685     return obtainMotionEvent(action, arrayOf(pp), arrayOf(pc), time, gestureStart)
686 }
687 
obtainMotionEventnull688 private fun obtainMotionEvent(
689     action: Int,
690     pointerProperties: Array<MotionEvent.PointerProperties>,
691     pointerCoords: Array<MotionEvent.PointerCoords>,
692     time: Long,
693     gestureStart: Long,
694 ): MotionEvent {
695     return MotionEvent.obtain(
696         gestureStart /* downTime */,
697         time /* eventTime */,
698         action /* action */,
699         pointerCoords.size /* pointerCount */,
700         pointerProperties /* pointerProperties */,
701         pointerCoords /* pointerCoords */,
702         0 /* metaState */,
703         0 /* buttonState */,
704         1f /* xPrecision */,
705         1f /* yPrecision */,
706         0 /* deviceId */,
707         0 /* edgeFlags */,
708         0 /* source */,
709         0 /* flags */
710     )
711 }
712 
copynull713 private fun MotionEvent.copy(
714     action: Int = this.action,
715     pointerId: Int = this.getPointerId(0),
716     x: Float = this.rawX,
717     y: Float = this.rawY,
718     minor: Float = this.touchMinor,
719     major: Float = this.touchMajor,
720     orientation: Float = this.orientation,
721     time: Long = this.eventTime,
722     gestureStart: Long = this.downTime,
723 ) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart)
724 
725 private fun MotionEvent.copy(
726     action: Int = this.action,
727     pointerProperties: List<MotionEvent.PointerProperties>,
728     pointerCoords: List<MotionEvent.PointerCoords>,
729     time: Long = this.eventTime,
730     gestureStart: Long = this.downTime
731 ) =
732     obtainMotionEvent(
733         action,
734         pointerProperties.toTypedArray(),
735         pointerCoords.toTypedArray(),
736         time,
737         gestureStart
738     )
739 
740 private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) }
741