• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.M;
4 import static android.os.Build.VERSION_CODES.P;
5 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
6 import static com.google.common.base.Preconditions.checkNotNull;
7 import static com.google.common.base.Preconditions.checkState;
8 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_ORIENTATION;
9 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_PRESSURE;
10 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_SIZE;
11 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOOL_MAJOR;
12 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOOL_MINOR;
13 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOUCH_MAJOR;
14 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOUCH_MINOR;
15 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_X;
16 import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_Y;
17 
18 import android.graphics.Matrix;
19 import android.os.Parcel;
20 import android.view.MotionEvent;
21 import android.view.MotionEvent.PointerCoords;
22 import android.view.MotionEvent.PointerProperties;
23 import java.lang.reflect.Field;
24 import java.lang.reflect.Modifier;
25 import java.util.List;
26 import org.robolectric.annotation.HiddenApi;
27 import org.robolectric.annotation.Implementation;
28 import org.robolectric.annotation.Implements;
29 import org.robolectric.annotation.InDevelopment;
30 import org.robolectric.annotation.RealObject;
31 import org.robolectric.annotation.Resetter;
32 import org.robolectric.res.android.NativeObjRegistry;
33 import org.robolectric.util.ReflectionHelpers;
34 import org.robolectric.versioning.AndroidVersions.V;
35 
36 /**
37  * Shadow of MotionEvent.
38  *
39  * <p>The Android framework stores motion events in a pool of native objects. All motion event data
40  * is stored natively, and accessed via a series of static native methods following the pattern
41  * nativeGetXXXX(mNativePtr, ...)
42  *
43  * <p>This shadow mirrors this design, but has java equivalents of each native object. Most of the
44  * contents of this class were transliterated from oreo-mr1 (SDK 27)
45  * frameworks/base/core/jni/android_view_MotionEvent.cpp
46  *
47  * @see <a
48  *     href="https://android.googlesource.com/platform/frameworks/base/+/oreo-mr1-release/core/jni/android_view_MotionEvent.cpp">core/jni/android_view_MotionEvent.cpp</a>
49  *     <p>Tests should not reference this class directly. MotionEvents should be created via one of
50  *     the MotionEvent.obtain methods or via MotionEventBuilder.
51  */
52 @SuppressWarnings({"UnusedDeclaration"})
53 @Implements(value = MotionEvent.class)
54 public class ShadowMotionEvent extends ShadowInputEvent {
55 
56   private static NativeObjRegistry<NativeInput.MotionEvent> nativeMotionEventRegistry =
57       new NativeObjRegistry<>(NativeInput.MotionEvent.class);
58 
59   private static final int HISTORY_CURRENT = -0x80000000;
60 
61   @RealObject private MotionEvent realMotionEvent;
62 
63   @Resetter
reset()64   public static void reset() {
65     // rely on MotionEvent finalizer to clear native object instead of calling
66     // nativeMotionEventRegistry.clear();
67     ReflectionHelpers.setStaticField(MotionEvent.class, "gRecyclerTop", null);
68     ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerCoords", null);
69     ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerProperties", null);
70     ReflectionHelpers.setStaticField(MotionEvent.class, "gRecyclerUsed", 0);
71     ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerIndexMap", null);
72   }
73 
validatePointerCount(int pointerCount)74   private static void validatePointerCount(int pointerCount) {
75     checkState(pointerCount >= 1, "pointerCount must be at least 1");
76   }
77 
validatePointerPropertiesArray( PointerProperties[] pointerPropertiesObjArray, int pointerCount)78   private static void validatePointerPropertiesArray(
79       PointerProperties[] pointerPropertiesObjArray, int pointerCount) {
80     checkNotNull(pointerPropertiesObjArray, "pointerProperties array must not be null");
81     checkState(
82         pointerPropertiesObjArray.length >= pointerCount,
83         "pointerProperties array must be large enough to hold all pointers");
84   }
85 
validatePointerCoordsObjArray( PointerCoords[] pointerCoordsObjArray, int pointerCount)86   private static void validatePointerCoordsObjArray(
87       PointerCoords[] pointerCoordsObjArray, int pointerCount) {
88     checkNotNull(pointerCoordsObjArray, "pointerCoords array must not be null");
89     checkState(
90         pointerCoordsObjArray.length >= pointerCount,
91         "pointerCoords array must be large enough to hold all pointers");
92   }
93 
validatePointerIndex(int pointerIndex, int pointerCount)94   private static void validatePointerIndex(int pointerIndex, int pointerCount) {
95     checkState(pointerIndex >= 0 && pointerIndex < pointerCount, "pointerIndex out of range");
96   }
97 
validateHistoryPos(int historyPos, int historySize)98   private static void validateHistoryPos(int historyPos, int historySize) {
99     checkState(historyPos >= 0 && historyPos < historySize, "historyPos out of range");
100   }
101 
validatePointerCoords(PointerCoords pointerCoordsObj)102   private static void validatePointerCoords(PointerCoords pointerCoordsObj) {
103     checkNotNull(pointerCoordsObj, "pointerCoords must not be null");
104   }
105 
validatePointerProperties(PointerProperties pointerPropertiesObj)106   private static void validatePointerProperties(PointerProperties pointerPropertiesObj) {
107     checkNotNull(pointerPropertiesObj, "pointerProperties must not be null");
108   }
109 
pointerCoordsToNative( PointerCoords pointerCoordsObj, float xOffset, float yOffset)110   private static NativeInput.PointerCoords pointerCoordsToNative(
111       PointerCoords pointerCoordsObj, float xOffset, float yOffset) {
112     NativeInput.PointerCoords outRawPointerCoords = new NativeInput.PointerCoords();
113     outRawPointerCoords.clear();
114     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, pointerCoordsObj.x - xOffset);
115     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointerCoordsObj.y - yOffset);
116     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerCoordsObj.pressure);
117     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, pointerCoordsObj.size);
118     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerCoordsObj.touchMajor);
119     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerCoordsObj.touchMinor);
120     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerCoordsObj.toolMajor);
121     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, pointerCoordsObj.toolMinor);
122     outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerCoordsObj.orientation);
123     long packedAxisBits = ReflectionHelpers.getField(pointerCoordsObj, "mPackedAxisBits");
124     NativeBitSet64 bits = new NativeBitSet64(packedAxisBits);
125     if (!bits.isEmpty()) {
126       float[] valuesArray = ReflectionHelpers.getField(pointerCoordsObj, "mPackedAxisValues");
127       if (valuesArray != null) {
128         int index = 0;
129         do {
130           int axis = bits.clearFirstMarkedBit();
131           outRawPointerCoords.setAxisValue(axis, valuesArray[index++]);
132         } while (!bits.isEmpty());
133       }
134     }
135     return outRawPointerCoords;
136   }
137 
obtainPackedAxisValuesArray( int minSize, PointerCoords outPointerCoordsObj)138   private static float[] obtainPackedAxisValuesArray(
139       int minSize, PointerCoords outPointerCoordsObj) {
140     float[] outValuesArray = ReflectionHelpers.getField(outPointerCoordsObj, "mPackedAxisValues");
141     if (outValuesArray != null) {
142       int size = outValuesArray.length;
143       if (minSize <= size) {
144         return outValuesArray;
145       }
146     }
147     int size = 8;
148     while (size < minSize) {
149       size *= 2;
150     }
151     outValuesArray = new float[size];
152     ReflectionHelpers.setField(outPointerCoordsObj, "mPackedAxisValues", outValuesArray);
153     return outValuesArray;
154   }
155 
pointerCoordsFromNative( NativeInput.PointerCoords rawPointerCoords, float xOffset, float yOffset, PointerCoords outPointerCoordsObj)156   private static void pointerCoordsFromNative(
157       NativeInput.PointerCoords rawPointerCoords,
158       float xOffset,
159       float yOffset,
160       PointerCoords outPointerCoordsObj) {
161     outPointerCoordsObj.x = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_X) + xOffset;
162     outPointerCoordsObj.y = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_Y) + yOffset;
163     outPointerCoordsObj.pressure = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
164     outPointerCoordsObj.size = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_SIZE);
165     outPointerCoordsObj.touchMajor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
166     outPointerCoordsObj.touchMinor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
167     outPointerCoordsObj.toolMajor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR);
168     outPointerCoordsObj.toolMinor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR);
169     outPointerCoordsObj.orientation = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
170     long outBits = 0;
171     NativeBitSet64 bits = new NativeBitSet64(rawPointerCoords.getBits());
172     bits.clearBit(AMOTION_EVENT_AXIS_X);
173     bits.clearBit(AMOTION_EVENT_AXIS_Y);
174     bits.clearBit(AMOTION_EVENT_AXIS_PRESSURE);
175     bits.clearBit(AMOTION_EVENT_AXIS_SIZE);
176     bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
177     bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MINOR);
178     bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MAJOR);
179     bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MINOR);
180     bits.clearBit(AMOTION_EVENT_AXIS_ORIENTATION);
181     if (!bits.isEmpty()) {
182       int packedAxesCount = bits.count();
183       float[] outValuesArray = obtainPackedAxisValuesArray(packedAxesCount, outPointerCoordsObj);
184       float[] outValues = outValuesArray;
185       int index = 0;
186       do {
187         int axis = bits.clearFirstMarkedBit();
188         outBits |= NativeBitSet64.valueForBit(axis);
189         outValues[index++] = rawPointerCoords.getAxisValue(axis);
190       } while (!bits.isEmpty());
191     }
192     ReflectionHelpers.setField(outPointerCoordsObj, "mPackedAxisBits", outBits);
193   }
194 
195   @Implementation(maxSdk = P)
196   @HiddenApi
nativeInitialize( long nativePtr, int deviceId, int source, int action, int flags, int edgeFlags, int metaState, int buttonState, float xOffset, float yOffset, float xPrecision, float yPrecision, long downTimeNanos, long eventTimeNanos, int pointerCount, PointerProperties[] pointerPropertiesObjArray, PointerCoords[] pointerCoordsObjArray)197   protected static long nativeInitialize(
198       long nativePtr,
199       int deviceId,
200       int source,
201       int action,
202       int flags,
203       int edgeFlags,
204       int metaState,
205       int buttonState,
206       float xOffset,
207       float yOffset,
208       float xPrecision,
209       float yPrecision,
210       long downTimeNanos,
211       long eventTimeNanos,
212       int pointerCount,
213       PointerProperties[] pointerPropertiesObjArray,
214       PointerCoords[] pointerCoordsObjArray) {
215 
216     validatePointerCount(pointerCount);
217     validatePointerPropertiesArray(pointerPropertiesObjArray, pointerCount);
218     validatePointerCoordsObjArray(pointerCoordsObjArray, pointerCount);
219 
220     NativeInput.MotionEvent event;
221     if (nativePtr > 0) {
222       event = nativeMotionEventRegistry.getNativeObject(nativePtr);
223     } else {
224       event = new NativeInput.MotionEvent();
225       nativePtr = nativeMotionEventRegistry.register(event);
226     }
227 
228     NativeInput.PointerCoords[] rawPointerCoords = new NativeInput.PointerCoords[pointerCount];
229     for (int i = 0; i < pointerCount; i++) {
230       PointerCoords pointerCoordsObj = pointerCoordsObjArray[i];
231       checkNotNull(pointerCoordsObj);
232       rawPointerCoords[i] = pointerCoordsToNative(pointerCoordsObj, xOffset, yOffset);
233     }
234 
235     event.initialize(
236         deviceId,
237         source,
238         action,
239         0,
240         flags,
241         edgeFlags,
242         metaState,
243         buttonState,
244         xOffset,
245         yOffset,
246         xPrecision,
247         yPrecision,
248         downTimeNanos,
249         eventTimeNanos,
250         pointerCount,
251         pointerPropertiesObjArray,
252         rawPointerCoords);
253     return nativePtr;
254   }
255 
256   // TODO(brettchabot): properly handle displayId
257   @Implementation(minSdk = android.os.Build.VERSION_CODES.Q)
258   @HiddenApi
nativeInitialize( long nativePtr, int deviceId, int source, int displayId, int action, int flags, int edgeFlags, int metaState, int buttonState, int classification, float xOffset, float yOffset, float xPrecision, float yPrecision, long downTimeNanos, long eventTimeNanos, int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords)259   protected static long nativeInitialize(
260       long nativePtr,
261       int deviceId,
262       int source,
263       int displayId,
264       int action,
265       int flags,
266       int edgeFlags,
267       int metaState,
268       int buttonState,
269       int classification,
270       float xOffset,
271       float yOffset,
272       float xPrecision,
273       float yPrecision,
274       long downTimeNanos,
275       long eventTimeNanos,
276       int pointerCount,
277       PointerProperties[] pointerIds,
278       PointerCoords[] pointerCoords) {
279     return nativeInitialize(
280         nativePtr,
281         deviceId,
282         source,
283         action,
284         flags,
285         edgeFlags,
286         metaState,
287         buttonState,
288         xOffset,
289         yOffset,
290         xPrecision,
291         yPrecision,
292         downTimeNanos,
293         eventTimeNanos,
294         pointerCount,
295         pointerIds,
296         pointerCoords);
297   }
298 
299   @Implementation
300   @HiddenApi
nativeDispose(long nativePtr)301   protected static void nativeDispose(long nativePtr) {
302     nativeMotionEventRegistry.unregister(nativePtr);
303   }
304 
305   @Implementation
306   @HiddenApi
nativeAddBatch( long nativePtr, long eventTimeNanos, PointerCoords[] pointerCoordsObjArray, int metaState)307   protected static void nativeAddBatch(
308       long nativePtr, long eventTimeNanos, PointerCoords[] pointerCoordsObjArray, int metaState) {
309     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
310     int pointerCount = event.getPointerCount();
311     validatePointerCoordsObjArray(pointerCoordsObjArray, pointerCount);
312     NativeInput.PointerCoords[] rawPointerCoords = new NativeInput.PointerCoords[pointerCount];
313     for (int i = 0; i < pointerCount; i++) {
314       PointerCoords pointerCoordsObj = pointerCoordsObjArray[i];
315       checkNotNull(pointerCoordsObj);
316       rawPointerCoords[i] =
317           pointerCoordsToNative(pointerCoordsObj, event.getXOffset(), event.getYOffset());
318     }
319     event.addSample(eventTimeNanos, rawPointerCoords);
320     event.setMetaState(event.getMetaState() | metaState);
321   }
322 
323   @Implementation
324   @HiddenApi
nativeGetPointerCoords( long nativePtr, int pointerIndex, int historyPos, PointerCoords outPointerCoordsObj)325   protected static void nativeGetPointerCoords(
326       long nativePtr, int pointerIndex, int historyPos, PointerCoords outPointerCoordsObj) {
327     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
328     int pointerCount = event.getPointerCount();
329     validatePointerIndex(pointerIndex, pointerCount);
330     validatePointerCoords(outPointerCoordsObj);
331 
332     NativeInput.PointerCoords rawPointerCoords;
333     if (historyPos == HISTORY_CURRENT) {
334       rawPointerCoords = event.getRawPointerCoords(pointerIndex);
335     } else {
336       int historySize = event.getHistorySize();
337       validateHistoryPos(historyPos, historySize);
338       rawPointerCoords = event.getHistoricalRawPointerCoords(pointerIndex, historyPos);
339     }
340     pointerCoordsFromNative(
341         rawPointerCoords, event.getXOffset(), event.getYOffset(), outPointerCoordsObj);
342   }
343 
344   @Implementation
345   @HiddenApi
nativeGetPointerProperties( long nativePtr, int pointerIndex, PointerProperties outPointerPropertiesObj)346   protected static void nativeGetPointerProperties(
347       long nativePtr, int pointerIndex, PointerProperties outPointerPropertiesObj) {
348     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
349     int pointerCount = event.getPointerCount();
350     validatePointerIndex(pointerIndex, pointerCount);
351     validatePointerProperties(outPointerPropertiesObj);
352 
353     PointerProperties pointerProperties = event.getPointerProperties(pointerIndex);
354     // pointerPropertiesFromNative(env, pointerProperties, outPointerPropertiesObj);
355     outPointerPropertiesObj.copyFrom(pointerProperties);
356   }
357 
358   @Implementation
359   @HiddenApi
nativeReadFromParcel(long nativePtr, Parcel parcelObj)360   protected static long nativeReadFromParcel(long nativePtr, Parcel parcelObj) {
361     NativeInput.MotionEvent event;
362     if (nativePtr == 0) {
363       event = new NativeInput.MotionEvent();
364       nativePtr = nativeMotionEventRegistry.register(event);
365     } else {
366       event = nativeMotionEventRegistry.getNativeObject(nativePtr);
367     }
368     boolean status = event.readFromParcel(parcelObj);
369     if (!status) {
370       if (nativePtr > 0) {
371         nativeMotionEventRegistry.unregister(nativePtr);
372       }
373       throw new RuntimeException("Failed to read MotionEvent parcel.");
374     }
375     return nativePtr;
376   }
377 
378   @Implementation
379   @HiddenApi
nativeWriteToParcel(long nativePtr, Parcel parcel)380   protected static void nativeWriteToParcel(long nativePtr, Parcel parcel) {
381     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
382     if (!event.writeToParcel(parcel)) {
383       throw new RuntimeException("Failed to write MotionEvent parcel.");
384     }
385   }
386 
387   @Implementation
388   @HiddenApi
nativeAxisToString(int axis)389   protected static String nativeAxisToString(int axis) {
390     // The native code just mirrors the AXIS_* constants defined in MotionEvent.java.
391     // Look up the field value by reflection to future proof this method
392     for (Field field : MotionEvent.class.getDeclaredFields()) {
393       int modifiers = field.getModifiers();
394       try {
395         if (Modifier.isStatic(modifiers)
396             && Modifier.isPublic(modifiers)
397             && field.getName().startsWith("AXIS_")
398             && field.getInt(null) == axis) {
399           // return the field name stripping off the "AXIS_" prefix
400           return field.getName().substring(5);
401         }
402       } catch (IllegalAccessException e) {
403         // ignore
404       }
405     }
406     return null;
407   }
408 
409   @Implementation
410   @HiddenApi
nativeAxisFromString(String label)411   protected static int nativeAxisFromString(String label) {
412     // The native code just mirrors the AXIS_* constants defined in MotionEvent.java. Look up
413     // the field value by reflection
414     try {
415       Field constantField = MotionEvent.class.getDeclaredField("AXIS_" + label);
416       return constantField.getInt(null);
417     } catch (NoSuchFieldException | IllegalAccessException e) {
418       return 0;
419     }
420   }
421 
422   @Implementation
423   @HiddenApi
nativeGetPointerId(long nativePtr, int pointerIndex)424   protected static int nativeGetPointerId(long nativePtr, int pointerIndex) {
425     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
426     int pointerCount = event.getPointerCount();
427     validatePointerIndex(pointerIndex, pointerCount);
428     return event.getPointerId(pointerIndex);
429   }
430 
431   @Implementation
432   @HiddenApi
nativeGetToolType(long nativePtr, int pointerIndex)433   protected static int nativeGetToolType(long nativePtr, int pointerIndex) {
434     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
435     int pointerCount = event.getPointerCount();
436     validatePointerIndex(pointerIndex, pointerCount);
437     return event.getToolType(pointerIndex);
438   }
439 
440   @Implementation
441   @HiddenApi
nativeGetEventTimeNanos(long nativePtr, int historyPos)442   protected static long nativeGetEventTimeNanos(long nativePtr, int historyPos) {
443     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
444     if (historyPos == HISTORY_CURRENT) {
445       return event.getEventTime();
446     } else {
447       int historySize = event.getHistorySize();
448       validateHistoryPos(historyPos, historySize);
449       return event.getHistoricalEventTime(historyPos);
450     }
451   }
452 
453   @Implementation
454   @HiddenApi
nativeGetRawAxisValue( long nativePtr, int axis, int pointerIndex, int historyPos)455   protected static float nativeGetRawAxisValue(
456       long nativePtr, int axis, int pointerIndex, int historyPos) {
457     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
458     int pointerCount = event.getPointerCount();
459     validatePointerIndex(pointerIndex, pointerCount);
460 
461     if (historyPos == HISTORY_CURRENT) {
462       return event.getRawAxisValue(axis, pointerIndex);
463     } else {
464       int historySize = event.getHistorySize();
465       validateHistoryPos(historyPos, historySize);
466       return event.getHistoricalRawAxisValue(axis, pointerIndex, historyPos);
467     }
468   }
469 
470   @Implementation
471   @HiddenApi
nativeGetAxisValue( long nativePtr, int axis, int pointerIndex, int historyPos)472   protected static float nativeGetAxisValue(
473       long nativePtr, int axis, int pointerIndex, int historyPos) {
474     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
475     int pointerCount = event.getPointerCount();
476     validatePointerIndex(pointerIndex, pointerCount);
477 
478     if (historyPos == HISTORY_CURRENT) {
479       return event.getAxisValue(axis, pointerIndex);
480     } else {
481       int historySize = event.getHistorySize();
482       validateHistoryPos(historyPos, historySize);
483       return event.getHistoricalAxisValue(axis, pointerIndex, historyPos);
484     }
485   }
486 
487   @Implementation
488   @HiddenApi
nativeCopy(long destNativePtr, long sourceNativePtr, boolean keepHistory)489   protected static long nativeCopy(long destNativePtr, long sourceNativePtr, boolean keepHistory) {
490     NativeInput.MotionEvent destEvent = nativeMotionEventRegistry.peekNativeObject(destNativePtr);
491     if (destEvent == null) {
492       destEvent = new NativeInput.MotionEvent();
493       destNativePtr = nativeMotionEventRegistry.register(destEvent);
494     }
495     NativeInput.MotionEvent sourceEvent = getNativeMotionEvent(sourceNativePtr);
496     destEvent.copyFrom(sourceEvent, keepHistory);
497     return destNativePtr;
498   }
499 
500   @Implementation
501   @HiddenApi
nativeGetDeviceId(long nativePtr)502   protected static int nativeGetDeviceId(long nativePtr) {
503     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
504     return event.getDeviceId();
505   }
506 
507   @Implementation
508   @HiddenApi
nativeGetSource(long nativePtr)509   protected static int nativeGetSource(long nativePtr) {
510     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
511     return event.getSource();
512   }
513 
514   @Implementation
515   @HiddenApi
516   @SuppressWarnings("robolectric.ShadowReturnTypeMismatch")
nativeSetSource(long nativePtr, int source)517   protected static void nativeSetSource(long nativePtr, int source) {
518     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
519     event.setSource(source);
520   }
521 
522   @Implementation
523   @HiddenApi
nativeGetAction(long nativePtr)524   protected static int nativeGetAction(long nativePtr) {
525     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
526     return event.getAction();
527   }
528 
529   @Implementation
530   @HiddenApi
nativeSetAction(long nativePtr, int action)531   protected static void nativeSetAction(long nativePtr, int action) {
532     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
533     event.setAction(action);
534   }
535 
536   @Implementation(minSdk = M)
537   @HiddenApi
nativeGetActionButton(long nativePtr)538   protected static int nativeGetActionButton(long nativePtr) {
539     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
540     return event.getActionButton();
541   }
542 
543   @Implementation(minSdk = M)
544   @HiddenApi
nativeSetActionButton(long nativePtr, int button)545   protected static void nativeSetActionButton(long nativePtr, int button) {
546     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
547     event.setActionButton(button);
548   }
549 
550   @Implementation
551   @HiddenApi
nativeIsTouchEvent(long nativePtr)552   protected static boolean nativeIsTouchEvent(long nativePtr) {
553     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
554     return event.isTouchEvent();
555   }
556 
557   @Implementation
558   @HiddenApi
nativeGetFlags(long nativePtr)559   protected static int nativeGetFlags(long nativePtr) {
560     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
561     return event.getFlags();
562   }
563 
564   @Implementation
565   @HiddenApi
nativeSetFlags(long nativePtr, int flags)566   protected static void nativeSetFlags(long nativePtr, int flags) {
567     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
568     event.setFlags(flags);
569   }
570 
571   @Implementation
572   @HiddenApi
nativeGetEdgeFlags(long nativePtr)573   protected static int nativeGetEdgeFlags(long nativePtr) {
574     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
575     return event.getEdgeFlags();
576   }
577 
578   @Implementation
579   @HiddenApi
nativeSetEdgeFlags(long nativePtr, int edgeFlags)580   protected static void nativeSetEdgeFlags(long nativePtr, int edgeFlags) {
581     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
582     event.setEdgeFlags(edgeFlags);
583   }
584 
585   @Implementation
586   @HiddenApi
nativeGetMetaState(long nativePtr)587   protected static int nativeGetMetaState(long nativePtr) {
588     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
589     return event.getMetaState();
590   }
591 
592   @Implementation
593   @HiddenApi
nativeGetButtonState(long nativePtr)594   protected static int nativeGetButtonState(long nativePtr) {
595     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
596     return event.getButtonState();
597   }
598 
599   @Implementation(minSdk = M)
600   @HiddenApi
nativeSetButtonState(long nativePtr, int buttonState)601   protected static void nativeSetButtonState(long nativePtr, int buttonState) {
602     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
603     event.setButtonState(buttonState);
604   }
605 
606   @Implementation
607   @HiddenApi
nativeOffsetLocation(long nativePtr, float deltaX, float deltaY)608   protected static void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY) {
609     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
610     event.offsetLocation(deltaX, deltaY);
611   }
612 
613   @Implementation(maxSdk = UPSIDE_DOWN_CAKE)
614   @HiddenApi
615   @InDevelopment
nativeGetXOffset(long nativePtr)616   protected static float nativeGetXOffset(long nativePtr) {
617     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
618     return event.getXOffset();
619   }
620 
621   @Implementation(maxSdk = UPSIDE_DOWN_CAKE)
622   @HiddenApi
623   @InDevelopment
nativeGetYOffset(long nativePtr)624   protected static float nativeGetYOffset(long nativePtr) {
625     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
626     return event.getYOffset();
627   }
628 
629   @Implementation(minSdk = V.SDK_INT)
split(int idBits)630   protected final MotionEvent split(int idBits) {
631     NativeInput.MotionEvent event = getNativeMotionEvent();
632     return event.nativeSplit(idBits);
633   }
634 
635   @Implementation(minSdk = V.SDK_INT)
636   @HiddenApi
637   @InDevelopment
nativeGetRawXOffset(long nativePtr)638   protected static float nativeGetRawXOffset(long nativePtr) {
639     return getNativeMotionEvent(nativePtr).getXOffset();
640   }
641 
642   @Implementation(minSdk = V.SDK_INT)
643   @HiddenApi
644   @InDevelopment
nativeGetRawYOffset(long nativePtr)645   protected static float nativeGetRawYOffset(long nativePtr) {
646     return getNativeMotionEvent(nativePtr).getYOffset();
647   }
648 
649   @Implementation
650   @HiddenApi
nativeGetXPrecision(long nativePtr)651   protected static float nativeGetXPrecision(long nativePtr) {
652     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
653     return event.getXPrecision();
654   }
655 
656   @Implementation
657   @HiddenApi
nativeGetYPrecision(long nativePtr)658   protected static float nativeGetYPrecision(long nativePtr) {
659     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
660     return event.getYPrecision();
661   }
662 
663   @Implementation
664   @HiddenApi
nativeGetDownTimeNanos(long nativePtr)665   protected static long nativeGetDownTimeNanos(long nativePtr) {
666     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
667     return event.getDownTime();
668   }
669 
670   @Implementation
671   @HiddenApi
nativeSetDownTimeNanos(long nativePtr, long downTimeNanos)672   protected static void nativeSetDownTimeNanos(long nativePtr, long downTimeNanos) {
673     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
674     event.setDownTime(downTimeNanos);
675   }
676 
677   @Implementation
678   @HiddenApi
nativeGetPointerCount(long nativePtr)679   protected static int nativeGetPointerCount(long nativePtr) {
680     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
681     return event.getPointerCount();
682   }
683 
684   @Implementation
685   @HiddenApi
nativeFindPointerIndex(long nativePtr, int pointerId)686   protected static int nativeFindPointerIndex(long nativePtr, int pointerId) {
687     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
688     return event.findPointerIndex(pointerId);
689   }
690 
691   @Implementation
692   @HiddenApi
nativeGetHistorySize(long nativePtr)693   protected static int nativeGetHistorySize(long nativePtr) {
694     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
695     return event.getHistorySize();
696   }
697 
698   @Implementation
699   @HiddenApi
nativeScale(long nativePtr, float scale)700   protected static void nativeScale(long nativePtr, float scale) {
701     NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr);
702     event.scale(scale);
703   }
704 
getNativeMotionEvent(long nativePtr)705   protected static NativeInput.MotionEvent getNativeMotionEvent(long nativePtr) {
706     // check that MotionEvent was initialized properly. This can occur if MotionEvent was mocked
707     checkState(
708         nativePtr > 0,
709         "MotionEvent has not been initialized. "
710             + "Ensure MotionEvent.obtain was used to create it, instead of creating it directly "
711             + "or via a Mocking framework");
712 
713     return nativeMotionEventRegistry.getNativeObject(nativePtr);
714   }
715 
716   @Implementation
transform(Matrix matrix)717   protected void transform(Matrix matrix) {
718     checkNotNull(matrix);
719     NativeInput.MotionEvent event = getNativeMotionEvent();
720 
721     float[] m = new float[9];
722     matrix.getValues(m);
723     event.transform(m);
724   }
725 
getNativeMotionEvent()726   protected NativeInput.MotionEvent getNativeMotionEvent() {
727 
728     long nativePtr = ReflectionHelpers.getField(realMotionEvent, "mNativePtr");
729 
730     return nativeMotionEventRegistry.getNativeObject(nativePtr);
731   }
732 
733   // Testing API methods
734 
735   /**
736    * @deprecated use {@link MotionEvent#obtain} or {@link
737    *     androidx.test.core.view.MotionEventBuilder} to create a MotionEvent with desired data.
738    */
739   @Deprecated
setPointer2(float pointer1X, float pointer1Y)740   public MotionEvent setPointer2(float pointer1X, float pointer1Y) {
741     NativeInput.MotionEvent event = getNativeMotionEvent();
742     List<NativeInput.PointerCoords> pointerCoords = event.getSamplePointerCoords();
743     List<PointerProperties> pointerProperties = event.getPointerProperties();
744     ensureTwoPointers(pointerCoords, pointerProperties);
745 
746     pointerCoords.get(1).setAxisValue(AMOTION_EVENT_AXIS_X, pointer1X);
747     pointerCoords.get(1).setAxisValue(AMOTION_EVENT_AXIS_Y, pointer1Y);
748     return realMotionEvent;
749   }
750 
ensureTwoPointers( List<NativeInput.PointerCoords> pointerCoords, List<PointerProperties> pointerProperties)751   private static void ensureTwoPointers(
752       List<NativeInput.PointerCoords> pointerCoords, List<PointerProperties> pointerProperties) {
753     if (pointerCoords.size() < 2) {
754       pointerCoords.add(new NativeInput.PointerCoords());
755     }
756     if (pointerProperties.size() < 2) {
757       pointerProperties.add(new PointerProperties());
758     }
759   }
760 
761   /**
762    * @deprecated use {@link MotionEvent#obtain} or {@link
763    *     androidx.test.core.view.MotionEventBuilder#setPointerAction(int, int)} to create a
764    *     MotionEvent with desired data.
765    */
766   @Deprecated
setPointerIndex(int pointerIndex)767   public void setPointerIndex(int pointerIndex) {
768     NativeInput.MotionEvent event = getNativeMotionEvent();
769     // pointer index is stored in upper two bytes of action
770     event.setAction(
771         event.getAction() | ((pointerIndex & 0xff) << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
772   }
773 
774   /**
775    * @deprecated use {@link MotionEvent#obtain} or {@link MotionEventBuilder} to create a
776    *     MotionEvent with desired data
777    */
778   @Deprecated
setPointerIds(int index0PointerId, int index1PointerId)779   public void setPointerIds(int index0PointerId, int index1PointerId) {
780     NativeInput.MotionEvent event = getNativeMotionEvent();
781     List<NativeInput.PointerCoords> pointerCoords = event.getSamplePointerCoords();
782     List<PointerProperties> pointerProperties = event.getPointerProperties();
783     ensureTwoPointers(pointerCoords, pointerProperties);
784 
785     pointerProperties.get(0).id = index0PointerId;
786     pointerProperties.get(1).id = index1PointerId;
787   }
788 }
789