• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package android.view;
2 
3 import static android.os.Build.VERSION_CODES.N;
4 import static androidx.test.ext.truth.view.MotionEventSubject.assertThat;
5 import static androidx.test.ext.truth.view.PointerCoordsSubject.assertThat;
6 import static androidx.test.ext.truth.view.PointerPropertiesSubject.assertThat;
7 import static com.google.common.truth.Truth.assertThat;
8 import static org.junit.Assert.fail;
9 
10 import android.graphics.Matrix;
11 import android.os.Build;
12 import android.os.Build.VERSION_CODES;
13 import android.os.Parcel;
14 import android.os.Parcelable;
15 import android.os.SystemClock;
16 import android.view.MotionEvent.PointerCoords;
17 import android.view.MotionEvent.PointerProperties;
18 import androidx.test.core.view.PointerCoordsBuilder;
19 import androidx.test.core.view.PointerPropertiesBuilder;
20 import androidx.test.ext.junit.runners.AndroidJUnit4;
21 import androidx.test.filters.SdkSuppress;
22 import com.google.common.truth.FailureMetadata;
23 import com.google.common.truth.Subject;
24 import com.google.common.truth.Truth;
25 import org.junit.After;
26 import org.junit.Before;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.robolectric.annotation.internal.DoNotInstrument;
30 
31 /**
32  * Test {@link android.view.MotionEvent}.
33  *
34  * <p>Baselined from Android cts/tests/tests/view/src/android/view/cts/MotionEventTest.java
35  */
36 @DoNotInstrument
37 @RunWith(AndroidJUnit4.class)
38 public class MotionEventTest {
39   private MotionEvent motionEvent1;
40   private MotionEvent motionEvent2;
41   private MotionEvent motionEventDynamic;
42   private long downTime;
43   private long eventTime;
44   private static final float X_3F = 3.0f;
45   private static final float Y_4F = 4.0f;
46   private static final int META_STATE = KeyEvent.META_SHIFT_ON;
47   private static final float PRESSURE_1F = 1.0f;
48   private static final float SIZE_1F = 1.0f;
49   private static final float X_PRECISION_3F = 3.0f;
50   private static final float Y_PRECISION_4F = 4.0f;
51   private static final int DEVICE_ID_1 = 1;
52   private static final int EDGE_FLAGS = MotionEvent.EDGE_TOP;
53   private static final float TOLERANCE = 0.01f;
54 
55   @Before
setup()56   public void setup() {
57     downTime = SystemClock.uptimeMillis();
58     eventTime = SystemClock.uptimeMillis();
59     motionEvent1 =
60         MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, X_3F, Y_4F, META_STATE);
61     motionEvent2 =
62         MotionEvent.obtain(
63             downTime,
64             eventTime,
65             MotionEvent.ACTION_MOVE,
66             X_3F,
67             Y_4F,
68             PRESSURE_1F,
69             SIZE_1F,
70             META_STATE,
71             X_PRECISION_3F,
72             Y_PRECISION_4F,
73             DEVICE_ID_1,
74             EDGE_FLAGS);
75   }
76 
77   @After
teardown()78   public void teardown() {
79     if (null != motionEvent1) {
80       motionEvent1.recycle();
81     }
82     if (null != motionEvent2) {
83       motionEvent2.recycle();
84     }
85     if (null != motionEventDynamic) {
86       motionEventDynamic.recycle();
87     }
88   }
89 
90   @Test
obtainBasic()91   public void obtainBasic() {
92     motionEvent1 =
93         MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, X_3F, Y_4F, META_STATE);
94     assertThat(motionEvent1).isNotNull();
95     assertThat(motionEvent1).hasDownTime(downTime);
96     assertThat(motionEvent1).hasEventTime(eventTime);
97     assertThat(motionEvent1).hasAction(MotionEvent.ACTION_DOWN);
98     assertThat(motionEvent1).x().isWithin(TOLERANCE).of(X_3F);
99     assertThat(motionEvent1).y().isWithin(TOLERANCE).of(Y_4F);
100     assertThat(motionEvent1).rawX().isWithin(TOLERANCE).of(X_3F);
101     assertThat(motionEvent1).rawY().isWithin(TOLERANCE).of(Y_4F);
102     assertThat(motionEvent1).hasMetaState(META_STATE);
103     assertThat(motionEvent1).hasDeviceId(0);
104     assertThat(motionEvent1).hasEdgeFlags(0);
105     assertThat(motionEvent1).pressure().isWithin(TOLERANCE).of(PRESSURE_1F);
106     assertThat(motionEvent1).size().isWithin(TOLERANCE).of(SIZE_1F);
107     assertThat(motionEvent1).xPrecision().isWithin(TOLERANCE).of(1.0f);
108     assertThat(motionEvent1).yPrecision().isWithin(TOLERANCE).of(1.0f);
109   }
110 
111   @Test
testObtainFromMotionEvent()112   public void testObtainFromMotionEvent() {
113     motionEventDynamic = MotionEvent.obtain(motionEvent2);
114     assertThat(motionEventDynamic).isNotNull();
115     MotionEventEqualitySubject.assertThat(motionEventDynamic)
116         .isEqualToWithinTolerance(motionEvent2, TOLERANCE);
117   }
118 
119   @Test
testObtainAllFields()120   public void testObtainAllFields() {
121     motionEventDynamic =
122         MotionEvent.obtain(
123             downTime,
124             eventTime,
125             MotionEvent.ACTION_DOWN,
126             X_3F,
127             Y_4F,
128             PRESSURE_1F,
129             SIZE_1F,
130             META_STATE,
131             X_PRECISION_3F,
132             Y_PRECISION_4F,
133             DEVICE_ID_1,
134             EDGE_FLAGS);
135     assertThat(motionEventDynamic).isNotNull();
136     assertThat(motionEventDynamic).hasButtonState(0);
137     assertThat(motionEventDynamic).hasDownTime(downTime);
138     assertThat(motionEventDynamic).hasEventTime(eventTime);
139     assertThat(motionEventDynamic).hasAction(MotionEvent.ACTION_DOWN);
140     assertThat(motionEventDynamic).x().isWithin(TOLERANCE).of(X_3F);
141     assertThat(motionEventDynamic).y().isWithin(TOLERANCE).of(Y_4F);
142     assertThat(motionEventDynamic).rawX().isWithin(TOLERANCE).of(X_3F);
143     assertThat(motionEventDynamic).rawY().isWithin(TOLERANCE).of(Y_4F);
144     assertThat(motionEventDynamic).hasMetaState(META_STATE);
145     assertThat(motionEventDynamic).hasDeviceId(DEVICE_ID_1);
146     assertThat(motionEventDynamic).hasEdgeFlags(EDGE_FLAGS);
147     assertThat(motionEventDynamic).pressure().isWithin(TOLERANCE).of(PRESSURE_1F);
148     assertThat(motionEventDynamic).size().isWithin(TOLERANCE).of(SIZE_1F);
149     assertThat(motionEventDynamic).xPrecision().isWithin(TOLERANCE).of(X_PRECISION_3F);
150     assertThat(motionEventDynamic).yPrecision().isWithin(TOLERANCE).of(Y_PRECISION_4F);
151   }
152 
153   @Test
actionButton()154   public void actionButton() {
155     MotionEvent event =
156         MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, X_3F, Y_4F, META_STATE);
157     if (Build.VERSION.SDK_INT < VERSION_CODES.M) {
158       try {
159         assertThat(event).hasActionButton(0);
160         fail("IllegalStateException not thrown");
161       } catch (IllegalStateException e) {
162         // expected
163       }
164     } else {
165       assertThat(event).hasActionButton(0);
166     }
167   }
168 
169   @Test
testObtainFromRecycledEvent()170   public void testObtainFromRecycledEvent() {
171     PointerCoords coords0 =
172         PointerCoordsBuilder.newBuilder()
173             .setCoords(X_3F, Y_4F)
174             .setPressure(PRESSURE_1F)
175             .setSize(SIZE_1F)
176             .setTool(1.2f, 1.4f)
177             .build();
178     PointerProperties properties0 =
179         PointerPropertiesBuilder.newBuilder()
180             .setId(0)
181             .setToolType(MotionEvent.TOOL_TYPE_FINGER)
182             .build();
183     motionEventDynamic =
184         MotionEvent.obtain(
185             downTime,
186             eventTime,
187             MotionEvent.ACTION_MOVE,
188             1,
189             new PointerProperties[] {properties0},
190             new PointerCoords[] {coords0},
191             META_STATE,
192             0,
193             X_PRECISION_3F,
194             Y_PRECISION_4F,
195             DEVICE_ID_1,
196             EDGE_FLAGS,
197             InputDevice.SOURCE_TOUCHSCREEN,
198             0);
199     MotionEvent motionEventDynamicCopy = MotionEvent.obtain(motionEventDynamic);
200     assertThat(motionEventDynamic.getToolType(0)).isEqualTo(MotionEvent.TOOL_TYPE_FINGER);
201     assertThat(motionEventDynamicCopy.getToolType(0)).isEqualTo(MotionEvent.TOOL_TYPE_FINGER);
202     motionEventDynamic.recycle();
203 
204     PointerCoords coords1 =
205         PointerCoordsBuilder.newBuilder()
206             .setCoords(X_3F + 1.0f, Y_4F - 2.0f)
207             .setPressure(PRESSURE_1F + 0.2f)
208             .setSize(SIZE_1F + 0.5f)
209             .setTouch(2.2f, 0.6f)
210             .build();
211     PointerProperties properties1 =
212         PointerPropertiesBuilder.newBuilder()
213             .setId(0)
214             .setToolType(MotionEvent.TOOL_TYPE_MOUSE)
215             .build();
216     motionEventDynamic =
217         MotionEvent.obtain(
218             downTime,
219             eventTime,
220             MotionEvent.ACTION_MOVE,
221             1,
222             new PointerProperties[] {properties1},
223             new PointerCoords[] {coords1},
224             META_STATE,
225             0,
226             X_PRECISION_3F,
227             Y_PRECISION_4F,
228             DEVICE_ID_1,
229             EDGE_FLAGS,
230             InputDevice.SOURCE_TOUCHSCREEN,
231             0);
232     assertThat(motionEventDynamicCopy.getToolType(0)).isEqualTo(MotionEvent.TOOL_TYPE_FINGER);
233     assertThat(motionEventDynamic.getToolType(0)).isEqualTo(MotionEvent.TOOL_TYPE_MOUSE);
234   }
235 
236   @Test
testObtainFromPropertyArrays()237   public void testObtainFromPropertyArrays() {
238     PointerCoords coords0 =
239         PointerCoordsBuilder.newBuilder()
240             .setCoords(X_3F, Y_4F)
241             .setPressure(PRESSURE_1F)
242             .setSize(SIZE_1F)
243             .setTool(1.2f, 1.4f)
244             .build();
245     PointerCoords coords1 =
246         PointerCoordsBuilder.newBuilder()
247             .setCoords(X_3F + 1.0f, Y_4F - 2.0f)
248             .setPressure(PRESSURE_1F + 0.2f)
249             .setSize(SIZE_1F + 0.5f)
250             .setTouch(2.2f, 0.6f)
251             .build();
252 
253     PointerProperties properties0 =
254         PointerPropertiesBuilder.newBuilder()
255             .setId(0)
256             .setToolType(MotionEvent.TOOL_TYPE_FINGER)
257             .build();
258     PointerProperties properties1 =
259         PointerPropertiesBuilder.newBuilder()
260             .setId(1)
261             .setToolType(MotionEvent.TOOL_TYPE_FINGER)
262             .build();
263 
264     motionEventDynamic =
265         MotionEvent.obtain(
266             downTime,
267             eventTime,
268             MotionEvent.ACTION_MOVE,
269             2,
270             new PointerProperties[] {properties0, properties1},
271             new PointerCoords[] {coords0, coords1},
272             META_STATE,
273             0,
274             X_PRECISION_3F,
275             Y_PRECISION_4F,
276             DEVICE_ID_1,
277             EDGE_FLAGS,
278             InputDevice.SOURCE_TOUCHSCREEN,
279             0);
280 
281     // We expect to have data for two pointers
282     assertThat(motionEventDynamic).hasPointerCount(2);
283     assertThat(motionEventDynamic).hasFlags(0);
284     assertThat(motionEventDynamic).pointerId(0).isEqualTo(0);
285     assertThat(motionEventDynamic).pointerId(1).isEqualTo(1);
286     MotionEventEqualitySubject.assertThat(motionEventDynamic)
287         .pointerCoords(0)
288         .isEqualToWithinTolerance(coords0, TOLERANCE);
289     MotionEventEqualitySubject.assertThat(motionEventDynamic)
290         .pointerCoords(1)
291         .isEqualToWithinTolerance(coords1, TOLERANCE);
292     assertThat(motionEventDynamic).pointerProperties(0).isEqualTo(properties0);
293     assertThat(motionEventDynamic).pointerProperties(1).isEqualTo(properties1);
294   }
295 
296   @Test
testObtainNoHistory()297   public void testObtainNoHistory() {
298     // Add two batch to one of our events
299     motionEvent2.addBatch(eventTime + 10, X_3F + 5.0f, Y_4F + 5.0f, 0.5f, 0.5f, 0);
300     motionEvent2.addBatch(eventTime + 20, X_3F + 10.0f, Y_4F + 15.0f, 2.0f, 3.0f, 0);
301     // The newly added batch should be the "new" values of the event
302     assertThat(motionEvent2).x().isWithin(TOLERANCE).of(X_3F + 10.0f);
303     assertThat(motionEvent2).y().isWithin(TOLERANCE).of(Y_4F + 15.0f);
304     assertThat(motionEvent2).pressure().isWithin(TOLERANCE).of(2.0f);
305     assertThat(motionEvent2).size().isWithin(TOLERANCE).of(3.0f);
306     assertThat(motionEvent2).hasEventTime(eventTime + 20);
307 
308     // We should have history with 2 entries
309     assertThat(motionEvent2).hasHistorySize(2);
310 
311     // The previous data should be history at index 1
312     assertThat(motionEvent2).historicalX(1).isWithin(TOLERANCE).of(X_3F + 5.0f);
313     assertThat(motionEvent2).historicalY(1).isWithin(TOLERANCE).of(Y_4F + 5.0f);
314     assertThat(motionEvent2).historicalPressure(1).isWithin(TOLERANCE).of(0.5f);
315     assertThat(motionEvent2).historicalSize(1).isWithin(TOLERANCE).of(0.5f);
316     assertThat(motionEvent2).historicalEventTime(1).isEqualTo(eventTime + 10);
317 
318     // And the original data should be history at index 0
319     assertThat(motionEvent2).historicalX(0).isWithin(TOLERANCE).of(X_3F);
320     assertThat(motionEvent2).historicalY(0).isWithin(TOLERANCE).of(Y_4F);
321     assertThat(motionEvent2).historicalPressure(0).isWithin(TOLERANCE).of(1.0f);
322     assertThat(motionEvent2).historicalSize(0).isWithin(TOLERANCE).of(1.0f);
323     assertThat(motionEvent2).historicalEventTime(0).isEqualTo(eventTime);
324 
325     motionEventDynamic = MotionEvent.obtainNoHistory(motionEvent2);
326     // The newly obtained event should have the matching current content and no history
327     assertThat(motionEventDynamic).x().isWithin(TOLERANCE).of(X_3F + 10.0f);
328     assertThat(motionEventDynamic).y().isWithin(TOLERANCE).of(Y_4F + 15.0f);
329     assertThat(motionEventDynamic).pressure().isWithin(TOLERANCE).of(2.0f);
330     assertThat(motionEventDynamic).size().isWithin(TOLERANCE).of(3.0f);
331     assertThat(motionEventDynamic).hasHistorySize(0);
332   }
333 
334   @Test
testAccessAction()335   public void testAccessAction() {
336     assertThat(motionEvent1).hasAction(MotionEvent.ACTION_MOVE);
337 
338     motionEvent1.setAction(MotionEvent.ACTION_UP);
339     assertThat(motionEvent1).hasAction(MotionEvent.ACTION_UP);
340   }
341 
342   @Test
testDescribeContents()343   public void testDescribeContents() {
344     // make sure this method never throw any exception.
345     motionEvent2.describeContents();
346   }
347 
348   @Test
testAccessEdgeFlags()349   public void testAccessEdgeFlags() {
350     assertThat(motionEvent2).hasEdgeFlags(EDGE_FLAGS);
351 
352     motionEvent2.setEdgeFlags(10);
353     assertThat(motionEvent2).hasEdgeFlags(10);
354   }
355 
356   @Test
testWriteToParcel()357   public void testWriteToParcel() {
358     Parcel parcel = Parcel.obtain();
359     motionEvent2.writeToParcel(parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
360     parcel.setDataPosition(0);
361 
362     MotionEvent motionEvent = MotionEvent.CREATOR.createFromParcel(parcel);
363     assertThat(motionEvent).rawY().isWithin(TOLERANCE).of(motionEvent2.getRawY());
364     assertThat(motionEvent).rawX().isWithin(TOLERANCE).of(motionEvent2.getRawX());
365     assertThat(motionEvent).y().isWithin(TOLERANCE).of(motionEvent2.getY());
366     assertThat(motionEvent).x().isWithin(TOLERANCE).of(motionEvent2.getX());
367     assertThat(motionEvent).hasAction(motionEvent2.getAction());
368     assertThat(motionEvent).hasDownTime(motionEvent2.getDownTime());
369     assertThat(motionEvent).hasEventTime(motionEvent2.getEventTime());
370     assertThat(motionEvent).hasEdgeFlags(motionEvent2.getEdgeFlags());
371     assertThat(motionEvent).hasDeviceId(motionEvent2.getDeviceId());
372   }
373 
374   @Test
testReadFromParcelWithInvalidPointerCountSize()375   public void testReadFromParcelWithInvalidPointerCountSize() {
376     Parcel parcel = Parcel.obtain();
377     motionEvent2.writeToParcel(parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
378 
379     // Move to pointer id count.
380     parcel.setDataPosition(4);
381     parcel.writeInt(17);
382 
383     parcel.setDataPosition(0);
384     try {
385       MotionEvent.CREATOR.createFromParcel(parcel);
386       fail("deserialized invalid parcel");
387     } catch (RuntimeException e) {
388       // Expected.
389     }
390   }
391 
392   @Test
393   @SdkSuppress(minSdkVersion = N)
testReadFromParcelWithInvalidSampleSize()394   public void testReadFromParcelWithInvalidSampleSize() {
395     Parcel parcel = Parcel.obtain();
396     motionEvent2.writeToParcel(parcel, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
397 
398     // Move to sample count.
399     parcel.setDataPosition(2 * 4);
400     parcel.writeInt(0x000f0000);
401 
402     parcel.setDataPosition(0);
403     try {
404       MotionEvent.CREATOR.createFromParcel(parcel);
405       fail("deserialized invalid parcel");
406     } catch (RuntimeException e) {
407       // Expected.
408     }
409   }
410 
411   @Test
testToString()412   public void testToString() {
413     // make sure this method never throw exception.
414     motionEvent2.toString();
415   }
416 
417   @Test
testOffsetLocationForPointerSource()418   public void testOffsetLocationForPointerSource() {
419     assertThat(motionEvent2).x().isWithin(TOLERANCE).of(X_3F);
420     assertThat(motionEvent2).y().isWithin(TOLERANCE).of(Y_4F);
421     motionEvent2.setSource(InputDevice.SOURCE_TOUCHSCREEN);
422 
423     float offsetX = 1.0f;
424     float offsetY = 1.0f;
425     motionEvent2.offsetLocation(offsetX, offsetY);
426 
427     assertThat(motionEvent2).x().isWithin(TOLERANCE).of(X_3F + offsetX);
428     assertThat(motionEvent2).y().isWithin(TOLERANCE).of(Y_4F + offsetY);
429   }
430 
431   @Test
testSetLocationForPointerSource()432   public void testSetLocationForPointerSource() {
433     assertThat(motionEvent2).x().isWithin(TOLERANCE).of(X_3F);
434     assertThat(motionEvent2).y().isWithin(TOLERANCE).of(Y_4F);
435     motionEvent2.setSource(InputDevice.SOURCE_TOUCHSCREEN);
436 
437     motionEvent2.setLocation(2.0f, 2.0f);
438 
439     assertThat(motionEvent2).x().isWithin(TOLERANCE).of(2.0f);
440     assertThat(motionEvent2).y().isWithin(TOLERANCE).of(2.0f);
441   }
442 
443   @Test
testGetHistoricalData()444   public void testGetHistoricalData() {
445     assertThat(motionEvent2).hasHistorySize(0);
446 
447     motionEvent2.addBatch(eventTime + 10, X_3F + 5.0f, Y_4F + 5.0f, 0.5f, 0.5f, 0);
448     // The newly added batch should be the "new" values of the event
449     assertThat(motionEvent2).x().isWithin(TOLERANCE).of(X_3F + 5.0f);
450     assertThat(motionEvent2).y().isWithin(TOLERANCE).of(Y_4F + 5.0f);
451     assertThat(motionEvent2).pressure().isWithin(TOLERANCE).of(0.5f);
452     assertThat(motionEvent2).size().isWithin(TOLERANCE).of(0.5f);
453     assertThat(motionEvent2).hasEventTime(eventTime + 10);
454 
455     // We should have history with 1 entry
456     assertThat(motionEvent2).hasHistorySize(1);
457     // And the previous / original data should be history at index 0
458     assertThat(motionEvent2).historicalEventTime(0).isEqualTo(eventTime);
459     assertThat(motionEvent2).historicalX(0).isWithin(TOLERANCE).of(X_3F);
460     assertThat(motionEvent2).historicalY(0).isWithin(TOLERANCE).of(Y_4F);
461     assertThat(motionEvent2).historicalPressure(0).isWithin(TOLERANCE).of(1.0f);
462     assertThat(motionEvent2).historicalSize(0).isWithin(TOLERANCE).of(1.0f);
463   }
464 
465   @Test
testGetCurrentDataWithTwoPointers()466   public void testGetCurrentDataWithTwoPointers() {
467     PointerCoords coords0 =
468         PointerCoordsBuilder.newBuilder()
469             .setCoords(10.0f, 20.0f)
470             .setPressure(1.2f)
471             .setSize(2.0f)
472             .setTool(1.2f, 1.4f)
473             .build();
474     PointerCoords coords1 =
475         PointerCoordsBuilder.newBuilder()
476             .setCoords(30.0f, 40.0f)
477             .setPressure(1.4f)
478             .setSize(3.0f)
479             .setTouch(2.2f, 0.6f)
480             .build();
481 
482     PointerProperties properties0 =
483         PointerPropertiesBuilder.newBuilder()
484             .setId(0)
485             .setToolType(MotionEvent.TOOL_TYPE_FINGER)
486             .build();
487     PointerProperties properties1 =
488         PointerPropertiesBuilder.newBuilder()
489             .setId(1)
490             .setToolType(MotionEvent.TOOL_TYPE_FINGER)
491             .build();
492 
493     motionEventDynamic =
494         MotionEvent.obtain(
495             downTime,
496             eventTime,
497             MotionEvent.ACTION_MOVE,
498             2,
499             new PointerProperties[] {properties0, properties1},
500             new PointerCoords[] {coords0, coords1},
501             0,
502             0,
503             1.0f,
504             1.0f,
505             0,
506             0,
507             InputDevice.SOURCE_TOUCHSCREEN,
508             0);
509 
510     // We expect to have data for two pointers
511     assertThat(motionEventDynamic).pointerId(0).isEqualTo(0);
512     assertThat(motionEventDynamic).pointerId(1).isEqualTo(1);
513 
514     assertThat(motionEventDynamic).hasPointerCount(2);
515     assertThat(motionEventDynamic).hasFlags(0);
516     MotionEventEqualitySubject.assertThat(motionEventDynamic)
517         .pointerCoords(0)
518         .isEqualToWithinTolerance(coords0, TOLERANCE);
519     MotionEventEqualitySubject.assertThat(motionEventDynamic)
520         .pointerCoords(1)
521         .isEqualToWithinTolerance(coords1, TOLERANCE);
522     assertThat(motionEventDynamic).pointerProperties(0).isEqualTo(properties0);
523     assertThat(motionEventDynamic).pointerProperties(1).isEqualTo(properties1);
524   }
525 
526   @Test
testGetHistoricalDataWithTwoPointers()527   public void testGetHistoricalDataWithTwoPointers() {
528     // PHASE 1 - construct the initial data for the event
529     PointerCoords coordsInitial0 =
530         PointerCoordsBuilder.newBuilder()
531             .setCoords(10.0f, 20.0f)
532             .setPressure(1.2f)
533             .setSize(2.0f)
534             .setTool(1.2f, 1.4f)
535             .setTouch(0.7f, 0.6f)
536             .setOrientation(2.0f)
537             .build();
538     PointerCoords coordsInitial1 =
539         PointerCoordsBuilder.newBuilder()
540             .setCoords(30.0f, 40.0f)
541             .setPressure(1.4f)
542             .setSize(3.0f)
543             .setTool(1.3f, 1.7f)
544             .setTouch(2.7f, 3.6f)
545             .setOrientation(1.0f)
546             .build();
547 
548     PointerProperties properties0 =
549         PointerPropertiesBuilder.newBuilder()
550             .setId(0)
551             .setToolType(MotionEvent.TOOL_TYPE_FINGER)
552             .build();
553     PointerProperties properties1 =
554         PointerPropertiesBuilder.newBuilder()
555             .setId(1)
556             .setToolType(MotionEvent.TOOL_TYPE_FINGER)
557             .build();
558 
559     motionEventDynamic =
560         MotionEvent.obtain(
561             downTime,
562             eventTime,
563             MotionEvent.ACTION_MOVE,
564             2,
565             new PointerProperties[] {properties0, properties1},
566             new PointerCoords[] {coordsInitial0, coordsInitial1},
567             0,
568             0,
569             1.0f,
570             1.0f,
571             0,
572             0,
573             InputDevice.SOURCE_TOUCHSCREEN,
574             0);
575 
576     // We expect to have data for two pointers
577     assertThat(motionEventDynamic).hasPointerCount(2);
578     assertThat(motionEventDynamic).pointerId(0).isEqualTo(0);
579     assertThat(motionEventDynamic).pointerId(1).isEqualTo(1);
580     assertThat(motionEventDynamic).hasFlags(0);
581     MotionEventEqualitySubject.assertThat(motionEventDynamic)
582         .pointerCoords(0)
583         .isEqualToWithinTolerance(coordsInitial0, TOLERANCE);
584     MotionEventEqualitySubject.assertThat(motionEventDynamic)
585         .pointerCoords(1)
586         .isEqualToWithinTolerance(coordsInitial1, TOLERANCE);
587     assertThat(motionEventDynamic).pointerProperties(0).isEqualTo(properties0);
588     assertThat(motionEventDynamic).pointerProperties(1).isEqualTo(properties1);
589 
590     // PHASE 2 - add a new batch of data to our event
591     PointerCoords coordsNext0 =
592         PointerCoordsBuilder.newBuilder()
593             .setCoords(15.0f, 25.0f)
594             .setPressure(1.6f)
595             .setSize(2.2f)
596             .setTool(1.2f, 1.4f)
597             .setTouch(1.0f, 0.9f)
598             .setOrientation(2.2f)
599             .build();
600     PointerCoords coordsNext1 =
601         PointerCoordsBuilder.newBuilder()
602             .setCoords(35.0f, 45.0f)
603             .setPressure(1.8f)
604             .setSize(3.2f)
605             .setTool(1.2f, 1.4f)
606             .setTouch(0.7f, 0.6f)
607             .setOrientation(2.9f)
608             .build();
609 
610     motionEventDynamic.addBatch(eventTime + 10, new PointerCoords[] {coordsNext0, coordsNext1}, 0);
611     // We still expect to have data for two pointers
612     assertThat(motionEventDynamic).hasPointerCount(2);
613     assertThat(motionEventDynamic).pointerId(0).isEqualTo(0);
614     assertThat(motionEventDynamic).pointerId(1).isEqualTo(1);
615     assertThat(motionEventDynamic).hasFlags(0);
616 
617     // The newly added batch should be the "new" values of the event
618     MotionEventEqualitySubject.assertThat(motionEventDynamic)
619         .pointerCoords(0)
620         .isEqualToWithinTolerance(coordsNext0, TOLERANCE);
621     MotionEventEqualitySubject.assertThat(motionEventDynamic)
622         .pointerCoords(1)
623         .isEqualToWithinTolerance(coordsNext1, TOLERANCE);
624     assertThat(motionEventDynamic).pointerProperties(0).isEqualTo(properties0);
625     assertThat(motionEventDynamic).pointerProperties(1).isEqualTo(properties1);
626     assertThat(motionEventDynamic).hasEventTime(eventTime + 10);
627 
628     // We should have history with 1 entry
629     assertThat(motionEventDynamic).hasHistorySize(1);
630     // And the previous / original data should be history at position 0
631     MotionEventEqualitySubject.assertThat(motionEventDynamic)
632         .historicalPointerCoords(0, 0)
633         .isEqualToWithinTolerance(coordsInitial0, TOLERANCE);
634     MotionEventEqualitySubject.assertThat(motionEventDynamic)
635         .historicalPointerCoords(1, 0)
636         .isEqualToWithinTolerance(coordsInitial1, TOLERANCE);
637   }
638 
639   @Test
testGetHistorySize()640   public void testGetHistorySize() {
641     long eventTime = SystemClock.uptimeMillis();
642     float x = 10.0f;
643     float y = 20.0f;
644     float pressure = 1.0f;
645     float size = 1.0f;
646 
647     motionEvent2.setAction(MotionEvent.ACTION_DOWN);
648     assertThat(motionEvent2).hasHistorySize(0);
649 
650     motionEvent2.addBatch(eventTime, x, y, pressure, size, 0);
651     assertThat(motionEvent2).hasHistorySize(1);
652   }
653 
654   @Test
testRecycle()655   public void testRecycle() {
656     motionEvent2.recycle();
657 
658     try {
659       motionEvent2.recycle();
660       fail("recycle() should throw an exception when the event has already been recycled.");
661     } catch (RuntimeException ex) {
662       // expected
663     }
664     motionEvent2 = null; // since it was recycled, don't try to recycle again in tear down
665   }
666 
667   @Test
testTransformShouldApplyMatrixToPointsAndPreserveRawPosition()668   public void testTransformShouldApplyMatrixToPointsAndPreserveRawPosition() {
669     // Generate some points on a circle.
670     // Each point 'i' is a point on a circle of radius ROTATION centered at (3,2) at an angle
671     // of ARC * i degrees clockwise relative to the Y axis.
672     // The geometrical representation is irrelevant to the test, it's just easy to generate
673     // and check rotation.  We set the orientation to the same angle.
674     // Coordinate system: down is increasing Y, right is increasing X.
675     final float pi180 = (float) (Math.PI / 180);
676     final float radius = 10;
677     final float arc = 36;
678     final float rotation = arc * 2;
679 
680     final int pointerCount = 11;
681     final int[] pointerIds = new int[pointerCount];
682     final PointerCoords[] pointerCoords = new PointerCoords[pointerCount];
683     for (int i = 0; i < pointerCount; i++) {
684       final PointerCoords c = new PointerCoords();
685       final float angle = (float) (i * arc * pi180);
686       pointerIds[i] = i;
687       pointerCoords[i] = c;
688       c.x = (float) (Math.sin(angle) * radius + 3);
689       c.y = (float) (-Math.cos(angle) * radius + 2);
690       c.orientation = angle;
691     }
692     final MotionEvent event =
693         MotionEvent.obtain(
694             0,
695             0,
696             MotionEvent.ACTION_MOVE,
697             pointerCount,
698             pointerIds,
699             pointerCoords,
700             0,
701             0,
702             0,
703             0,
704             0,
705             InputDevice.SOURCE_TOUCHSCREEN,
706             0);
707     final float originalRawX = 0 + 3;
708     final float originalRawY = -radius + 2;
709 
710     // Check original raw X and Y assumption.
711     assertThat(event).rawX().isWithin(TOLERANCE).of(originalRawX);
712     assertThat(event).rawY().isWithin(TOLERANCE).of(originalRawY);
713 
714     // Now translate the motion event so the circle's origin is at (0,0).
715     event.offsetLocation(-3, -2);
716 
717     // Offsetting the location should preserve the raw X and Y of the first point.
718     assertThat(event).rawX().isWithin(TOLERANCE).of(originalRawX);
719     assertThat(event).rawY().isWithin(TOLERANCE).of(originalRawY);
720 
721     // Apply a rotation about the origin by ROTATION degrees clockwise.
722     Matrix matrix = new Matrix();
723     matrix.setRotate(rotation);
724     event.transform(matrix);
725 
726     // Check the points.
727     for (int i = 0; i < pointerCount; i++) {
728       final PointerCoords c = pointerCoords[i];
729       event.getPointerCoords(i, c);
730 
731       final float angle = (float) ((i * arc + rotation) * pi180);
732       assertThat(event)
733           .pointerCoords(i)
734           .x()
735           .isWithin(TOLERANCE)
736           .of((float) (Math.sin(angle) * radius));
737       assertThat(event)
738           .pointerCoords(i)
739           .y()
740           .isWithin(TOLERANCE)
741           .of(-(float) Math.cos(angle) * radius);
742 
743       assertThat(Math.tan(c.orientation)).isWithin(0.1f).of(Math.tan(angle));
744     }
745 
746     // Applying the transformation should preserve the raw X and Y of the first point.
747     assertThat(event).rawX().isWithin(TOLERANCE).of(originalRawX);
748     assertThat(event).rawY().isWithin(TOLERANCE).of(originalRawY);
749   }
750 
751   @Test
testPointerCoordsCopyConstructor()752   public void testPointerCoordsCopyConstructor() {
753     PointerCoords coords = new PointerCoords();
754     coords.x = 1;
755     coords.y = 2;
756     coords.pressure = 3;
757     coords.size = 4;
758     coords.touchMajor = 5;
759     coords.touchMinor = 6;
760     coords.toolMajor = 7;
761     coords.toolMinor = 8;
762     coords.orientation = 9;
763     coords.setAxisValue(MotionEvent.AXIS_GENERIC_1, 10);
764 
765     PointerCoords copy = new PointerCoords(coords);
766     assertThat(copy).x().isWithin(TOLERANCE).of(1f);
767     assertThat(copy).y().isWithin(TOLERANCE).of(2f);
768     assertThat(copy).pressure().isWithin(TOLERANCE).of(3f);
769     assertThat(copy).size().isWithin(TOLERANCE).of(4f);
770     assertThat(copy).touchMajor().isWithin(TOLERANCE).of(5f);
771     assertThat(copy).touchMinor().isWithin(TOLERANCE).of(6f);
772     assertThat(copy).toolMajor().isWithin(TOLERANCE).of(7f);
773     assertThat(copy).toolMinor().isWithin(TOLERANCE).of(8f);
774     assertThat(copy).orientation().isWithin(TOLERANCE).of(9f);
775     assertThat(copy).axisValue(MotionEvent.AXIS_GENERIC_1).isWithin(TOLERANCE).of(10f);
776   }
777 
778   @Test
testPointerCoordsCopyFrom()779   public void testPointerCoordsCopyFrom() {
780     PointerCoords coords = new PointerCoords();
781     coords.x = 1;
782     coords.y = 2;
783     coords.pressure = 3;
784     coords.size = 4;
785     coords.touchMajor = 5;
786     coords.touchMinor = 6;
787     coords.toolMajor = 7;
788     coords.toolMinor = 8;
789     coords.orientation = 9;
790     coords.setAxisValue(MotionEvent.AXIS_GENERIC_1, 10);
791 
792     PointerCoords copy = new PointerCoords();
793     copy.copyFrom(coords);
794     assertThat(copy).x().isWithin(TOLERANCE).of(1f);
795     assertThat(copy).y().isWithin(TOLERANCE).of(2f);
796     assertThat(copy).pressure().isWithin(TOLERANCE).of(3f);
797     assertThat(copy).size().isWithin(TOLERANCE).of(4f);
798     assertThat(copy).touchMajor().isWithin(TOLERANCE).of(5f);
799     assertThat(copy).touchMinor().isWithin(TOLERANCE).of(6f);
800     assertThat(copy).toolMajor().isWithin(TOLERANCE).of(7f);
801     assertThat(copy).toolMinor().isWithin(TOLERANCE).of(8f);
802     assertThat(copy).orientation().isWithin(TOLERANCE).of(9f);
803     assertThat(copy).axisValue(MotionEvent.AXIS_GENERIC_1).isWithin(TOLERANCE).of(10f);
804   }
805 
806   @Test
testPointerPropertiesDefaultConstructor()807   public void testPointerPropertiesDefaultConstructor() {
808     PointerProperties properties = new PointerProperties();
809 
810     assertThat(properties).hasId(MotionEvent.INVALID_POINTER_ID);
811     assertThat(properties).hasToolType(MotionEvent.TOOL_TYPE_UNKNOWN);
812   }
813 
814   @Test
testPointerPropertiesCopyConstructor()815   public void testPointerPropertiesCopyConstructor() {
816     PointerProperties properties = new PointerProperties();
817     properties.id = 1;
818     properties.toolType = MotionEvent.TOOL_TYPE_MOUSE;
819 
820     PointerProperties copy = new PointerProperties(properties);
821     assertThat(copy).hasId(1);
822     assertThat(copy).hasToolType(MotionEvent.TOOL_TYPE_MOUSE);
823   }
824 
825   @Test
testPointerPropertiesCopyFrom()826   public void testPointerPropertiesCopyFrom() {
827     PointerProperties properties = new PointerProperties();
828     properties.id = 1;
829     properties.toolType = MotionEvent.TOOL_TYPE_MOUSE;
830 
831     PointerProperties copy = new PointerProperties();
832     copy.copyFrom(properties);
833     assertThat(copy).hasId(1);
834     assertThat(properties).hasToolType(MotionEvent.TOOL_TYPE_MOUSE);
835   }
836 
837   @Test
testAxisFromToString()838   public void testAxisFromToString() {
839     assertThat(MotionEvent.axisToString(MotionEvent.AXIS_RTRIGGER)).isEqualTo("AXIS_RTRIGGER");
840     assertThat(MotionEvent.axisFromString("AXIS_RTRIGGER")).isEqualTo(MotionEvent.AXIS_RTRIGGER);
841   }
842 
843   private static class MotionEventEqualitySubject extends Subject {
844     private final MotionEvent actual;
845 
MotionEventEqualitySubject(FailureMetadata metadata, MotionEvent actual)846     private MotionEventEqualitySubject(FailureMetadata metadata, MotionEvent actual) {
847       super(metadata, actual);
848       this.actual = actual;
849     }
850 
assertThat(MotionEvent event)851     public static MotionEventEqualitySubject assertThat(MotionEvent event) {
852       return Truth.assertAbout(motionEvents()).that(event);
853     }
854 
motionEvents()855     public static Subject.Factory<MotionEventEqualitySubject, MotionEvent> motionEvents() {
856       return MotionEventEqualitySubject::new;
857     }
858 
pointerCoords(int pointerIndex)859     public PointerCoordsEqualitySubject pointerCoords(int pointerIndex) {
860       PointerCoords outPointerCoords = new PointerCoords();
861       actual.getPointerCoords(pointerIndex, outPointerCoords);
862       return check("getPointerCoords(%s)", pointerIndex)
863           .about(PointerCoordsEqualitySubject.pointerCoords())
864           .that(outPointerCoords);
865     }
866 
historicalPointerCoords(int pointerIndex, int pos)867     public PointerCoordsEqualitySubject historicalPointerCoords(int pointerIndex, int pos) {
868       PointerCoords outPointerCoords = new PointerCoords();
869       actual.getHistoricalPointerCoords(pointerIndex, pos, outPointerCoords);
870       return check("getHistoricalPointerCoords(%s, %s)", pointerIndex, pos)
871           .about(PointerCoordsEqualitySubject.pointerCoords())
872           .that(outPointerCoords);
873     }
874 
875     /** Asserts that the given MotionEvent matches the current subject. */
isEqualToWithinTolerance(MotionEvent other, float tolerance)876     public void isEqualToWithinTolerance(MotionEvent other, float tolerance) {
877       check("getDownTime()").that(actual.getDownTime()).isEqualTo(other.getDownTime());
878       check("getEventTime()").that(actual.getEventTime()).isEqualTo(other.getEventTime());
879       check("action()").that(actual.getAction()).isEqualTo(other.getAction());
880       check("buttonState()").that(actual.getButtonState()).isEqualTo(other.getButtonState());
881       check("deviceId()").that(actual.getDeviceId()).isEqualTo(other.getDeviceId());
882       check("getFlags()").that(actual.getFlags()).isEqualTo(other.getFlags());
883       check("getEdgeFlags()").that(actual.getEdgeFlags()).isEqualTo(other.getEdgeFlags());
884       check("getXPrecision()").that(actual.getXPrecision()).isEqualTo(other.getXPrecision());
885       check("getYPrecision()").that(actual.getYPrecision()).isEqualTo(other.getYPrecision());
886 
887       check("getX()").that(actual.getX()).isWithin(tolerance).of(other.getX());
888       check("getY()").that(actual.getY()).isWithin(tolerance).of(other.getY());
889       check("getPressure()").that(actual.getPressure()).isWithin(tolerance).of(other.getPressure());
890       check("getSize()").that(actual.getSize()).isWithin(tolerance).of(other.getSize());
891       check("getTouchMajor()")
892           .that(actual.getTouchMajor())
893           .isWithin(tolerance)
894           .of(other.getTouchMajor());
895       check("getTouchMinor()")
896           .that(actual.getTouchMinor())
897           .isWithin(tolerance)
898           .of(other.getTouchMinor());
899       check("getToolMajor()")
900           .that(actual.getToolMajor())
901           .isWithin(tolerance)
902           .of(other.getToolMajor());
903       check("getToolMinor()")
904           .that(actual.getToolMinor())
905           .isWithin(tolerance)
906           .of(other.getToolMinor());
907       check("getOrientation()")
908           .that(actual.getOrientation())
909           .isWithin(tolerance)
910           .of(other.getOrientation());
911       check("getPointerCount()").that(actual.getPointerCount()).isEqualTo(other.getPointerCount());
912 
913       for (int i = 1; i < actual.getPointerCount(); i++) {
914         check("getX(%s)", i).that(actual.getX(i)).isWithin(tolerance).of(other.getX(i));
915         check("getY(%s)", i).that(actual.getY(i)).isWithin(tolerance).of(other.getY(i));
916         check("getPressure(%s)", i)
917             .that(actual.getPressure(i))
918             .isWithin(tolerance)
919             .of(other.getPressure(i));
920         check("getSize(%s)", i).that(actual.getSize(i)).isWithin(tolerance).of(other.getSize(i));
921         check("getTouchMajor(%s)", i)
922             .that(actual.getTouchMajor(i))
923             .isWithin(tolerance)
924             .of(other.getTouchMajor(i));
925         check("getTouchMinor(%s)", i)
926             .that(actual.getTouchMinor(i))
927             .isWithin(tolerance)
928             .of(other.getTouchMinor(i));
929         check("getToolMajor(%s)", i)
930             .that(actual.getToolMajor(i))
931             .isWithin(tolerance)
932             .of(other.getToolMajor(i));
933         check("getToolMinor(%s)", i)
934             .that(actual.getToolMinor(i))
935             .isWithin(tolerance)
936             .of(other.getToolMinor(i));
937         check("getOrientation(%s)", i)
938             .that(actual.getOrientation(i))
939             .isWithin(tolerance)
940             .of(other.getOrientation(i));
941       }
942       check("getHistorySize()").that(actual.getHistorySize()).isEqualTo(other.getHistorySize());
943 
944       for (int i = 0; i < actual.getHistorySize(); i++) {
945         check("getHistoricalX(%s)", i).that(actual.getX(i)).isWithin(tolerance).of(other.getX(i));
946         check("getHistoricalY(%s)", i)
947             .that(actual.getHistoricalY(i))
948             .isWithin(tolerance)
949             .of(other.getHistoricalY(i));
950         check("getHistoricalPressure(%s)", i)
951             .that(actual.getHistoricalPressure(i))
952             .isWithin(tolerance)
953             .of(other.getHistoricalPressure(i));
954         check("getHistoricalSize(%s)", i)
955             .that(actual.getHistoricalSize(i))
956             .isWithin(tolerance)
957             .of(other.getHistoricalSize(i));
958         check("getHistoricalTouchMajor(%s)", i)
959             .that(actual.getHistoricalTouchMajor(i))
960             .isWithin(tolerance)
961             .of(other.getHistoricalTouchMajor(i));
962         check("getHistoricalTouchMinor(%s)", i)
963             .that(actual.getHistoricalTouchMinor(i))
964             .isWithin(tolerance)
965             .of(other.getHistoricalTouchMinor(i));
966         check("getHistoricalToolMajor(%s)", i)
967             .that(actual.getHistoricalToolMajor(i))
968             .isWithin(tolerance)
969             .of(other.getHistoricalToolMajor(i));
970         check("getHistoricalToolMinor(%s)", i)
971             .that(actual.getHistoricalToolMinor(i))
972             .isWithin(tolerance)
973             .of(other.getHistoricalToolMinor(i));
974         check("getHistoricalOrientation(%s)", i)
975             .that(actual.getHistoricalOrientation(i))
976             .isWithin(tolerance)
977             .of(other.getHistoricalOrientation(i));
978       }
979     }
980   }
981 
982   private static class PointerCoordsEqualitySubject extends Subject {
983     private final PointerCoords actual;
984 
PointerCoordsEqualitySubject(FailureMetadata metadata, PointerCoords actual)985     private PointerCoordsEqualitySubject(FailureMetadata metadata, PointerCoords actual) {
986       super(metadata, actual);
987       this.actual = actual;
988     }
989 
assertThat(PointerCoords coords)990     public static PointerCoordsEqualitySubject assertThat(PointerCoords coords) {
991       return Truth.assertAbout(pointerCoords()).that(coords);
992     }
993 
pointerCoords()994     public static Subject.Factory<PointerCoordsEqualitySubject, PointerCoords> pointerCoords() {
995       return PointerCoordsEqualitySubject::new;
996     }
997 
isEqualToWithinTolerance(PointerCoords other, float tolerance)998     public void isEqualToWithinTolerance(PointerCoords other, float tolerance) {
999       check("orientation").that(actual.orientation).isWithin(tolerance).of(other.orientation);
1000       check("pressure").that(actual.pressure).isWithin(tolerance).of(other.pressure);
1001       check("size").that(actual.size).isWithin(tolerance).of(other.size);
1002       check("toolMajor").that(actual.toolMajor).isWithin(tolerance).of(other.toolMajor);
1003       check("toolMinor").that(actual.toolMinor).isWithin(tolerance).of(other.toolMinor);
1004       check("touchMajor").that(actual.touchMajor).isWithin(tolerance).of(other.touchMajor);
1005       check("touchMinor").that(actual.touchMinor).isWithin(tolerance).of(other.touchMinor);
1006       check("x").that(actual.x).isWithin(tolerance).of(other.x);
1007       check("y").that(actual.y).isWithin(tolerance).of(other.y);
1008     }
1009   }
1010 }
1011