/**
* 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);
}
}