/** * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.testing; import android.os.SystemClock; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; /** * Provides methods for generating touch events and dispatching them directly to a given view. * Events scenarios are based on {@link android.test.TouchUtils} but they get gets dispatched * directly through the view hierarchy using {@link View#dispatchTouchEvent} method instead of * using instrumentation API. * <p> * All the events for a gesture are dispatched immediately which makes tests run very fast. * The eventTime for each event is still set correctly. Android's gesture recognizers check * eventTime in order to figure out gesture speed, and therefore scroll vs fling is recognized. */ public class SingleTouchGestureGenerator { private static final long DEFAULT_DELAY_MS = 20; private View mDispatcherView; private IdleWaiter mIdleWaiter; private long mLastDownTime; private long mEventTime; private float mLastX; private float mLastY; private ViewConfiguration mViewConfig; public SingleTouchGestureGenerator(View view, IdleWaiter idleWaiter) { mDispatcherView = view; mIdleWaiter = idleWaiter; mViewConfig = ViewConfiguration.get(view.getContext()); } private SingleTouchGestureGenerator dispatchEvent( final int action, final float x, final float y, long eventTime) { mEventTime = eventTime; if (action == MotionEvent.ACTION_DOWN) { mLastDownTime = eventTime; } mLastX = x; mLastY = y; mDispatcherView.post( new Runnable() { @Override public void run() { MotionEvent event = MotionEvent.obtain(mLastDownTime, mEventTime, action, x, y, 0); mDispatcherView.dispatchTouchEvent(event); event.recycle(); } }); mIdleWaiter.waitForBridgeAndUIIdle(); return this; } private float getViewCenterX(View view) { int[] xy = new int[2]; view.getLocationOnScreen(xy); int viewWidth = view.getWidth(); return xy[0] + (viewWidth / 2.0f); } private float getViewCenterY(View view) { int[] xy = new int[2]; view.getLocationOnScreen(xy); int viewHeight = view.getHeight(); return xy[1] + (viewHeight / 2.0f); } public SingleTouchGestureGenerator startGesture(float x, float y) { return dispatchEvent(MotionEvent.ACTION_DOWN, x, y, SystemClock.uptimeMillis()); } public SingleTouchGestureGenerator startGesture(View view) { return startGesture(getViewCenterX(view), getViewCenterY(view)); } private SingleTouchGestureGenerator dispatchDelayedEvent( int action, float x, float y, long delay) { return dispatchEvent(action, x, y, mEventTime + delay); } public SingleTouchGestureGenerator endGesture(float x, float y, long delay) { return dispatchDelayedEvent(MotionEvent.ACTION_UP, x, y, delay); } public SingleTouchGestureGenerator endGesture(float x, float y) { return endGesture(x, y, DEFAULT_DELAY_MS); } public SingleTouchGestureGenerator endGesture() { return endGesture(mLastX, mLastY); } public SingleTouchGestureGenerator moveGesture(float x, float y, long delay) { return dispatchDelayedEvent(MotionEvent.ACTION_MOVE, x, y, delay); } public SingleTouchGestureGenerator moveBy(float dx, float dy, long delay) { return moveGesture(mLastX + dx, mLastY + dy, delay); } public SingleTouchGestureGenerator moveBy(float dx, float dy) { return moveBy(dx, dy, DEFAULT_DELAY_MS); } public SingleTouchGestureGenerator clickViewAt(float x, float y) { float touchSlop = mViewConfig.getScaledTouchSlop(); return startGesture(x, y).moveBy(touchSlop / 2.0f, touchSlop / 2.0f).endGesture(); } public SingleTouchGestureGenerator drag( float fromX, float fromY, float toX, float toY, int stepCount, long totalDelay) { float xStep = (toX - fromX) / stepCount; float yStep = (toY - fromY) / stepCount; float x = fromX; float y = fromY; for (int i = 0; i < stepCount; i++) { x += xStep; y += yStep; moveGesture(x, y, totalDelay / stepCount); } return this; } public SingleTouchGestureGenerator dragTo(float toX, float toY, int stepCount, long totalDelay) { return drag(mLastX, mLastY, toX, toY, stepCount, totalDelay); } public SingleTouchGestureGenerator dragTo(View view, int stepCount, long totalDelay) { return dragTo(getViewCenterX(view), getViewCenterY(view), stepCount, totalDelay); } public SingleTouchGestureGenerator dragTo(View view, int stepCount) { return dragTo(view, stepCount, stepCount * DEFAULT_DELAY_MS); } }