| package org.robolectric.shadows; |
| |
| import static android.os.Build.VERSION_CODES.KITKAT_WATCH; |
| import static android.os.Build.VERSION_CODES.LOLLIPOP; |
| import static android.os.Build.VERSION_CODES.M; |
| import static android.os.Build.VERSION_CODES.P; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_ORIENTATION; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_PRESSURE; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_SIZE; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOOL_MAJOR; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOOL_MINOR; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOUCH_MAJOR; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_TOUCH_MINOR; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_X; |
| import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_Y; |
| |
| import android.graphics.Matrix; |
| import android.os.Parcel; |
| import android.view.MotionEvent; |
| import android.view.MotionEvent.PointerCoords; |
| import android.view.MotionEvent.PointerProperties; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.util.List; |
| import org.robolectric.RuntimeEnvironment; |
| import org.robolectric.annotation.HiddenApi; |
| import org.robolectric.annotation.Implementation; |
| import org.robolectric.annotation.Implements; |
| import org.robolectric.annotation.RealObject; |
| import org.robolectric.annotation.Resetter; |
| import org.robolectric.res.android.NativeObjRegistry; |
| import org.robolectric.shadow.api.Shadow; |
| import org.robolectric.util.ReflectionHelpers; |
| |
| /** |
| * Shadow of MotionEvent. |
| * |
| * The Android framework stores motion events in a pool of native objects. All motion event data |
| * is stored natively, and accessed via a series of static native methods following the pattern |
| * nativeGetXXXX(mNativePtr, ...) |
| * |
| * This shadow mirrors this design, but has java equivalents of each native object. Most of the |
| * contents of this class were transliterated from oreo-mr1 (SDK 27) |
| * frameworks/base/core/jni/android_view_MotionEvent.cpp |
| * |
| * @see <a href="https://android.googlesource.com/platform/frameworks/base/+/oreo-mr1-release/core/jni/android_view_MotionEvent.cpp">core/jni/android_view_MotionEvent.cpp</a> |
| * |
| * Tests should not reference this class directly. MotionEvents should be created via one of the |
| * MotionEvent.obtain methods or via MotionEventBuilder. |
| */ |
| @SuppressWarnings({"UnusedDeclaration"}) |
| @Implements(MotionEvent.class) |
| public class ShadowMotionEvent { |
| |
| private static NativeObjRegistry<NativeInput.MotionEvent> nativeMotionEventRegistry = |
| new NativeObjRegistry<>(NativeInput.MotionEvent.class); |
| |
| private static final int HISTORY_CURRENT = -0x80000000; |
| |
| @RealObject private MotionEvent realMotionEvent; |
| |
| @Resetter |
| public static void reset() { |
| // rely on MotionEvent finalizer to clear native object instead of calling |
| // nativeMotionEventRegistry.clear(); |
| ReflectionHelpers.setStaticField(MotionEvent.class, "gRecyclerTop", null); |
| ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerCoords", null); |
| ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerProperties", null); |
| ReflectionHelpers.setStaticField(MotionEvent.class, "gRecyclerUsed", 0); |
| ReflectionHelpers.setStaticField(MotionEvent.class, "gSharedTempPointerIndexMap", null); |
| } |
| |
| private static void validatePointerCount(int pointerCount) { |
| checkState(pointerCount >= 1, "pointerCount must be at least 1"); |
| } |
| |
| private static void validatePointerPropertiesArray( |
| PointerProperties[] pointerPropertiesObjArray, int pointerCount) { |
| checkNotNull(pointerPropertiesObjArray, "pointerProperties array must not be null"); |
| checkState( |
| pointerPropertiesObjArray.length >= pointerCount, |
| "pointerProperties array must be large enough to hold all pointers"); |
| } |
| |
| private static void validatePointerCoordsObjArray( |
| PointerCoords[] pointerCoordsObjArray, int pointerCount) { |
| checkNotNull(pointerCoordsObjArray, "pointerCoords array must not be null"); |
| checkState( |
| pointerCoordsObjArray.length >= pointerCount, |
| "pointerCoords array must be large enough to hold all pointers"); |
| } |
| |
| private static void validatePointerIndex(int pointerIndex, int pointerCount) { |
| checkState(pointerIndex >= 0 && pointerIndex < pointerCount, "pointerIndex out of range"); |
| } |
| |
| private static void validateHistoryPos(int historyPos, int historySize) { |
| checkState(historyPos >= 0 && historyPos < historySize, "historyPos out of range"); |
| } |
| |
| private static void validatePointerCoords(PointerCoords pointerCoordsObj) { |
| checkNotNull(pointerCoordsObj, "pointerCoords must not be null"); |
| } |
| |
| private static void validatePointerProperties(PointerProperties pointerPropertiesObj) { |
| checkNotNull(pointerPropertiesObj, "pointerProperties must not be null"); |
| } |
| |
| private static NativeInput.PointerCoords pointerCoordsToNative( |
| PointerCoords pointerCoordsObj, float xOffset, float yOffset) { |
| NativeInput.PointerCoords outRawPointerCoords = new NativeInput.PointerCoords(); |
| outRawPointerCoords.clear(); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, pointerCoordsObj.x - xOffset); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointerCoordsObj.y - yOffset); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerCoordsObj.pressure); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, pointerCoordsObj.size); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerCoordsObj.touchMajor); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerCoordsObj.touchMinor); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerCoordsObj.toolMajor); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, pointerCoordsObj.toolMinor); |
| outRawPointerCoords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerCoordsObj.orientation); |
| long packedAxisBits = ReflectionHelpers.getField(pointerCoordsObj, "mPackedAxisBits"); |
| NativeBitSet64 bits = new NativeBitSet64(packedAxisBits); |
| if (!bits.isEmpty()) { |
| float[] valuesArray = ReflectionHelpers.getField(pointerCoordsObj, "mPackedAxisValues"); |
| if (valuesArray != null) { |
| int index = 0; |
| do { |
| int axis = bits.clearFirstMarkedBit(); |
| outRawPointerCoords.setAxisValue(axis, valuesArray[index++]); |
| } while (!bits.isEmpty()); |
| } |
| } |
| return outRawPointerCoords; |
| } |
| |
| private static float[] obtainPackedAxisValuesArray( |
| int minSize, PointerCoords outPointerCoordsObj) { |
| float[] outValuesArray = ReflectionHelpers.getField(outPointerCoordsObj, "mPackedAxisValues"); |
| if (outValuesArray != null) { |
| int size = outValuesArray.length; |
| if (minSize <= size) { |
| return outValuesArray; |
| } |
| } |
| int size = 8; |
| while (size < minSize) { |
| size *= 2; |
| } |
| outValuesArray = new float[size]; |
| ReflectionHelpers.setField(outPointerCoordsObj, "mPackedAxisValues", outValuesArray); |
| return outValuesArray; |
| } |
| |
| private static void pointerCoordsFromNative( |
| NativeInput.PointerCoords rawPointerCoords, |
| float xOffset, |
| float yOffset, |
| PointerCoords outPointerCoordsObj) { |
| outPointerCoordsObj.x = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_X) + xOffset; |
| outPointerCoordsObj.y = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_Y) + yOffset; |
| outPointerCoordsObj.pressure = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); |
| outPointerCoordsObj.size = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_SIZE); |
| outPointerCoordsObj.touchMajor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR); |
| outPointerCoordsObj.touchMinor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR); |
| outPointerCoordsObj.toolMajor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR); |
| outPointerCoordsObj.toolMinor = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR); |
| outPointerCoordsObj.orientation = rawPointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); |
| long outBits = 0; |
| NativeBitSet64 bits = new NativeBitSet64(rawPointerCoords.getBits()); |
| bits.clearBit(AMOTION_EVENT_AXIS_X); |
| bits.clearBit(AMOTION_EVENT_AXIS_Y); |
| bits.clearBit(AMOTION_EVENT_AXIS_PRESSURE); |
| bits.clearBit(AMOTION_EVENT_AXIS_SIZE); |
| bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MAJOR); |
| bits.clearBit(AMOTION_EVENT_AXIS_TOUCH_MINOR); |
| bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MAJOR); |
| bits.clearBit(AMOTION_EVENT_AXIS_TOOL_MINOR); |
| bits.clearBit(AMOTION_EVENT_AXIS_ORIENTATION); |
| if (!bits.isEmpty()) { |
| int packedAxesCount = bits.count(); |
| float[] outValuesArray = obtainPackedAxisValuesArray(packedAxesCount, outPointerCoordsObj); |
| float[] outValues = outValuesArray; |
| int index = 0; |
| do { |
| int axis = bits.clearFirstMarkedBit(); |
| outBits |= NativeBitSet64.valueForBit(axis); |
| outValues[index++] = rawPointerCoords.getAxisValue(axis); |
| } while (!bits.isEmpty()); |
| } |
| ReflectionHelpers.setField(outPointerCoordsObj, "mPackedAxisBits", outBits); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeInitialize( |
| int 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[] pointerIds, |
| PointerCoords[] pointerCoords) { |
| return (int) |
| nativeInitialize( |
| (long) nativePtr, |
| deviceId, |
| source, |
| action, |
| flags, |
| edgeFlags, |
| metaState, |
| buttonState, |
| xOffset, |
| yOffset, |
| xPrecision, |
| yPrecision, |
| downTimeNanos, |
| eventTimeNanos, |
| pointerCount, |
| pointerIds, |
| pointerCoords); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP, maxSdk = P) |
| @HiddenApi |
| protected static long 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) { |
| |
| validatePointerCount(pointerCount); |
| validatePointerPropertiesArray(pointerPropertiesObjArray, pointerCount); |
| validatePointerCoordsObjArray(pointerCoordsObjArray, pointerCount); |
| |
| NativeInput.MotionEvent event; |
| if (nativePtr > 0) { |
| event = nativeMotionEventRegistry.getNativeObject(nativePtr); |
| } else { |
| event = new NativeInput.MotionEvent(); |
| nativePtr = nativeMotionEventRegistry.register(event); |
| } |
| |
| NativeInput.PointerCoords[] rawPointerCoords = new NativeInput.PointerCoords[pointerCount]; |
| for (int i = 0; i < pointerCount; i++) { |
| PointerCoords pointerCoordsObj = pointerCoordsObjArray[i]; |
| checkNotNull(pointerCoordsObj); |
| rawPointerCoords[i] = pointerCoordsToNative(pointerCoordsObj, xOffset, yOffset); |
| } |
| |
| event.initialize( |
| deviceId, |
| source, |
| action, |
| 0, |
| flags, |
| edgeFlags, |
| metaState, |
| buttonState, |
| xOffset, |
| yOffset, |
| xPrecision, |
| yPrecision, |
| downTimeNanos, |
| eventTimeNanos, |
| pointerCount, |
| pointerPropertiesObjArray, |
| rawPointerCoords); |
| return nativePtr; |
| } |
| |
| // BEGIN-INTERNAL |
| // TODO(brettchabot): properly handle displayId |
| @Implementation(minSdk = android.os.Build.VERSION_CODES.Q) |
| @HiddenApi |
| protected static long nativeInitialize( |
| long nativePtr, |
| int deviceId, |
| int source, |
| int displayId, |
| 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) { |
| return |
| nativeInitialize( |
| nativePtr, |
| deviceId, |
| source, |
| action, |
| flags, |
| edgeFlags, |
| metaState, |
| buttonState, |
| xOffset, |
| yOffset, |
| xPrecision, |
| yPrecision, |
| downTimeNanos, |
| eventTimeNanos, |
| pointerCount, |
| pointerPropertiesObjArray, |
| pointerCoordsObjArray); |
| } |
| |
| @Implementation(minSdk = android.os.Build.VERSION_CODES.R) |
| @HiddenApi |
| protected static long 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[] pointerPropertiesObjArray, |
| PointerCoords[] pointerCoordsObjArray) { |
| return |
| nativeInitialize( |
| nativePtr, |
| deviceId, |
| source, |
| action, |
| flags, |
| edgeFlags, |
| metaState, |
| buttonState, |
| xOffset, |
| yOffset, |
| xPrecision, |
| yPrecision, |
| downTimeNanos, |
| eventTimeNanos, |
| pointerCount, |
| pointerPropertiesObjArray, |
| pointerCoordsObjArray); |
| } |
| |
| // END-INTERNAL |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeDispose(int nativePtr) { |
| nativeDispose((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeDispose(long nativePtr) { |
| nativeMotionEventRegistry.unregister(nativePtr); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeAddBatch( |
| int nativePtr, long eventTimeNanos, PointerCoords[] pointerCoordsObjArray, int metaState) { |
| nativeAddBatch((long) nativePtr, eventTimeNanos, pointerCoordsObjArray, metaState); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeAddBatch( |
| long nativePtr, long eventTimeNanos, PointerCoords[] pointerCoordsObjArray, int metaState) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| int pointerCount = event.getPointerCount(); |
| validatePointerCoordsObjArray(pointerCoordsObjArray, pointerCount); |
| NativeInput.PointerCoords[] rawPointerCoords = new NativeInput.PointerCoords[pointerCount]; |
| for (int i = 0; i < pointerCount; i++) { |
| PointerCoords pointerCoordsObj = pointerCoordsObjArray[i]; |
| checkNotNull(pointerCoordsObj); |
| rawPointerCoords[i] = |
| pointerCoordsToNative(pointerCoordsObj, event.getXOffset(), event.getYOffset()); |
| } |
| event.addSample(eventTimeNanos, rawPointerCoords); |
| event.setMetaState(event.getMetaState() | metaState); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeGetPointerCoords( |
| int nativePtr, int pointerIndex, int historyPos, PointerCoords outPointerCoordsObj) { |
| nativeGetPointerCoords((long) nativePtr, pointerIndex, historyPos, outPointerCoordsObj); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeGetPointerCoords( |
| long nativePtr, int pointerIndex, int historyPos, PointerCoords outPointerCoordsObj) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| int pointerCount = event.getPointerCount(); |
| validatePointerIndex(pointerIndex, pointerCount); |
| validatePointerCoords(outPointerCoordsObj); |
| |
| NativeInput.PointerCoords rawPointerCoords; |
| if (historyPos == HISTORY_CURRENT) { |
| rawPointerCoords = event.getRawPointerCoords(pointerIndex); |
| } else { |
| int historySize = event.getHistorySize(); |
| validateHistoryPos(historyPos, historySize); |
| rawPointerCoords = event.getHistoricalRawPointerCoords(pointerIndex, historyPos); |
| } |
| pointerCoordsFromNative( |
| rawPointerCoords, event.getXOffset(), event.getYOffset(), outPointerCoordsObj); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeGetPointerProperties( |
| int nativePtr, int pointerIndex, PointerProperties outPointerPropertiesObj) { |
| nativeGetPointerProperties((long) nativePtr, pointerIndex, outPointerPropertiesObj); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeGetPointerProperties( |
| long nativePtr, int pointerIndex, PointerProperties outPointerPropertiesObj) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| int pointerCount = event.getPointerCount(); |
| validatePointerIndex(pointerIndex, pointerCount); |
| validatePointerProperties(outPointerPropertiesObj); |
| |
| PointerProperties pointerProperties = event.getPointerProperties(pointerIndex); |
| // pointerPropertiesFromNative(env, pointerProperties, outPointerPropertiesObj); |
| outPointerPropertiesObj.copyFrom(pointerProperties); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeReadFromParcel(int nativePtr, Parcel parcelObj) { |
| return (int) nativeReadFromParcel((long) nativePtr, parcelObj); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static long nativeReadFromParcel(long nativePtr, Parcel parcelObj) { |
| NativeInput.MotionEvent event; |
| if (nativePtr == 0) { |
| event = new NativeInput.MotionEvent(); |
| nativePtr = nativeMotionEventRegistry.register(event); |
| } else { |
| event = nativeMotionEventRegistry.getNativeObject(nativePtr); |
| } |
| boolean status = event.readFromParcel(parcelObj); |
| if (!status) { |
| if (nativePtr > 0) { |
| nativeMotionEventRegistry.unregister(nativePtr); |
| } |
| throw new RuntimeException("Failed to read MotionEvent parcel."); |
| } |
| return nativePtr; |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeWriteToParcel(int nativePtr, Parcel parcel) { |
| nativeWriteToParcel((long) nativePtr, parcel); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeWriteToParcel(long nativePtr, Parcel parcel) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| if (!event.writeToParcel(parcel)) { |
| throw new RuntimeException("Failed to write MotionEvent parcel."); |
| } |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static String nativeAxisToString(int axis) { |
| // The native code just mirrors the AXIS_* constants defined in MotionEvent.java. |
| // Look up the field value by reflection to future proof this method |
| for (Field field : MotionEvent.class.getDeclaredFields()) { |
| int modifiers = field.getModifiers(); |
| try { |
| if (Modifier.isStatic(modifiers) |
| && Modifier.isPublic(modifiers) |
| && field.getName().startsWith("AXIS_") |
| && field.getInt(null) == axis) { |
| // return the field name stripping off the "AXIS_" prefix |
| return field.getName().substring(5); |
| } |
| } catch (IllegalAccessException e) { |
| // ignore |
| } |
| } |
| return null; |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeAxisFromString(String label) { |
| // The native code just mirrors the AXIS_* constants defined in MotionEvent.java. Look up |
| // the field value by reflection |
| try { |
| Field constantField = MotionEvent.class.getDeclaredField("AXIS_" + label); |
| return constantField.getInt(null); |
| } catch (NoSuchFieldException | IllegalAccessException e) { |
| return 0; |
| } |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetPointerId(int nativePtr, int pointerIndex) { |
| return nativeGetPointerId((long) nativePtr, pointerIndex); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetPointerId(long nativePtr, int pointerIndex) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| int pointerCount = event.getPointerCount(); |
| validatePointerIndex(pointerIndex, pointerCount); |
| return event.getPointerId(pointerIndex); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetToolType(int nativePtr, int pointerIndex) { |
| return nativeGetToolType((long) nativePtr, pointerIndex); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetToolType(long nativePtr, int pointerIndex) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| int pointerCount = event.getPointerCount(); |
| validatePointerIndex(pointerIndex, pointerCount); |
| return event.getToolType(pointerIndex); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static long nativeGetEventTimeNanos(int nativePtr, int historyPos) { |
| return nativeGetEventTimeNanos((long) nativePtr, historyPos); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static long nativeGetEventTimeNanos(long nativePtr, int historyPos) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| if (historyPos == HISTORY_CURRENT) { |
| return event.getEventTime(); |
| } else { |
| int historySize = event.getHistorySize(); |
| validateHistoryPos(historyPos, historySize); |
| return event.getHistoricalEventTime(historyPos); |
| } |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static float nativeGetRawAxisValue( |
| int nativePtr, int axis, int pointerIndex, int historyPos) { |
| return nativeGetRawAxisValue((long) nativePtr, axis, pointerIndex, historyPos); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static float nativeGetRawAxisValue( |
| long nativePtr, int axis, int pointerIndex, int historyPos) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| int pointerCount = event.getPointerCount(); |
| validatePointerIndex(pointerIndex, pointerCount); |
| |
| if (historyPos == HISTORY_CURRENT) { |
| return event.getRawAxisValue(axis, pointerIndex); |
| } else { |
| int historySize = event.getHistorySize(); |
| validateHistoryPos(historyPos, historySize); |
| return event.getHistoricalRawAxisValue(axis, pointerIndex, historyPos); |
| } |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static float nativeGetAxisValue( |
| int nativePtr, int axis, int pointerIndex, int historyPos) { |
| return nativeGetAxisValue((long) nativePtr, axis, pointerIndex, historyPos); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static float nativeGetAxisValue( |
| long nativePtr, int axis, int pointerIndex, int historyPos) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| int pointerCount = event.getPointerCount(); |
| validatePointerIndex(pointerIndex, pointerCount); |
| |
| if (historyPos == HISTORY_CURRENT) { |
| return event.getAxisValue(axis, pointerIndex); |
| } else { |
| int historySize = event.getHistorySize(); |
| validateHistoryPos(historyPos, historySize); |
| return event.getHistoricalAxisValue(axis, pointerIndex, historyPos); |
| } |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeCopy(int destNativePtr, int sourceNativePtr, boolean keepHistory) { |
| return (int) nativeCopy((long) destNativePtr, (long) sourceNativePtr, keepHistory); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static long nativeCopy(long destNativePtr, long sourceNativePtr, boolean keepHistory) { |
| NativeInput.MotionEvent destEvent = nativeMotionEventRegistry.peekNativeObject(destNativePtr); |
| if (destEvent == null) { |
| destEvent = new NativeInput.MotionEvent(); |
| destNativePtr = nativeMotionEventRegistry.register(destEvent); |
| } |
| NativeInput.MotionEvent sourceEvent = getNativeMotionEvent(sourceNativePtr); |
| destEvent.copyFrom(sourceEvent, keepHistory); |
| return destNativePtr; |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetDeviceId(int nativePtr) { |
| return nativeGetDeviceId((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetDeviceId(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getDeviceId(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetSource(int nativePtr) { |
| return nativeGetSource((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetSource(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getSource(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeSetSource(int nativePtr, int source) { |
| nativeSetSource((long) nativePtr, source); |
| return 0; |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| @SuppressWarnings("robolectric.ShadowReturnTypeMismatch") |
| protected static void nativeSetSource(long nativePtr, int source) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.setSource(source); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetAction(int nativePtr) { |
| return nativeGetAction((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetAction(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getAction(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeSetAction(int nativePtr, int action) { |
| nativeSetAction((long) nativePtr, action); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeSetAction(long nativePtr, int action) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.setAction(action); |
| } |
| |
| @Implementation(minSdk = M) |
| @HiddenApi |
| protected static int nativeGetActionButton(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getActionButton(); |
| } |
| |
| @Implementation(minSdk = M) |
| @HiddenApi |
| protected static void nativeSetActionButton(long nativePtr, int button) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.setActionButton(button); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static boolean nativeIsTouchEvent(int nativePtr) { |
| return nativeIsTouchEvent((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static boolean nativeIsTouchEvent(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.isTouchEvent(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetFlags(int nativePtr) { |
| return nativeGetFlags((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetFlags(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getFlags(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeSetFlags(int nativePtr, int flags) { |
| nativeSetFlags((long) nativePtr, flags); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeSetFlags(long nativePtr, int flags) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.setFlags(flags); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetEdgeFlags(int nativePtr) { |
| return nativeGetEdgeFlags((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetEdgeFlags(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getEdgeFlags(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeSetEdgeFlags(int nativePtr, int edgeFlags) { |
| nativeSetEdgeFlags((long) nativePtr, edgeFlags); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeSetEdgeFlags(long nativePtr, int edgeFlags) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.setEdgeFlags(edgeFlags); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetMetaState(int nativePtr) { |
| return nativeGetMetaState((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetMetaState(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getMetaState(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetButtonState(int nativePtr) { |
| return nativeGetButtonState((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetButtonState(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getButtonState(); |
| } |
| |
| @Implementation(minSdk = M) |
| @HiddenApi |
| protected static void nativeSetButtonState(long nativePtr, int buttonState) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.setButtonState(buttonState); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeOffsetLocation(int nativePtr, float deltaX, float deltaY) { |
| nativeOffsetLocation((long) nativePtr, deltaX, deltaY); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.offsetLocation(deltaX, deltaY); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static float nativeGetXOffset(int nativePtr) { |
| return nativeGetXOffset((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static float nativeGetXOffset(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getXOffset(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static float nativeGetYOffset(int nativePtr) { |
| return nativeGetYOffset((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static float nativeGetYOffset(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getYOffset(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static float nativeGetXPrecision(int nativePtr) { |
| return nativeGetXPrecision((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static float nativeGetXPrecision(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getXPrecision(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static float nativeGetYPrecision(int nativePtr) { |
| return nativeGetYPrecision((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static float nativeGetYPrecision(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getYPrecision(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static long nativeGetDownTimeNanos(int nativePtr) { |
| return nativeGetDownTimeNanos((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static long nativeGetDownTimeNanos(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getDownTime(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeSetDownTimeNanos(int nativePtr, long downTimeNanos) { |
| nativeSetDownTimeNanos((long) nativePtr, downTimeNanos); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeSetDownTimeNanos(long nativePtr, long downTimeNanos) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.setDownTime(downTimeNanos); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetPointerCount(int nativePtr) { |
| return nativeGetPointerCount((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetPointerCount(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getPointerCount(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeFindPointerIndex(int nativePtr, int pointerId) { |
| return nativeFindPointerIndex((long) nativePtr, pointerId); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeFindPointerIndex(long nativePtr, int pointerId) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.findPointerIndex(pointerId); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static int nativeGetHistorySize(int nativePtr) { |
| return nativeGetHistorySize((long) nativePtr); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static int nativeGetHistorySize(long nativePtr) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| return event.getHistorySize(); |
| } |
| |
| @Implementation(maxSdk = KITKAT_WATCH) |
| @HiddenApi |
| protected static void nativeScale(int nativePtr, float scale) { |
| nativeScale((long) nativePtr, scale); |
| } |
| |
| @Implementation(minSdk = LOLLIPOP) |
| @HiddenApi |
| protected static void nativeScale(long nativePtr, float scale) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(nativePtr); |
| event.scale(scale); |
| } |
| |
| private static NativeInput.MotionEvent getNativeMotionEvent(long nativePtr) { |
| // check that MotionEvent was initialized properly. This can occur if MotionEvent was mocked |
| checkState( |
| nativePtr > 0, |
| "MotionEvent has not been initialized. " |
| + "Ensure MotionEvent.obtain was used to create it, instead of creating it directly " |
| + "or via a Mocking framework"); |
| |
| return nativeMotionEventRegistry.getNativeObject(nativePtr); |
| } |
| |
| // shadow this directly as opposed to nativeTransform because need access to ShadowMatrix |
| @Implementation |
| protected final void transform(Matrix matrix) { |
| checkNotNull(matrix); |
| NativeInput.MotionEvent event = getNativeMotionEvent(); |
| ShadowMatrix shadowMatrix = Shadow.extract(matrix); |
| |
| float[] m = new float[9]; |
| shadowMatrix.getValues(m); |
| event.transform(m); |
| } |
| |
| private NativeInput.MotionEvent getNativeMotionEvent() { |
| long nativePtr; |
| if (RuntimeEnvironment.getApiLevel() <= KITKAT_WATCH) { |
| Integer nativePtrInt = ReflectionHelpers.getField(realMotionEvent, "mNativePtr"); |
| nativePtr = nativePtrInt.longValue(); |
| } else { |
| nativePtr = ReflectionHelpers.getField(realMotionEvent, "mNativePtr"); |
| } |
| return nativeMotionEventRegistry.getNativeObject(nativePtr); |
| } |
| |
| // Testing API methods |
| |
| /** |
| * @deprecated use {@link MotionEvent#obtain} or {@link |
| * androidx.test.core.view.MotionEventBuilder} to create a MotionEvent with desired data. |
| */ |
| @Deprecated |
| public MotionEvent setPointer2(float pointer1X, float pointer1Y) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(); |
| List<NativeInput.PointerCoords> pointerCoords = event.getSamplePointerCoords(); |
| List<PointerProperties> pointerProperties = event.getPointerProperties(); |
| ensureTwoPointers(pointerCoords, pointerProperties); |
| |
| pointerCoords.get(1).setAxisValue(AMOTION_EVENT_AXIS_X, pointer1X); |
| pointerCoords.get(1).setAxisValue(AMOTION_EVENT_AXIS_Y, pointer1Y); |
| return realMotionEvent; |
| } |
| |
| private static void ensureTwoPointers( |
| List<NativeInput.PointerCoords> pointerCoords, List<PointerProperties> pointerProperties) { |
| if (pointerCoords.size() < 2) { |
| pointerCoords.add(new NativeInput.PointerCoords()); |
| } |
| if (pointerProperties.size() < 2) { |
| pointerProperties.add(new PointerProperties()); |
| } |
| } |
| |
| /** |
| * @deprecated use {@link MotionEvent#obtain} or {@link |
| * androidx.test.core.view.MotionEventBuilder#setPointerAction(int, int)} to create a |
| * MotionEvent with desired data. |
| */ |
| @Deprecated |
| public void setPointerIndex(int pointerIndex) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(); |
| // pointer index is stored in upper two bytes of action |
| event.setAction( |
| event.getAction() | ((pointerIndex & 0xff) << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); |
| } |
| |
| /** |
| * @deprecated use {@link MotionEvent#obtain} or {@link MotionEventBuilder} to create a |
| * MotionEvent with desired data |
| */ |
| @Deprecated |
| public void setPointerIds(int index0PointerId, int index1PointerId) { |
| NativeInput.MotionEvent event = getNativeMotionEvent(); |
| List<NativeInput.PointerCoords> pointerCoords = event.getSamplePointerCoords(); |
| List<PointerProperties> pointerProperties = event.getPointerProperties(); |
| ensureTwoPointers(pointerCoords, pointerProperties); |
| |
| pointerProperties.get(0).id = index0PointerId; |
| pointerProperties.get(1).id = index1PointerId; |
| } |
| } |