diff options
3 files changed, 166 insertions, 14 deletions
diff --git a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java index bf626e4a9..e21545d9f 100755 --- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java +++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java @@ -100,8 +100,10 @@ import org.robolectric.shadows.ShadowView; import org.robolectric.util.Logger; import org.robolectric.util.PerfStatsCollector; import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.Scheduler; import org.robolectric.util.TempDirectory; +import org.robolectric.versioning.AndroidVersions; import org.robolectric.versioning.AndroidVersions.V; @SuppressLint("NewApi") @@ -399,9 +401,18 @@ public class AndroidTestEnvironment implements TestEnvironment { populateAssetPaths(appResources.getAssets(), appManifest); } - // circument the 'No Compatibility callbacks set!' log. See #8509 - if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.R) { - AppCompatCallbacks.install(new long[0]); + // Circumvent the 'No Compatibility callbacks set!' log. See #8509 + if (apiLevel >= AndroidVersions.V.SDK_INT) { + // Adds loggableChanges parameter. + ReflectionHelpers.callStaticMethod( + AppCompatCallbacks.class, + "install", + ClassParameter.from(long[].class, new long[0]), + ClassParameter.from(long[].class, new long[0])); + } else if (apiLevel >= AndroidVersions.R.SDK_INT) { + // Invoke the previous version. + ReflectionHelpers.callStaticMethod( + AppCompatCallbacks.class, "install", ClassParameter.from(long[].class, new long[0])); } PerfStatsCollector.getInstance() diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/NativeInput.java b/shadows/framework/src/main/java/org/robolectric/shadows/NativeInput.java index d136be83d..cbda24865 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/NativeInput.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/NativeInput.java @@ -3,6 +3,7 @@ package org.robolectric.shadows; import static com.google.common.base.Preconditions.checkState; import static org.robolectric.shadows.NativeAndroidInput.AINPUT_EVENT_TYPE_MOTION; import static org.robolectric.shadows.NativeAndroidInput.AINPUT_SOURCE_CLASS_POINTER; +import static org.robolectric.shadows.NativeAndroidInput.AKEY_EVENT_FLAG_CANCELED; import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_CANCEL; import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_DOWN; import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_MASK; @@ -28,6 +29,7 @@ import android.view.MotionEvent.PointerProperties; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import org.robolectric.res.android.Ref; /** @@ -64,11 +66,11 @@ public class NativeInput { */ static class AInputEvent {} - /* + /** * Pointer coordinate data. * - * Deviates from original platform implementation to store axises in simple SparseArray as opposed - * to complicated bitset + array arrangement. + * <p>Deviates from original platform implementation to store axises in simple SparseArray as + * opposed to complicated bitset + array arrangement. */ static class PointerCoords { @@ -272,9 +274,7 @@ public class NativeInput { // nsecs_t mEventTime; } - /* - * Motion events. - */ + /** Motion events. */ static class MotionEvent extends InputEvent { // constants copied from android bionic/libc/include/math.h @@ -284,6 +284,18 @@ public class NativeInput { @SuppressWarnings("FloatingPointLiteralPrecision") private static final double M_PI_2 = 1.57079632679489661923f; /* pi/2 */ + public static final int ACTION_MASK = 0xff; + public static final int ACTION_DOWN = 0; + public static final int ACTION_UP = 1; + public static final int ACTION_MOVE = 2; + public static final int ACTION_CANCEL = 3; + public static final int ACTION_POINTER_DOWN = 5; + public static final int ACTION_POINTER_UP = 6; + private static final int HISTORY_CURRENT = -0x80000000; + public static final int FLAG_CANCELED = 0x20; + public static final int ACTION_POINTER_INDEX_MASK = 0xff00; + public static final int ACTION_POINTER_INDEX_SHIFT = 8; + private int mAction; private int mActionButton; private int mFlags; @@ -540,6 +552,115 @@ public class NativeInput { return getHistoricalAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex); } + private android.view.MotionEvent.PointerCoords[] getNativePointerCoords() { + android.view.MotionEvent.PointerCoords[] nativePointerCoords = + new android.view.MotionEvent.PointerCoords[mSamplePointerCoords.size()]; + for (int i = 0; i < mSamplePointerCoords.size(); i++) { + android.view.MotionEvent.PointerCoords newPc = new android.view.MotionEvent.PointerCoords(); + PointerCoords pc = mSamplePointerCoords.get(i); + newPc.x = pc.getX(); + newPc.y = pc.getY(); + newPc.setAxisValue(AMOTION_EVENT_AXIS_X, pc.getX()); + newPc.setAxisValue(AMOTION_EVENT_AXIS_Y, pc.getY()); + nativePointerCoords[i] = newPc; + } + return nativePointerCoords; + } + + private int resolveActionForSplitMotionEvent( + int action, + int flags, + PointerProperties[] pointerProperties, + PointerProperties[] splitPointerProperties) { + int maskedAction = getActionMasked(); + if (maskedAction != AMOTION_EVENT_ACTION_POINTER_DOWN + && maskedAction != AMOTION_EVENT_ACTION_POINTER_UP) { + // The action is unaffected by splitting this motion event. + return action; + } + + int actionIndex = getActionIndex(); + + int affectedPointerId = pointerProperties[actionIndex].id; + Optional<Integer> splitActionIndex = Optional.empty(); + for (int i = 0; i < splitPointerProperties.length; i++) { + if (affectedPointerId == splitPointerProperties[i].id) { + splitActionIndex = Optional.of(i); + break; + } + } + if (!splitActionIndex.isPresent()) { + // The affected pointer is not part of the split motion event. + return AMOTION_EVENT_ACTION_MOVE; + } + + if (splitPointerProperties.length > 1) { + return maskedAction | (splitActionIndex.get() << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + + if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + return ((flags & AKEY_EVENT_FLAG_CANCELED) != 0) + ? AMOTION_EVENT_ACTION_CANCEL + : AMOTION_EVENT_ACTION_UP; + } + return AMOTION_EVENT_ACTION_DOWN; + } + + public android.view.MotionEvent nativeSplit(int idBits) { + final int pointerCount = getPointerCount(); + List<PointerProperties> pointerProperties = new ArrayList<>(mPointerProperties); + final PointerProperties[] pp = pointerProperties.toArray(new PointerProperties[pointerCount]); + final android.view.MotionEvent.PointerCoords[] pc = getNativePointerCoords(); + + List<PointerProperties> splitPointerProperties = new ArrayList<>(); + List<android.view.MotionEvent.PointerCoords> splitPointerCoords = new ArrayList<>(); + + // Split the matching ids out for the new MotionEvent. + for (int i = 0; i < pointerCount; i++) { + final int idBit = 1 << pp[i].id; + if ((idBit & idBits) != 0) { + splitPointerProperties.add(pp[i]); + } + } + for (int i = 0; i < pc.length; i++) { + final int idBit = 1 << pp[i % pointerCount].id; + if ((idBit & idBits) != 0) { + splitPointerCoords.add(pc[i]); + } + } + + // Convert them to arrays + PointerProperties[] splitPointerPropertiesArray = + new PointerProperties[splitPointerProperties.size()]; + splitPointerProperties.toArray(splitPointerPropertiesArray); + + android.view.MotionEvent.PointerCoords[] splitPointerCoordsArray = + new android.view.MotionEvent.PointerCoords[splitPointerCoords.size()]; + splitPointerCoords.toArray(splitPointerCoordsArray); + + int splitAction = + resolveActionForSplitMotionEvent( + getAction(), getFlags(), pp, splitPointerPropertiesArray); + + android.view.MotionEvent newEvent = + android.view.MotionEvent.obtain( + getDownTime(), + getEventTime(), + splitAction, + splitPointerProperties.size(), + splitPointerPropertiesArray, + splitPointerCoordsArray, + getMetaState(), + getButtonState(), + getXPrecision(), + getYPrecision(), + getDeviceId(), + getEdgeFlags(), + getSource(), + getFlags()); + return newEvent; + } + public int findPointerIndex(int pointerId) { int pointerCount = mPointerProperties.size(); for (int i = 0; i < pointerCount; i++) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java index dc5345ace..371cb6d9e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java @@ -4,6 +4,7 @@ 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 android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 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; @@ -33,6 +34,7 @@ import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; import org.robolectric.res.android.NativeObjRegistry; import org.robolectric.util.ReflectionHelpers; +import org.robolectric.versioning.AndroidVersions.V; /** * Shadow of MotionEvent. @@ -51,7 +53,7 @@ import org.robolectric.util.ReflectionHelpers; * the MotionEvent.obtain methods or via MotionEventBuilder. */ @SuppressWarnings({"UnusedDeclaration"}) -@Implements(MotionEvent.class) +@Implements(value = MotionEvent.class) public class ShadowMotionEvent extends ShadowInputEvent { private static NativeObjRegistry<NativeInput.MotionEvent> nativeMotionEventRegistry = @@ -814,7 +816,7 @@ public class ShadowMotionEvent extends ShadowInputEvent { return nativeGetXOffset((long) nativePtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = UPSIDE_DOWN_CAKE) @HiddenApi @InDevelopment protected static float nativeGetXOffset(long nativePtr) { @@ -828,7 +830,7 @@ public class ShadowMotionEvent extends ShadowInputEvent { return nativeGetYOffset((long) nativePtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = UPSIDE_DOWN_CAKE) @HiddenApi @InDevelopment protected static float nativeGetYOffset(long nativePtr) { @@ -836,6 +838,24 @@ public class ShadowMotionEvent extends ShadowInputEvent { return event.getYOffset(); } + @Implementation(minSdk = V.SDK_INT) + protected final MotionEvent split(int idBits) { + NativeInput.MotionEvent event = getNativeMotionEvent(); + return event.nativeSplit(idBits); + } + + @Implementation(minSdk = V.SDK_INT) + @HiddenApi + protected static float nativeGetRawXOffset(long nativePtr) { + return getNativeMotionEvent(nativePtr).getXOffset(); + } + + @Implementation(minSdk = V.SDK_INT) + @HiddenApi + protected static float nativeGetRawYOffset(long nativePtr) { + return getNativeMotionEvent(nativePtr).getYOffset(); + } + @Implementation(maxSdk = KITKAT_WATCH) @HiddenApi protected static float nativeGetXPrecision(int nativePtr) { @@ -940,7 +960,7 @@ public class ShadowMotionEvent extends ShadowInputEvent { event.scale(scale); } - private static NativeInput.MotionEvent getNativeMotionEvent(long nativePtr) { + protected static NativeInput.MotionEvent getNativeMotionEvent(long nativePtr) { // check that MotionEvent was initialized properly. This can occur if MotionEvent was mocked checkState( nativePtr > 0, @@ -961,7 +981,7 @@ public class ShadowMotionEvent extends ShadowInputEvent { event.transform(m); } - private NativeInput.MotionEvent getNativeMotionEvent() { + protected NativeInput.MotionEvent getNativeMotionEvent() { long nativePtr; if (RuntimeEnvironment.getApiLevel() <= KITKAT_WATCH) { Integer nativePtrInt = ReflectionHelpers.getField(realMotionEvent, "mNativePtr"); |