1 /*
<lambda>null2  * Copyright 2019 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 androidx.compose.ui.input.pointer
18 
19 import android.util.SparseLongArray
20 import android.view.InputDevice
21 import android.view.MotionEvent
22 import android.view.MotionEvent.ACTION_CANCEL
23 import android.view.MotionEvent.ACTION_DOWN
24 import android.view.MotionEvent.ACTION_HOVER_ENTER
25 import android.view.MotionEvent.ACTION_HOVER_EXIT
26 import android.view.MotionEvent.ACTION_MOVE
27 import android.view.MotionEvent.ACTION_POINTER_DOWN
28 import android.view.MotionEvent.ACTION_POINTER_UP
29 import android.view.MotionEvent.ACTION_SCROLL
30 import android.view.MotionEvent.ACTION_UP
31 import android.view.MotionEvent.AXIS_HSCROLL
32 import android.view.MotionEvent.AXIS_VSCROLL
33 import android.view.MotionEvent.TOOL_TYPE_FINGER
34 import android.view.MotionEvent.TOOL_TYPE_MOUSE
35 import androidx.compose.ui.geometry.Offset
36 import androidx.test.ext.junit.runners.AndroidJUnit4
37 import androidx.test.filters.SmallTest
38 import com.google.common.truth.Truth.assertThat
39 import org.junit.Before
40 import org.junit.Test
41 import org.junit.runner.RunWith
42 
43 @SmallTest
44 @RunWith(AndroidJUnit4::class)
45 class MotionEventAdapterTest {
46 
47     private lateinit var motionEventAdapter: MotionEventAdapter
48     private val positionCalculator =
49         object : PositionCalculator {
50             override fun screenToLocal(positionOnScreen: Offset): Offset = positionOnScreen
51 
52             override fun localToScreen(localPosition: Offset): Offset = localPosition
53         }
54 
55     @Before
56     fun setup() {
57         motionEventAdapter = MotionEventAdapter()
58     }
59 
60     @Test
61     fun convertToolType() {
62         val types =
63             mapOf(
64                 MotionEvent.TOOL_TYPE_FINGER to PointerType.Touch,
65                 MotionEvent.TOOL_TYPE_UNKNOWN to PointerType.Unknown,
66                 MotionEvent.TOOL_TYPE_ERASER to PointerType.Eraser,
67                 MotionEvent.TOOL_TYPE_STYLUS to PointerType.Stylus,
68                 MotionEvent.TOOL_TYPE_MOUSE to PointerType.Mouse,
69             )
70         types.entries.forEach { (toolType, pointerType) ->
71             motionEventAdapter = MotionEventAdapter()
72             val motionEvent =
73                 MotionEvent(
74                     2894,
75                     ACTION_DOWN,
76                     1,
77                     0,
78                     arrayOf(
79                         PointerProperties(1000, toolType),
80                     ),
81                     arrayOf(
82                         PointerCoords(2967f, 5928f),
83                     )
84                 )
85             val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)!!
86             assertPointerInputEventData(
87                 pointerInputEvent.pointers[0],
88                 PointerId(0),
89                 true,
90                 2967f,
91                 5928f,
92                 pointerType
93             )
94         }
95     }
96 
97     @Test
98     fun hoverEventsStay() {
99         // When a hover event happens, the pointer ID should stick around until it is removed.
100         val hoverEnter =
101             MotionEvent(
102                 0,
103                 ACTION_HOVER_ENTER,
104                 1,
105                 0,
106                 arrayOf(PointerProperties(1, TOOL_TYPE_MOUSE)),
107                 arrayOf(PointerCoords(10f, 10f))
108             )
109         val hoverEnterEvent = motionEventAdapter.convertToPointerInputEvent(hoverEnter)!!
110         assertThat(hoverEnterEvent.pointers).hasSize(1)
111         val hoverEnterId = hoverEnterEvent.pointers[0].id
112 
113         val hoverExit =
114             MotionEvent(
115                 1,
116                 ACTION_HOVER_EXIT,
117                 1,
118                 0,
119                 arrayOf(PointerProperties(1, TOOL_TYPE_MOUSE)),
120                 arrayOf(PointerCoords(10f, 10f))
121             )
122 
123         val hoverExitEvent = motionEventAdapter.convertToPointerInputEvent(hoverExit)!!
124         assertThat(hoverExitEvent.pointers).hasSize(1)
125         assertThat(hoverExitEvent.pointers[0].id).isEqualTo(hoverEnterId)
126 
127         val down =
128             MotionEvent(
129                 1,
130                 ACTION_DOWN,
131                 1,
132                 0,
133                 arrayOf(PointerProperties(1, TOOL_TYPE_MOUSE)),
134                 arrayOf(PointerCoords(10f, 10f))
135             )
136 
137         val downEvent = motionEventAdapter.convertToPointerInputEvent(down)!!
138         assertThat(downEvent.pointers).hasSize(1)
139         assertThat(downEvent.pointers[0].id).isEqualTo(hoverEnterId)
140 
141         val up =
142             MotionEvent(
143                 2,
144                 ACTION_UP,
145                 1,
146                 0,
147                 arrayOf(PointerProperties(1, TOOL_TYPE_MOUSE)),
148                 arrayOf(PointerCoords(10f, 10f))
149             )
150 
151         val upEvent = motionEventAdapter.convertToPointerInputEvent(up)!!
152         assertThat(upEvent.pointers).hasSize(1)
153         assertThat(upEvent.pointers[0].id).isEqualTo(hoverEnterId)
154 
155         val hoverEnterEvent2 = motionEventAdapter.convertToPointerInputEvent(hoverEnter)!!
156         assertThat(hoverEnterEvent2.pointers).hasSize(1)
157         assertThat(hoverEnterEvent2.pointers[0].id).isEqualTo(hoverEnterId)
158         motionEventAdapter.convertToPointerInputEvent(hoverExit)!!
159 
160         val touchDown =
161             MotionEvent(
162                 3,
163                 ACTION_DOWN,
164                 1,
165                 0,
166                 arrayOf(PointerProperties(1, TOOL_TYPE_FINGER)),
167                 arrayOf(PointerCoords(10f, 10f))
168             )
169         val touchDownEvent = motionEventAdapter.convertToPointerInputEvent(touchDown)!!
170         assertThat(touchDownEvent.pointers).hasSize(1)
171         assertThat(touchDownEvent.pointers[0].id).isNotEqualTo(hoverEnterId)
172         val touchDownId = touchDownEvent.pointers[0].id
173 
174         val touchUp =
175             MotionEvent(
176                 4,
177                 ACTION_UP,
178                 1,
179                 0,
180                 arrayOf(PointerProperties(1, TOOL_TYPE_FINGER)),
181                 arrayOf(PointerCoords(10f, 10f))
182             )
183         val touchUpEvent = motionEventAdapter.convertToPointerInputEvent(touchUp)!!
184         assertThat(touchUpEvent.pointers).hasSize(1)
185         assertThat(touchUpEvent.pointers[0].id).isEqualTo(touchDownEvent.pointers[0].id)
186 
187         val hoverEnterEvent3 = motionEventAdapter.convertToPointerInputEvent(hoverEnter)!!
188         assertThat(hoverEnterEvent3.pointers).hasSize(1)
189         assertThat(hoverEnterEvent3.pointers[0].id).isNotEqualTo(touchDownId)
190         assertThat(hoverEnterEvent3.pointers[0].id).isNotEqualTo(hoverEnterId)
191     }
192 
193     @Test
194     fun robustIdConversion() {
195         // When an ID shows up unexpectedly, it shouldn't crash
196         val hoverExit =
197             MotionEvent(
198                 3,
199                 ACTION_HOVER_EXIT,
200                 1,
201                 0,
202                 arrayOf(PointerProperties(1, TOOL_TYPE_MOUSE)),
203                 arrayOf(PointerCoords(10f, 10f))
204             )
205         val event = motionEventAdapter.convertToPointerInputEvent(hoverExit)!!
206         assertThat(event.pointers).hasSize(1)
207     }
208 
209     @Test
210     fun convertToPointerInputEvent_1pointerActionDown_convertsCorrectly() {
211         val motionEvent =
212             MotionEvent(
213                 2894,
214                 ACTION_DOWN,
215                 1,
216                 0,
217                 arrayOf(PointerProperties(8290)),
218                 arrayOf(PointerCoords(2967f, 5928f))
219             )
220 
221         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
222         assertThat(pointerInputEvent).isNotNull()
223 
224         val uptime = pointerInputEvent!!.uptime
225         val pointers = pointerInputEvent.pointers
226         val platformEvent = pointerInputEvent.motionEvent
227         assertThat(uptime).isEqualTo(2_894L)
228         assertThat(pointers).hasSize(1)
229         assertPointerInputEventData(pointers[0], PointerId(0), true, 2967f, 5928f)
230         assertThat(platformEvent).isSameInstanceAs(motionEvent)
231     }
232 
233     @Test
234     fun convertToPointerInputEvent_1pointerActionMove_convertsCorrectly() {
235         motionEventAdapter.convertToPointerInputEvent(
236             MotionEvent(
237                 1,
238                 ACTION_DOWN,
239                 1,
240                 0,
241                 arrayOf(PointerProperties(2)),
242                 arrayOf(PointerCoords(3f, 4f))
243             )
244         )
245         val motionEvent =
246             MotionEvent(
247                 5,
248                 ACTION_MOVE,
249                 1,
250                 0,
251                 arrayOf(PointerProperties(2)),
252                 arrayOf(PointerCoords(6f, 7f))
253             )
254 
255         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
256         assertThat(pointerInputEvent).isNotNull()
257 
258         val uptime = pointerInputEvent!!.uptime
259         val pointers = pointerInputEvent.pointers
260         assertThat(uptime).isEqualTo(5L)
261         assertThat(pointers).hasSize(1)
262         assertPointerInputEventData(pointers[0], PointerId(0), true, 6f, 7f)
263     }
264 
265     @Test
266     fun convertToPointerInputEvent_1pointerActionUp_convertsCorrectly() {
267         motionEventAdapter.convertToPointerInputEvent(
268             MotionEvent(
269                 10,
270                 ACTION_DOWN,
271                 1,
272                 0,
273                 arrayOf(PointerProperties(46)),
274                 arrayOf(PointerCoords(3f, 4f))
275             )
276         )
277         val motionEvent =
278             MotionEvent(
279                 34,
280                 ACTION_UP,
281                 1,
282                 0,
283                 arrayOf(PointerProperties(46)),
284                 arrayOf(PointerCoords(3f, 4f))
285             )
286 
287         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
288         assertThat(pointerInputEvent).isNotNull()
289 
290         val uptime = pointerInputEvent!!.uptime
291         val pointers = pointerInputEvent.pointers
292         assertThat(uptime).isEqualTo(34L)
293         assertThat(uptime).isEqualTo(34L)
294         assertThat(pointers).hasSize(1)
295         assertPointerInputEventData(pointers[0], PointerId(0), false, 3f, 4f)
296     }
297 
298     @Test
299     fun convertToPointerInputEvent_2pointers1stPointerActionPointerDown_convertsCorrectly() {
300         motionEventAdapter.convertToPointerInputEvent(
301             MotionEvent(
302                 1,
303                 ACTION_DOWN,
304                 1,
305                 0,
306                 arrayOf(PointerProperties(2)),
307                 arrayOf(PointerCoords(3f, 4f))
308             )
309         )
310         val motionEvent =
311             MotionEvent(
312                 4,
313                 ACTION_POINTER_DOWN,
314                 2,
315                 0,
316                 arrayOf(PointerProperties(5), PointerProperties(2)),
317                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
318             )
319 
320         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
321         assertThat(pointerInputEvent).isNotNull()
322 
323         val uptime = pointerInputEvent!!.uptime
324         val pointers = pointerInputEvent.pointers
325         assertThat(uptime).isEqualTo(4L)
326         assertThat(pointers).hasSize(2)
327         assertPointerInputEventData(pointers[0], PointerId(1), true, 7f, 8f)
328         assertPointerInputEventData(pointers[1], PointerId(0), true, 3f, 4f)
329     }
330 
331     @Test
332     fun convertToPointerInputEvent_2pointers2ndPointerActionPointerDown_convertsCorrectly() {
333         motionEventAdapter.convertToPointerInputEvent(
334             MotionEvent(
335                 1,
336                 ACTION_DOWN,
337                 1,
338                 0,
339                 arrayOf(PointerProperties(2)),
340                 arrayOf(PointerCoords(3f, 4f))
341             )
342         )
343         val motionEvent =
344             MotionEvent(
345                 4,
346                 ACTION_POINTER_DOWN,
347                 2,
348                 1,
349                 arrayOf(PointerProperties(2), PointerProperties(5)),
350                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
351             )
352 
353         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
354         assertThat(pointerInputEvent).isNotNull()
355 
356         val uptime = pointerInputEvent!!.uptime
357         val pointers = pointerInputEvent.pointers
358         assertThat(uptime).isEqualTo(4L)
359         assertThat(pointers).hasSize(2)
360         assertPointerInputEventData(pointers[0], PointerId(0), true, 3f, 4f)
361         assertPointerInputEventData(pointers[1], PointerId(1), true, 7f, 8f)
362     }
363 
364     @Test
365     fun convertToPointerInputEvent_3pointers1stPointerActionPointerDown_convertsCorrectly() {
366         motionEventAdapter.convertToPointerInputEvent(
367             MotionEvent(
368                 1,
369                 ACTION_DOWN,
370                 1,
371                 0,
372                 arrayOf(PointerProperties(2)),
373                 arrayOf(PointerCoords(3f, 4f))
374             )
375         )
376         motionEventAdapter.convertToPointerInputEvent(
377             MotionEvent(
378                 4,
379                 ACTION_POINTER_DOWN,
380                 2,
381                 1,
382                 arrayOf(PointerProperties(2), PointerProperties(5)),
383                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
384             )
385         )
386         val motionEvent =
387             MotionEvent(
388                 12,
389                 ACTION_POINTER_DOWN,
390                 3,
391                 0,
392                 arrayOf(PointerProperties(9), PointerProperties(2), PointerProperties(5)),
393                 arrayOf(PointerCoords(10f, 11f), PointerCoords(3f, 4f), PointerCoords(7f, 8f))
394             )
395 
396         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
397         assertThat(pointerInputEvent).isNotNull()
398 
399         val uptime = pointerInputEvent!!.uptime
400         val pointers = pointerInputEvent.pointers
401         assertThat(uptime).isEqualTo(12L)
402         assertThat(pointers).hasSize(3)
403         assertPointerInputEventData(pointers[0], PointerId(2), true, 10f, 11f)
404         assertPointerInputEventData(pointers[1], PointerId(0), true, 3f, 4f)
405         assertPointerInputEventData(pointers[2], PointerId(1), true, 7f, 8f)
406     }
407 
408     @Test
409     fun convertToPointerInputEvent_3pointers2ndPointerActionPointerDown_convertsCorrectly() {
410         motionEventAdapter.convertToPointerInputEvent(
411             MotionEvent(
412                 1,
413                 ACTION_DOWN,
414                 1,
415                 0,
416                 arrayOf(PointerProperties(2)),
417                 arrayOf(PointerCoords(3f, 4f))
418             )
419         )
420         motionEventAdapter.convertToPointerInputEvent(
421             MotionEvent(
422                 4,
423                 ACTION_POINTER_DOWN,
424                 2,
425                 1,
426                 arrayOf(PointerProperties(2), PointerProperties(5)),
427                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
428             )
429         )
430         val motionEvent =
431             MotionEvent(
432                 12,
433                 ACTION_POINTER_DOWN,
434                 3,
435                 1,
436                 arrayOf(PointerProperties(2), PointerProperties(9), PointerProperties(5)),
437                 arrayOf(PointerCoords(3f, 4f), PointerCoords(10f, 11f), PointerCoords(7f, 8f))
438             )
439 
440         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
441         assertThat(pointerInputEvent).isNotNull()
442 
443         val uptime = pointerInputEvent!!.uptime
444         val pointers = pointerInputEvent.pointers
445         assertThat(uptime).isEqualTo(12L)
446         assertThat(pointers).hasSize(3)
447         assertPointerInputEventData(pointers[0], PointerId(0), true, 3f, 4f)
448         assertPointerInputEventData(pointers[1], PointerId(2), true, 10f, 11f)
449         assertPointerInputEventData(pointers[2], PointerId(1), true, 7f, 8f)
450     }
451 
452     @Test
453     fun convertToPointerInputEvent_3pointers3rdPointerActionPointerDown_convertsCorrectly() {
454         motionEventAdapter.convertToPointerInputEvent(
455             MotionEvent(
456                 1,
457                 ACTION_DOWN,
458                 1,
459                 0,
460                 arrayOf(PointerProperties(2)),
461                 arrayOf(PointerCoords(3f, 4f))
462             )
463         )
464         motionEventAdapter.convertToPointerInputEvent(
465             MotionEvent(
466                 4,
467                 ACTION_POINTER_DOWN,
468                 2,
469                 1,
470                 arrayOf(PointerProperties(2), PointerProperties(5)),
471                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
472             )
473         )
474         val motionEvent =
475             MotionEvent(
476                 12,
477                 ACTION_POINTER_DOWN,
478                 3,
479                 2,
480                 arrayOf(PointerProperties(2), PointerProperties(5), PointerProperties(9)),
481                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f), PointerCoords(10f, 11f))
482             )
483 
484         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
485         assertThat(pointerInputEvent).isNotNull()
486 
487         val uptime = pointerInputEvent!!.uptime
488         val pointers = pointerInputEvent.pointers
489         assertThat(uptime).isEqualTo(12L)
490         assertThat(pointers).hasSize(3)
491         assertPointerInputEventData(pointers[0], PointerId(0), true, 3f, 4f)
492         assertPointerInputEventData(pointers[1], PointerId(1), true, 7f, 8f)
493         assertPointerInputEventData(pointers[2], PointerId(2), true, 10f, 11f)
494     }
495 
496     @Test
497     fun convertToPointerInputEvent_2pointersActionMove_convertsCorrectly() {
498         motionEventAdapter.convertToPointerInputEvent(
499             MotionEvent(
500                 1,
501                 ACTION_DOWN,
502                 1,
503                 0,
504                 arrayOf(PointerProperties(2)),
505                 arrayOf(PointerCoords(3f, 4f))
506             )
507         )
508         motionEventAdapter.convertToPointerInputEvent(
509             MotionEvent(
510                 4,
511                 ACTION_POINTER_DOWN,
512                 2,
513                 1,
514                 arrayOf(PointerProperties(2), PointerProperties(5)),
515                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
516             )
517         )
518         val motionEvent =
519             MotionEvent(
520                 10,
521                 ACTION_MOVE,
522                 2,
523                 0,
524                 arrayOf(PointerProperties(2), PointerProperties(5)),
525                 arrayOf(PointerCoords(11f, 12f), PointerCoords(13f, 15f))
526             )
527 
528         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
529         assertThat(pointerInputEvent).isNotNull()
530 
531         val uptime = pointerInputEvent!!.uptime
532         val pointers = pointerInputEvent.pointers
533         assertThat(uptime).isEqualTo(10L)
534         assertThat(pointers).hasSize(2)
535         assertPointerInputEventData(pointers[0], PointerId(0), true, 11f, 12f)
536         assertPointerInputEventData(pointers[1], PointerId(1), true, 13f, 15f)
537     }
538 
539     @Test
540     fun convertToPointerInputEvent_2pointers1stPointerActionPointerUP_convertsCorrectly() {
541         motionEventAdapter.convertToPointerInputEvent(
542             MotionEvent(
543                 1,
544                 ACTION_DOWN,
545                 1,
546                 0,
547                 arrayOf(PointerProperties(2)),
548                 arrayOf(PointerCoords(3f, 4f))
549             )
550         )
551         motionEventAdapter.convertToPointerInputEvent(
552             MotionEvent(
553                 4,
554                 ACTION_POINTER_DOWN,
555                 2,
556                 1,
557                 arrayOf(PointerProperties(2), PointerProperties(5)),
558                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
559             )
560         )
561 
562         val motionEvent =
563             MotionEvent(
564                 10,
565                 ACTION_POINTER_UP,
566                 2,
567                 0,
568                 arrayOf(PointerProperties(2), PointerProperties(5)),
569                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
570             )
571         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
572         assertThat(pointerInputEvent).isNotNull()
573 
574         val uptime = pointerInputEvent!!.uptime
575         val pointers = pointerInputEvent.pointers
576         assertThat(uptime).isEqualTo(10L)
577         assertThat(pointers).hasSize(2)
578         assertPointerInputEventData(pointers[0], PointerId(0), false, 3f, 4f)
579         assertPointerInputEventData(pointers[1], PointerId(1), true, 7f, 8f)
580     }
581 
582     @Test
583     fun convertToPointerInputEvent_2pointers2ndPointerActionPointerUp_convertsCorrectly() {
584         motionEventAdapter.convertToPointerInputEvent(
585             MotionEvent(
586                 1,
587                 ACTION_DOWN,
588                 1,
589                 0,
590                 arrayOf(PointerProperties(2)),
591                 arrayOf(PointerCoords(3f, 4f))
592             )
593         )
594         motionEventAdapter.convertToPointerInputEvent(
595             MotionEvent(
596                 4,
597                 ACTION_POINTER_DOWN,
598                 2,
599                 1,
600                 arrayOf(PointerProperties(2), PointerProperties(5)),
601                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
602             )
603         )
604 
605         val motionEvent =
606             MotionEvent(
607                 10,
608                 ACTION_POINTER_UP,
609                 2,
610                 1,
611                 arrayOf(PointerProperties(2), PointerProperties(5)),
612                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
613             )
614         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
615         assertThat(pointerInputEvent).isNotNull()
616 
617         val uptime = pointerInputEvent!!.uptime
618         val pointers = pointerInputEvent.pointers
619         assertThat(uptime).isEqualTo(10L)
620         assertThat(pointers).hasSize(2)
621         assertPointerInputEventData(pointers[0], PointerId(0), true, 3f, 4f)
622         assertPointerInputEventData(pointers[1], PointerId(1), false, 7f, 8f)
623     }
624 
625     @Test
626     fun convertToPointerInputEvent_3pointers1stPointerActionPointerUp_convertsCorrectly() {
627         motionEventAdapter.convertToPointerInputEvent(
628             MotionEvent(
629                 1,
630                 ACTION_DOWN,
631                 1,
632                 0,
633                 arrayOf(PointerProperties(2)),
634                 arrayOf(PointerCoords(3f, 4f))
635             )
636         )
637         motionEventAdapter.convertToPointerInputEvent(
638             MotionEvent(
639                 4,
640                 ACTION_POINTER_DOWN,
641                 2,
642                 1,
643                 arrayOf(PointerProperties(2), PointerProperties(5)),
644                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
645             )
646         )
647         motionEventAdapter.convertToPointerInputEvent(
648             MotionEvent(
649                 12,
650                 ACTION_POINTER_DOWN,
651                 3,
652                 2,
653                 arrayOf(PointerProperties(2), PointerProperties(5), PointerProperties(9)),
654                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f), PointerCoords(10f, 11f))
655             )
656         )
657 
658         val motionEvent =
659             MotionEvent(
660                 20,
661                 ACTION_POINTER_UP,
662                 3,
663                 0,
664                 arrayOf(PointerProperties(2), PointerProperties(5), PointerProperties(9)),
665                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f), PointerCoords(10f, 11f))
666             )
667 
668         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
669         assertThat(pointerInputEvent).isNotNull()
670 
671         val uptime = pointerInputEvent!!.uptime
672         val pointers = pointerInputEvent.pointers
673         assertThat(uptime).isEqualTo(20L)
674         assertThat(pointers).hasSize(3)
675         assertPointerInputEventData(pointers[0], PointerId(0), false, 3f, 4f)
676         assertPointerInputEventData(pointers[1], PointerId(1), true, 7f, 8f)
677         assertPointerInputEventData(pointers[2], PointerId(2), true, 10f, 11f)
678     }
679 
680     @Test
681     fun convertToPointerInputEvent_3pointers2ndPointerActionPointerUp_convertsCorrectly() {
682         motionEventAdapter.convertToPointerInputEvent(
683             MotionEvent(
684                 1,
685                 ACTION_DOWN,
686                 1,
687                 0,
688                 arrayOf(PointerProperties(2)),
689                 arrayOf(PointerCoords(3f, 4f))
690             )
691         )
692         motionEventAdapter.convertToPointerInputEvent(
693             MotionEvent(
694                 4,
695                 ACTION_POINTER_DOWN,
696                 2,
697                 1,
698                 arrayOf(PointerProperties(2), PointerProperties(5)),
699                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
700             )
701         )
702         motionEventAdapter.convertToPointerInputEvent(
703             MotionEvent(
704                 12,
705                 ACTION_POINTER_DOWN,
706                 3,
707                 2,
708                 arrayOf(PointerProperties(2), PointerProperties(5), PointerProperties(9)),
709                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f), PointerCoords(10f, 11f))
710             )
711         )
712 
713         val motionEvent =
714             MotionEvent(
715                 20,
716                 ACTION_POINTER_UP,
717                 3,
718                 1,
719                 arrayOf(PointerProperties(2), PointerProperties(5), PointerProperties(9)),
720                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f), PointerCoords(10f, 11f))
721             )
722 
723         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
724         assertThat(pointerInputEvent).isNotNull()
725 
726         val uptime = pointerInputEvent!!.uptime
727         val pointers = pointerInputEvent.pointers
728         assertThat(uptime).isEqualTo(20L)
729         assertThat(pointers).hasSize(3)
730         assertPointerInputEventData(pointers[0], PointerId(0), true, 3f, 4f)
731         assertPointerInputEventData(pointers[1], PointerId(1), false, 7f, 8f)
732         assertPointerInputEventData(pointers[2], PointerId(2), true, 10f, 11f)
733     }
734 
735     @Test
736     fun convertToPointerInputEvent_3pointers3rdPointerActionPointerUp_convertsCorrectly() {
737         motionEventAdapter.convertToPointerInputEvent(
738             MotionEvent(
739                 1,
740                 ACTION_DOWN,
741                 1,
742                 0,
743                 arrayOf(PointerProperties(2)),
744                 arrayOf(PointerCoords(3f, 4f))
745             )
746         )
747         motionEventAdapter.convertToPointerInputEvent(
748             MotionEvent(
749                 4,
750                 ACTION_POINTER_DOWN,
751                 2,
752                 1,
753                 arrayOf(PointerProperties(2), PointerProperties(5)),
754                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f))
755             )
756         )
757         motionEventAdapter.convertToPointerInputEvent(
758             MotionEvent(
759                 12,
760                 ACTION_POINTER_DOWN,
761                 3,
762                 2,
763                 arrayOf(PointerProperties(2), PointerProperties(5), PointerProperties(9)),
764                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f), PointerCoords(10f, 11f))
765             )
766         )
767 
768         val motionEvent =
769             MotionEvent(
770                 20,
771                 ACTION_POINTER_UP,
772                 3,
773                 2,
774                 arrayOf(PointerProperties(2), PointerProperties(5), PointerProperties(9)),
775                 arrayOf(PointerCoords(3f, 4f), PointerCoords(7f, 8f), PointerCoords(10f, 11f))
776             )
777 
778         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
779         assertThat(pointerInputEvent).isNotNull()
780 
781         val uptime = pointerInputEvent!!.uptime
782         val pointers = pointerInputEvent.pointers
783         assertThat(uptime).isEqualTo(20L)
784         assertThat(pointers).hasSize(3)
785         assertPointerInputEventData(pointers[0], PointerId(0), true, 3f, 4f)
786         assertPointerInputEventData(pointers[1], PointerId(1), true, 7f, 8f)
787         assertPointerInputEventData(pointers[2], PointerId(2), false, 10f, 11f)
788     }
789 
790     @Test
791     fun convertToPointerInputEvent_downUpDownUpDownUpSameMotionEventId_pointerIdsAreUnique() {
792         val down1 =
793             MotionEvent(
794                 100,
795                 ACTION_DOWN,
796                 1,
797                 0,
798                 arrayOf(PointerProperties(0)),
799                 arrayOf(PointerCoords(10f, 11f))
800             )
801 
802         val up1 =
803             MotionEvent(
804                 200,
805                 ACTION_UP,
806                 1,
807                 0,
808                 arrayOf(PointerProperties(0)),
809                 arrayOf(PointerCoords(10f, 11f))
810             )
811 
812         val down2 =
813             MotionEvent(
814                 300,
815                 ACTION_DOWN,
816                 1,
817                 0,
818                 arrayOf(PointerProperties(0)),
819                 arrayOf(PointerCoords(20f, 21f))
820             )
821 
822         val up2 =
823             MotionEvent(
824                 400,
825                 ACTION_UP,
826                 1,
827                 0,
828                 arrayOf(PointerProperties(0)),
829                 arrayOf(PointerCoords(20f, 21f))
830             )
831 
832         val down3 =
833             MotionEvent(
834                 500,
835                 ACTION_DOWN,
836                 1,
837                 0,
838                 arrayOf(PointerProperties(0)),
839                 arrayOf(PointerCoords(30f, 31f))
840             )
841 
842         val up3 =
843             MotionEvent(
844                 600,
845                 ACTION_UP,
846                 1,
847                 0,
848                 arrayOf(PointerProperties(0)),
849                 arrayOf(PointerCoords(30f, 31f))
850             )
851 
852         // Test the different events sequentially, since the returned event contains a list that
853         // will be reused by convertToPointerInputEvent for performance, so it shouldn't be held
854         // for longer than needed during the sequential dispatch.
855 
856         val pointerInputEventDown1 = motionEventAdapter.convertToPointerInputEvent(down1)
857         assertThat(pointerInputEventDown1).isNotNull()
858         assertThat(pointerInputEventDown1!!.pointers[0].id).isEqualTo(PointerId(0))
859 
860         val pointerInputEventUp1 = motionEventAdapter.convertToPointerInputEvent(up1)
861         assertThat(pointerInputEventUp1).isNotNull()
862         assertThat(pointerInputEventUp1!!.pointers[0].id).isEqualTo(PointerId(0))
863 
864         val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
865         assertThat(pointerInputEventDown2).isNotNull()
866         assertThat(pointerInputEventDown2!!.pointers[0].id).isEqualTo(PointerId(1))
867 
868         val pointerInputEventUp2 = motionEventAdapter.convertToPointerInputEvent(up2)
869         assertThat(pointerInputEventUp2).isNotNull()
870         assertThat(pointerInputEventUp2!!.pointers[0].id).isEqualTo(PointerId(1))
871 
872         val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
873         assertThat(pointerInputEventDown3).isNotNull()
874         assertThat(pointerInputEventDown3!!.pointers[0].id).isEqualTo(PointerId(2))
875 
876         val pointerInputEventUp3 = motionEventAdapter.convertToPointerInputEvent(up3)
877         assertThat(pointerInputEventUp3).isNotNull()
878         assertThat(pointerInputEventUp3!!.pointers[0].id).isEqualTo(PointerId(2))
879     }
880 
881     @Test
882     fun convertToPointerInputEvent_downDownDownRandomMotionEventIds_pointerIdsAreUnique() {
883         val down1 =
884             MotionEvent(
885                 100,
886                 ACTION_DOWN,
887                 1,
888                 0,
889                 arrayOf(PointerProperties(9276)),
890                 arrayOf(PointerCoords(10f, 11f))
891             )
892 
893         val down2 =
894             MotionEvent(
895                 200,
896                 ACTION_POINTER_DOWN,
897                 2,
898                 1,
899                 arrayOf(PointerProperties(9276), PointerProperties(1759)),
900                 arrayOf(PointerCoords(10f, 11f), PointerCoords(20f, 21f))
901             )
902 
903         val down3 =
904             MotionEvent(
905                 300,
906                 ACTION_POINTER_DOWN,
907                 3,
908                 2,
909                 arrayOf(PointerProperties(9276), PointerProperties(1759), PointerProperties(5043)),
910                 arrayOf(PointerCoords(10f, 11f), PointerCoords(20f, 21f), PointerCoords(30f, 31f))
911             )
912 
913         // Test the different events sequentially, since the returned event contains a list that
914         // will be reused by convertToPointerInputEvent for performance, so it shouldn't be held
915         // for longer than needed during the sequential dispatch.
916 
917         val pointerInputEventDown1 = motionEventAdapter.convertToPointerInputEvent(down1)
918 
919         assertThat(pointerInputEventDown1).isNotNull()
920         assertThat(pointerInputEventDown1!!.pointers).hasSize(1)
921         assertThat(pointerInputEventDown1.pointers[0].id).isEqualTo(PointerId(0))
922 
923         val pointerInputEventDown2 = motionEventAdapter.convertToPointerInputEvent(down2)
924 
925         assertThat(pointerInputEventDown2).isNotNull()
926         assertThat(pointerInputEventDown2!!.pointers).hasSize(2)
927         assertThat(pointerInputEventDown2.pointers[0].id).isEqualTo(PointerId(0))
928         assertThat(pointerInputEventDown2.pointers[1].id).isEqualTo(PointerId(1))
929 
930         val pointerInputEventDown3 = motionEventAdapter.convertToPointerInputEvent(down3)
931 
932         assertThat(pointerInputEventDown3).isNotNull()
933         assertThat(pointerInputEventDown3!!.pointers).hasSize(3)
934         assertThat(pointerInputEventDown2.pointers[0].id).isEqualTo(PointerId(0))
935         assertThat(pointerInputEventDown2.pointers[1].id).isEqualTo(PointerId(1))
936         assertThat(pointerInputEventDown3.pointers[2].id).isEqualTo(PointerId(2))
937     }
938 
939     @Test
940     fun convertToPointerInputEvent_motionEventOffset_usesRawCoordinatesInsteadOfOffset() {
941         val motionEvent =
942             MotionEvent(
943                 0,
944                 ACTION_DOWN,
945                 1,
946                 0,
947                 arrayOf(PointerProperties(0)),
948                 arrayOf(PointerCoords(1f, 2f))
949             )
950 
951         motionEvent.offsetLocation(10f, 20f)
952 
953         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
954         assertThat(pointerInputEvent).isNotNull()
955 
956         val uptime = pointerInputEvent!!.uptime
957         val pointers = pointerInputEvent.pointers
958         assertThat(uptime).isEqualTo(0L)
959         assertThat(pointers).hasSize(1)
960         assertPointerInputEventData(
961             pointers[0],
962             PointerId(0),
963             true,
964             1f,
965             2f,
966             originalX = 11f,
967             originalY = 22f
968         )
969     }
970 
971     @Test
972     fun convertToPointerInputEvent_actionCancel_returnsNull() {
973         val motionEvent =
974             MotionEvent(
975                 0,
976                 ACTION_CANCEL,
977                 1,
978                 0,
979                 arrayOf(PointerProperties(0)),
980                 arrayOf(PointerCoords(1f, 2f))
981             )
982 
983         motionEvent.offsetLocation(10f, 20f)
984 
985         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
986         assertThat(pointerInputEvent).isNull()
987     }
988 
989     @Test
990     fun convertToPointerInputEvent_downUp_noPointersTracked() {
991         val motionEvent1 =
992             MotionEvent(
993                 2894,
994                 ACTION_DOWN,
995                 1,
996                 0,
997                 arrayOf(PointerProperties(8290)),
998                 arrayOf(PointerCoords(2967f, 5928f))
999             )
1000         val motionEvent2 =
1001             MotionEvent(
1002                 2894,
1003                 ACTION_UP,
1004                 1,
1005                 0,
1006                 arrayOf(PointerProperties(8290)),
1007                 arrayOf(PointerCoords(2967f, 5928f))
1008             )
1009 
1010         motionEventAdapter.convertToPointerInputEvent(motionEvent1)
1011         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
1012 
1013         assertThat(motionEventAdapter.motionEventToComposePointerIdMap.size()).isEqualTo(0)
1014     }
1015 
1016     @Test
1017     fun convertToPointerInputEvent_downDown_correctPointersTracked() {
1018         val motionEvent1 =
1019             MotionEvent(
1020                 1,
1021                 ACTION_DOWN,
1022                 1,
1023                 0,
1024                 arrayOf(PointerProperties(2)),
1025                 arrayOf(PointerCoords(3f, 4f))
1026             )
1027         val motionEvent2 =
1028             MotionEvent(
1029                 4,
1030                 ACTION_POINTER_DOWN,
1031                 2,
1032                 0,
1033                 arrayOf(PointerProperties(5), PointerProperties(2)),
1034                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1035             )
1036 
1037         motionEventAdapter.convertToPointerInputEvent(motionEvent1)
1038         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
1039 
1040         assertThat(motionEventAdapter.motionEventToComposePointerIdMap.toMap())
1041             .containsExactlyEntriesIn(mapOf(2 to PointerId(0), 5 to PointerId(1)))
1042     }
1043 
1044     @Test
1045     fun convertToPointerInputEvent_downDownFirstUp_correctPointerTracked() {
1046         val motionEvent1 =
1047             MotionEvent(
1048                 1,
1049                 ACTION_DOWN,
1050                 1,
1051                 0,
1052                 arrayOf(PointerProperties(2)),
1053                 arrayOf(PointerCoords(3f, 4f))
1054             )
1055         val motionEvent2 =
1056             MotionEvent(
1057                 4,
1058                 ACTION_POINTER_DOWN,
1059                 2,
1060                 0,
1061                 arrayOf(PointerProperties(5), PointerProperties(2)),
1062                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1063             )
1064         val motionEvent3 =
1065             MotionEvent(
1066                 10,
1067                 ACTION_POINTER_UP,
1068                 2,
1069                 0,
1070                 arrayOf(PointerProperties(5), PointerProperties(2)),
1071                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1072             )
1073 
1074         motionEventAdapter.convertToPointerInputEvent(motionEvent1)
1075         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
1076         motionEventAdapter.convertToPointerInputEvent(motionEvent3)
1077 
1078         assertThat(motionEventAdapter.motionEventToComposePointerIdMap.toMap())
1079             .containsExactlyEntriesIn(mapOf(2 to PointerId(0)))
1080     }
1081 
1082     @Test
1083     fun convertToPointerInputEvent_downDownSecondUp_correctPointerTracked() {
1084         val motionEvent1 =
1085             MotionEvent(
1086                 1,
1087                 ACTION_DOWN,
1088                 1,
1089                 0,
1090                 arrayOf(PointerProperties(2)),
1091                 arrayOf(PointerCoords(3f, 4f))
1092             )
1093         val motionEvent2 =
1094             MotionEvent(
1095                 4,
1096                 ACTION_POINTER_DOWN,
1097                 2,
1098                 0,
1099                 arrayOf(PointerProperties(5), PointerProperties(2)),
1100                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1101             )
1102         val motionEvent3 =
1103             MotionEvent(
1104                 10,
1105                 ACTION_POINTER_UP,
1106                 2,
1107                 1,
1108                 arrayOf(PointerProperties(5), PointerProperties(2)),
1109                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1110             )
1111 
1112         motionEventAdapter.convertToPointerInputEvent(motionEvent1)
1113         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
1114         motionEventAdapter.convertToPointerInputEvent(motionEvent3)
1115 
1116         assertThat(motionEventAdapter.motionEventToComposePointerIdMap.toMap())
1117             .containsExactlyEntriesIn(mapOf(5 to PointerId(1)))
1118     }
1119 
1120     @Test
1121     fun convertToPointerInputEvent_downDownUpUp_noPointersTracked() {
1122         val motionEvent1 =
1123             MotionEvent(
1124                 1,
1125                 ACTION_DOWN,
1126                 1,
1127                 0,
1128                 arrayOf(PointerProperties(2)),
1129                 arrayOf(PointerCoords(3f, 4f))
1130             )
1131         val motionEvent2 =
1132             MotionEvent(
1133                 4,
1134                 ACTION_POINTER_DOWN,
1135                 2,
1136                 0,
1137                 arrayOf(PointerProperties(5), PointerProperties(2)),
1138                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1139             )
1140         val motionEvent3 =
1141             MotionEvent(
1142                 10,
1143                 ACTION_POINTER_UP,
1144                 2,
1145                 0,
1146                 arrayOf(PointerProperties(5), PointerProperties(2)),
1147                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1148             )
1149         val motionEvent4 =
1150             MotionEvent(
1151                 20,
1152                 ACTION_UP,
1153                 1,
1154                 0,
1155                 arrayOf(PointerProperties(2)),
1156                 arrayOf(PointerCoords(3f, 4f))
1157             )
1158 
1159         motionEventAdapter.convertToPointerInputEvent(motionEvent1)
1160         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
1161         motionEventAdapter.convertToPointerInputEvent(motionEvent3)
1162         motionEventAdapter.convertToPointerInputEvent(motionEvent4)
1163 
1164         assertThat(motionEventAdapter.motionEventToComposePointerIdMap.toMap()).isEmpty()
1165     }
1166 
1167     @Test
1168     fun convertToPointerInputEvent_downCancel_noPointersTracked() {
1169         val motionEvent1 =
1170             MotionEvent(
1171                 1,
1172                 ACTION_DOWN,
1173                 1,
1174                 0,
1175                 arrayOf(PointerProperties(2)),
1176                 arrayOf(PointerCoords(3f, 4f))
1177             )
1178         val motionEvent2 =
1179             MotionEvent(
1180                 4,
1181                 ACTION_POINTER_DOWN,
1182                 2,
1183                 0,
1184                 arrayOf(PointerProperties(5), PointerProperties(2)),
1185                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1186             )
1187         val motionEvent3 =
1188             MotionEvent(
1189                 10,
1190                 ACTION_CANCEL,
1191                 2,
1192                 0,
1193                 arrayOf(PointerProperties(5), PointerProperties(2)),
1194                 arrayOf(PointerCoords(7f, 8f), PointerCoords(3f, 4f))
1195             )
1196         motionEventAdapter.convertToPointerInputEvent(motionEvent1)
1197         motionEventAdapter.convertToPointerInputEvent(motionEvent2)
1198         motionEventAdapter.convertToPointerInputEvent(motionEvent3)
1199 
1200         assertThat(motionEventAdapter.motionEventToComposePointerIdMap.toMap()).isEmpty()
1201     }
1202 
1203     @Test
1204     fun convertToPointerInputEvent_doesNotSynchronouslyMutateMotionEvent() {
1205         val motionEvent =
1206             MotionEvent(
1207                 1,
1208                 ACTION_DOWN,
1209                 1,
1210                 0,
1211                 arrayOf(PointerProperties(2)),
1212                 arrayOf(PointerCoords(3f, 4f))
1213             )
1214         motionEvent.offsetLocation(10f, 100f)
1215 
1216         motionEventAdapter.convertToPointerInputEvent(motionEvent)
1217 
1218         assertThat(motionEvent.x).isEqualTo(13f)
1219         assertThat(motionEvent.y).isEqualTo(104f)
1220     }
1221 
1222     @Test
1223     fun convertToPointerInputEvent_1PointerActionDown_includesMotionEvent() {
1224         val motionEvent =
1225             MotionEvent(
1226                 2894,
1227                 ACTION_DOWN,
1228                 1,
1229                 0,
1230                 arrayOf(PointerProperties(8290)),
1231                 arrayOf(PointerCoords(2967f, 5928f))
1232             )
1233 
1234         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
1235         assertThat(pointerInputEvent).isNotNull()
1236 
1237         assertThat(pointerInputEvent!!.motionEvent).isSameInstanceAs(motionEvent)
1238     }
1239 
1240     @Test
1241     fun convertToPointerInputEvent_1pointerActionMove_includesMotionEvent() {
1242         motionEventAdapter.convertToPointerInputEvent(
1243             MotionEvent(
1244                 1,
1245                 ACTION_DOWN,
1246                 1,
1247                 0,
1248                 arrayOf(PointerProperties(2)),
1249                 arrayOf(PointerCoords(3f, 4f))
1250             )
1251         )
1252         val motionEvent =
1253             MotionEvent(
1254                 5,
1255                 ACTION_MOVE,
1256                 1,
1257                 0,
1258                 arrayOf(PointerProperties(2)),
1259                 arrayOf(PointerCoords(6f, 7f))
1260             )
1261 
1262         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
1263         assertThat(pointerInputEvent).isNotNull()
1264 
1265         assertThat(pointerInputEvent!!.motionEvent).isSameInstanceAs(motionEvent)
1266     }
1267 
1268     @Test
1269     fun convertToPointerInputEvent_1pointerActionUp_includesMotionEvent() {
1270         motionEventAdapter.convertToPointerInputEvent(
1271             MotionEvent(
1272                 10,
1273                 ACTION_DOWN,
1274                 1,
1275                 0,
1276                 arrayOf(PointerProperties(46)),
1277                 arrayOf(PointerCoords(3f, 4f))
1278             )
1279         )
1280         val motionEvent =
1281             MotionEvent(
1282                 34,
1283                 ACTION_UP,
1284                 1,
1285                 0,
1286                 arrayOf(PointerProperties(46)),
1287                 arrayOf(PointerCoords(3f, 4f))
1288             )
1289 
1290         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
1291         assertThat(pointerInputEvent).isNotNull()
1292 
1293         assertThat(pointerInputEvent!!.motionEvent).isSameInstanceAs(motionEvent)
1294     }
1295 
1296     @Test
1297     fun convertToPointerInputEvent_differentCoordinateSpace_useOriginalPointCoordinate() {
1298         motionEventAdapter.convertToPointerInputEvent(
1299             MotionEvent(
1300                 10,
1301                 ACTION_DOWN,
1302                 1,
1303                 0,
1304                 arrayOf(PointerProperties(46)),
1305                 arrayOf(PointerCoords(3f, 4f))
1306             )
1307         )
1308         val motionEvent =
1309             MotionEvent(
1310                 34,
1311                 ACTION_MOVE,
1312                 1,
1313                 0,
1314                 arrayOf(PointerProperties(46)),
1315                 arrayOf(PointerCoords(30f, 40f))
1316             )
1317 
1318         val positionCalculator =
1319             object : PositionCalculator by positionCalculator {
1320                 override fun screenToLocal(positionOnScreen: Offset): Offset {
1321                     return positionOnScreen / 2f
1322                 }
1323             }
1324 
1325         val pointerInputEvent =
1326             motionEventAdapter.convertToPointerInputEvent(motionEvent, positionCalculator)
1327         assertPointerInputEventData(
1328             pointerInputEvent!!.pointers[0],
1329             PointerId(0),
1330             true,
1331             30f,
1332             40f,
1333             originalX = 30f,
1334             originalY = 40f
1335         )
1336     }
1337 
1338     @Test
1339     fun convertScrollEvent_horizontalPositive() {
1340         val motionEvent =
1341             MotionEvent(
1342                 eventTime = 1,
1343                 action = ACTION_SCROLL,
1344                 numPointers = 1,
1345                 actionIndex = 0,
1346                 pointerProperties = arrayOf(PointerProperties(2)),
1347                 pointerCoords =
1348                     arrayOf(PointerCoords(3f, 4f).apply { setAxisValue(AXIS_HSCROLL, 5f) })
1349             )
1350 
1351         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
1352         assertThat(pointerInputEvent).isNotNull()
1353         assertThat(pointerInputEvent!!.pointers[0].scrollDelta).isEqualTo(Offset(5f, 0f))
1354         assertThat(pointerInputEvent.motionEvent).isSameInstanceAs(motionEvent)
1355     }
1356 
1357     @Test
1358     fun convertScrollEvent_horizontalNegative() {
1359         val motionEvent =
1360             MotionEvent(
1361                 eventTime = 1,
1362                 action = ACTION_SCROLL,
1363                 numPointers = 1,
1364                 actionIndex = 0,
1365                 pointerProperties = arrayOf(PointerProperties(2)),
1366                 pointerCoords =
1367                     arrayOf(PointerCoords(3f, 4f).apply { setAxisValue(AXIS_HSCROLL, -5f) })
1368             )
1369 
1370         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
1371         assertThat(pointerInputEvent).isNotNull()
1372         assertThat(pointerInputEvent!!.pointers[0].scrollDelta).isEqualTo(Offset(-5f, 0f))
1373         assertThat(pointerInputEvent.motionEvent).isSameInstanceAs(motionEvent)
1374     }
1375 
1376     @Test
1377     fun convertScrollEvent_verticalPositive() {
1378         val motionEvent =
1379             MotionEvent(
1380                 eventTime = 1,
1381                 action = ACTION_SCROLL,
1382                 numPointers = 1,
1383                 actionIndex = 0,
1384                 pointerProperties = arrayOf(PointerProperties(2)),
1385                 pointerCoords =
1386                     arrayOf(PointerCoords(3f, 4f).apply { setAxisValue(AXIS_VSCROLL, 5f) })
1387             )
1388 
1389         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
1390         assertThat(pointerInputEvent).isNotNull()
1391         // Note: y is inverted, per https://r.android.com/2071209
1392         assertThat(pointerInputEvent!!.pointers[0].scrollDelta).isEqualTo(Offset(0f, -5f))
1393         assertThat(pointerInputEvent.motionEvent).isSameInstanceAs(motionEvent)
1394     }
1395 
1396     @Test
1397     fun convertScrollEvent_verticalNegative() {
1398         val motionEvent =
1399             MotionEvent(
1400                 eventTime = 1,
1401                 action = ACTION_SCROLL,
1402                 numPointers = 1,
1403                 actionIndex = 0,
1404                 pointerProperties = arrayOf(PointerProperties(2)),
1405                 pointerCoords =
1406                     arrayOf(PointerCoords(3f, 4f).apply { setAxisValue(AXIS_VSCROLL, -5f) })
1407             )
1408 
1409         val pointerInputEvent = motionEventAdapter.convertToPointerInputEvent(motionEvent)
1410         assertThat(pointerInputEvent).isNotNull()
1411         // Note: y is inverted, per https://r.android.com/2071209
1412         assertThat(pointerInputEvent!!.pointers[0].scrollDelta).isEqualTo(Offset(0f, 5f))
1413         assertThat(pointerInputEvent.motionEvent).isSameInstanceAs(motionEvent)
1414     }
1415 
1416     private fun MotionEventAdapter.convertToPointerInputEvent(motionEvent: MotionEvent) =
1417         convertToPointerInputEvent(motionEvent, positionCalculator)
1418 
1419     private fun SparseLongArray.toMap(): Map<Int, PointerId> {
1420         val map = mutableMapOf<Int, PointerId>()
1421         for (i in 0 until size()) {
1422             val key = keyAt(i)
1423             val value = valueAt(i)
1424             map[key] = PointerId(value)
1425         }
1426         return map
1427     }
1428 }
1429 
1430 // Private helper functions
1431 
MotionEventnull1432 private fun MotionEvent(
1433     eventTime: Int,
1434     action: Int,
1435     numPointers: Int,
1436     actionIndex: Int,
1437     pointerProperties: Array<MotionEvent.PointerProperties>,
1438     pointerCoords: Array<MotionEvent.PointerCoords>,
1439     downTime: Long = 0
1440 ) =
1441     MotionEvent.obtain(
1442         downTime,
1443         eventTime.toLong(),
1444         action + (actionIndex shl MotionEvent.ACTION_POINTER_INDEX_SHIFT),
1445         numPointers,
1446         pointerProperties,
1447         pointerCoords,
1448         0,
1449         0,
1450         0f,
1451         0f,
1452         0,
1453         0,
1454         InputDevice.SOURCE_TOUCHSCREEN,
1455         0
1456     )
1457 
1458 private fun assertPointerInputEventData(
1459     actual: PointerInputEventData,
1460     id: PointerId,
1461     isDown: Boolean,
1462     x: Float,
1463     y: Float,
1464     type: PointerType = PointerType.Touch,
1465     originalX: Float = x,
1466     originalY: Float = y,
1467 ) {
1468     assertThat(actual.id).isEqualTo(id)
1469     assertThat(actual.down).isEqualTo(isDown)
1470     assertThat(actual.positionOnScreen.x).isEqualTo(x)
1471     assertThat(actual.positionOnScreen.y).isEqualTo(y)
1472     assertThat(actual.originalEventPosition.x).isEqualTo(originalX)
1473     assertThat(actual.originalEventPosition.y).isEqualTo(originalY)
1474     assertThat(actual.type).isEqualTo(type)
1475 }
1476