/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.test;

import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.navigationBars;

import android.app.Activity;
import android.app.Instrumentation;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.Size;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;

/**
 * Reusable methods for generating touch events. These methods can be used with
 * InstrumentationTestCase or ActivityInstrumentationTestCase2 to simulate user interaction with
 * the application through a touch screen.
 *
 * @deprecated Use
 * <a href="{@docRoot}training/testing/ui-testing/espresso-testing.html">Espresso UI testing
 * framework</a> instead. New tests should be written using the
 * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
 */
@Deprecated
public class TouchUtils {

    /**
     * Simulate touching in the center of the screen and dragging one quarter of the way down
     * @param test The test case that is being run
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void dragQuarterScreenDown(ActivityInstrumentationTestCase test) {
        dragQuarterScreenDown(test, test.getActivity());
    }

    /**
     * Simulate touching in the center of the screen and dragging one quarter of the way down
     * @param test The test case that is being run
     * @param activity The activity that is in the foreground of the test case
     */
    public static void dragQuarterScreenDown(InstrumentationTestCase test, Activity activity) {
        WindowManager wm = activity.getWindowManager();
        final Size size = getSizeExcludingNavigationBarAndCutout(wm.getCurrentWindowMetrics());

        final float x = size.getWidth() / 2.0f;
        final float fromY = size.getHeight() * 0.5f;
        final float toY = size.getHeight() * 0.75f;

        drag(test, x, x, fromY, toY, 4);
    }

    /**
     * Simulate touching in the center of the screen and dragging one quarter of the way up
     * @param test The test case that is being run
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void dragQuarterScreenUp(ActivityInstrumentationTestCase test) {
        dragQuarterScreenUp(test, test.getActivity());
    }

    /**
     * Simulate touching in the center of the screen and dragging one quarter of the way up
     * @param test The test case that is being run
     * @param activity The activity that is in the foreground of the test case
     */
    public static void dragQuarterScreenUp(InstrumentationTestCase test, Activity activity) {
        WindowManager wm = activity.getWindowManager();
        final Size size = getSizeExcludingNavigationBarAndCutout(wm.getCurrentWindowMetrics());

        final float x = size.getWidth() / 2.0f;
        final float fromY = size.getHeight() * 0.5f;
        final float toY = size.getHeight() * 0.25f;

        drag(test, x, x, fromY, toY, 4);
    }

    private static Size getSizeExcludingNavigationBarAndCutout(WindowMetrics windowMetrics) {
        WindowInsets windowInsets = windowMetrics.getWindowInsets();
        final Insets insetsWithCutout = windowInsets
                .getInsetsIgnoringVisibility(navigationBars() | displayCutout());
        final int insetsWidth = insetsWithCutout.left + insetsWithCutout.right;
        final int insetsHeight = insetsWithCutout.top + insetsWithCutout.bottom;

        Rect bounds = windowMetrics.getBounds();
        return new Size(bounds.width() - insetsWidth, bounds.height() - insetsHeight);
    }

    /**
     * Scroll a ViewGroup to the bottom by repeatedly calling
     * {@link #dragQuarterScreenUp(InstrumentationTestCase, Activity)}
     *
     * @param test The test case that is being run
     * @param v The ViewGroup that should be dragged
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void scrollToBottom(ActivityInstrumentationTestCase test, ViewGroup v) {
        scrollToBottom(test, test.getActivity(), v);
    }

    /**
     * Scroll a ViewGroup to the bottom by repeatedly calling
     * {@link #dragQuarterScreenUp(InstrumentationTestCase, Activity)}
     *
     * @param test The test case that is being run
     * @param activity The activity that is in the foreground of the test case
     * @param v The ViewGroup that should be dragged
     */
    public static void scrollToBottom(InstrumentationTestCase test, Activity activity,
            ViewGroup v) {
        ViewStateSnapshot prev;
        ViewStateSnapshot next = new ViewStateSnapshot(v);
        do {
            prev = next;
            TouchUtils.dragQuarterScreenUp(test, activity);
            next = new ViewStateSnapshot(v);
        } while (!prev.equals(next));
    }

    /**
     * Scroll a ViewGroup to the top by repeatedly calling
     * {@link #dragQuarterScreenDown(InstrumentationTestCase, Activity)}
     *
     * @param test The test case that is being run
     * @param v The ViewGroup that should be dragged
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void scrollToTop(ActivityInstrumentationTestCase test, ViewGroup v) {
        scrollToTop(test, test.getActivity(), v);
    }

    /**
     * Scroll a ViewGroup to the top by repeatedly calling
     * {@link #dragQuarterScreenDown(InstrumentationTestCase, Activity)}
     *
     * @param test The test case that is being run
     * @param activity The activity that is in the foreground of the test case
     * @param v The ViewGroup that should be dragged
     */
    public static void scrollToTop(InstrumentationTestCase test, Activity activity, ViewGroup v) {
        ViewStateSnapshot prev;
        ViewStateSnapshot next = new ViewStateSnapshot(v);
        do {
            prev = next;
            TouchUtils.dragQuarterScreenDown(test, activity);
            next = new ViewStateSnapshot(v);
        } while (!prev.equals(next));
    }

    /**
     * Simulate touching the center of a view and dragging to the bottom of the screen.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void dragViewToBottom(ActivityInstrumentationTestCase test, View v) {
        dragViewToBottom(test, test.getActivity(), v, 4);
    }

    /**
     * Simulate touching the center of a view and dragging to the bottom of the screen.
     *
     * @param test The test case that is being run
     * @param activity The activity that is in the foreground of the test case
     * @param v The view that should be dragged
     */
    public static void dragViewToBottom(InstrumentationTestCase test, Activity activity, View v) {
        dragViewToBottom(test, activity, v, 4);
    }

    /**
     * Simulate touching the center of a view and dragging to the bottom of the screen.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param stepCount How many move steps to include in the drag
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void dragViewToBottom(ActivityInstrumentationTestCase test, View v,
            int stepCount) {
        dragViewToBottom(test, test.getActivity(), v, stepCount);
    }

    /**
     * Simulate touching the center of a view and dragging to the bottom of the screen.
     *
     * @param test The test case that is being run
     * @param activity The activity that is in the foreground of the test case
     * @param v The view that should be dragged
     * @param stepCount How many move steps to include in the drag
     */
    public static void dragViewToBottom(InstrumentationTestCase test, Activity activity, View v,
            int stepCount) {
        WindowManager wm = activity.getWindowManager();
        final int screenHeight = getSizeExcludingNavigationBarAndCutout(
                wm.getCurrentWindowMetrics()).getHeight();

        int[] xy = new int[2];
        v.getLocationOnScreen(xy);

        final int viewWidth = v.getWidth();
        final int viewHeight = v.getHeight();

        final float x = xy[0] + (viewWidth / 2.0f);
        float fromY = xy[1] + (viewHeight / 2.0f);
        float toY = screenHeight - 1;

        drag(test, x, x, fromY, toY, stepCount);
    }

    /**
     * Simulate touching the center of a view and releasing quickly (before the tap timeout).
     *
     * @param test The test case that is being run
     * @param v The view that should be clicked
     */
    public static void tapView(InstrumentationTestCase test, View v) {
        int[] xy = new int[2];
        v.getLocationOnScreen(xy);

        final int viewWidth = v.getWidth();
        final int viewHeight = v.getHeight();

        final float x = xy[0] + (viewWidth / 2.0f);
        float y = xy[1] + (viewHeight / 2.0f);

        Instrumentation inst = test.getInstrumentation();

        long downTime = SystemClock.uptimeMillis();
        long eventTime = SystemClock.uptimeMillis();

        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
                MotionEvent.ACTION_DOWN, x, y, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();

        eventTime = SystemClock.uptimeMillis();
        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();

        eventTime = SystemClock.uptimeMillis();
        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();
    }

    /**
     * Simulate touching the center of a view and cancelling (so no onClick should
     * fire, etc).
     *
     * @param test The test case that is being run
     * @param v The view that should be clicked
     */
    public static void touchAndCancelView(InstrumentationTestCase test, View v) {
        int[] xy = new int[2];
        v.getLocationOnScreen(xy);

        final int viewWidth = v.getWidth();
        final int viewHeight = v.getHeight();

        final float x = xy[0] + (viewWidth / 2.0f);
        float y = xy[1] + (viewHeight / 2.0f);

        Instrumentation inst = test.getInstrumentation();

        long downTime = SystemClock.uptimeMillis();
        long eventTime = SystemClock.uptimeMillis();

        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
                MotionEvent.ACTION_DOWN, x, y, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();

        eventTime = SystemClock.uptimeMillis();
        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL,
                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();

    }

    /**
     * Simulate touching the center of a view and releasing.
     *
     * @param test The test case that is being run
     * @param v The view that should be clicked
     */
    public static void clickView(InstrumentationTestCase test, View v) {
        int[] xy = new int[2];
        v.getLocationOnScreen(xy);

        final int viewWidth = v.getWidth();
        final int viewHeight = v.getHeight();

        final float x = xy[0] + (viewWidth / 2.0f);
        float y = xy[1] + (viewHeight / 2.0f);

        Instrumentation inst = test.getInstrumentation();

        long downTime = SystemClock.uptimeMillis();
        long eventTime = SystemClock.uptimeMillis();

        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
                MotionEvent.ACTION_DOWN, x, y, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();


        eventTime = SystemClock.uptimeMillis();
        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();

        eventTime = SystemClock.uptimeMillis();
        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * Simulate touching the center of a view, holding until it is a long press, and then releasing.
     *
     * @param test The test case that is being run
     * @param v The view that should be clicked
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void longClickView(ActivityInstrumentationTestCase test, View v) {
        longClickView((InstrumentationTestCase) test, v);
    }

    /**
     * Simulate touching the center of a view, holding until it is a long press, and then releasing.
     *
     * @param test The test case that is being run
     * @param v The view that should be clicked
     */
    public static void longClickView(InstrumentationTestCase test, View v) {
        int[] xy = new int[2];
        v.getLocationOnScreen(xy);

        final int viewWidth = v.getWidth();
        final int viewHeight = v.getHeight();

        final float x = xy[0] + (viewWidth / 2.0f);
        float y = xy[1] + (viewHeight / 2.0f);

        Instrumentation inst = test.getInstrumentation();

        long downTime = SystemClock.uptimeMillis();
        long eventTime = SystemClock.uptimeMillis();

        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
                MotionEvent.ACTION_DOWN, x, y, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();

        eventTime = SystemClock.uptimeMillis();
        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
                x + touchSlop / 2, y + touchSlop / 2, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();

        try {
            Thread.sleep((long)(ViewConfiguration.getLongPressTimeout() * 1.5f));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        eventTime = SystemClock.uptimeMillis();
        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();
    }

    /**
     * Simulate touching the center of a view and dragging to the top of the screen.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void dragViewToTop(ActivityInstrumentationTestCase test, View v) {
        dragViewToTop((InstrumentationTestCase) test, v, 4);
    }

    /**
     * Simulate touching the center of a view and dragging to the top of the screen.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param stepCount How many move steps to include in the drag
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void dragViewToTop(ActivityInstrumentationTestCase test, View v, int stepCount) {
        dragViewToTop((InstrumentationTestCase) test, v, stepCount);
    }

    /**
     * Simulate touching the center of a view and dragging to the top of the screen.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     */
    public static void dragViewToTop(InstrumentationTestCase test, View v) {
        dragViewToTop(test, v, 4);
    }

    /**
     * Simulate touching the center of a view and dragging to the top of the screen.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param stepCount How many move steps to include in the drag
     */
    public static void dragViewToTop(InstrumentationTestCase test, View v, int stepCount) {
        int[] xy = new int[2];
        v.getLocationOnScreen(xy);

        final int viewWidth = v.getWidth();
        final int viewHeight = v.getHeight();

        final float x = xy[0] + (viewWidth / 2.0f);
        float fromY = xy[1] + (viewHeight / 2.0f);
        float toY = 0;

        drag(test, x, x, fromY, toY, stepCount);
    }

    /**
     * Get the location of a view. Use the gravity param to specify which part of the view to
     * return.
     *
     * @param v View to find
     * @param gravity A combination of (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL,
     *        RIGHT)
     * @param xy Result
     */
    private static void getStartLocation(View v, int gravity, int[] xy) {
        v.getLocationOnScreen(xy);

        final int viewWidth = v.getWidth();
        final int viewHeight = v.getHeight();

        switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
        case Gravity.TOP:
            break;
        case Gravity.CENTER_VERTICAL:
            xy[1] += viewHeight / 2;
            break;
        case Gravity.BOTTOM:
            xy[1] += viewHeight - 1;
            break;
        default:
            // Same as top -- do nothing
        }

        switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
        case Gravity.LEFT:
            break;
        case Gravity.CENTER_HORIZONTAL:
            xy[0] += viewWidth / 2;
            break;
        case Gravity.RIGHT:
            xy[0] += viewWidth - 1;
            break;
        default:
            // Same as left -- do nothing
        }
    }

    /**
     * Simulate touching a view and dragging it by the specified amount.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param gravity Which part of the view to use for the initial down event. A combination of
     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
     * @param deltaX Amount to drag horizontally in pixels
     * @param deltaY Amount to drag vertically in pixels
     *
     * @return distance in pixels covered by the drag
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static int dragViewBy(ActivityInstrumentationTestCase test, View v, int gravity,
            int deltaX, int deltaY) {
        return dragViewBy((InstrumentationTestCase) test, v, gravity, deltaX, deltaY);
    }

    /**
     * Simulate touching a view and dragging it by the specified amount.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param gravity Which part of the view to use for the initial down event. A combination of
     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
     * @param deltaX Amount to drag horizontally in pixels
     * @param deltaY Amount to drag vertically in pixels
     *
     * @return distance in pixels covered by the drag
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static int dragViewBy(InstrumentationTestCase test, View v, int gravity, int deltaX,
            int deltaY) {
        int[] xy = new int[2];

        getStartLocation(v, gravity, xy);

        final int fromX = xy[0];
        final int fromY = xy[1];

        int distance = (int) Math.hypot(deltaX, deltaY);

        drag(test, fromX, fromX + deltaX, fromY, fromY + deltaY, distance);

        return distance;
    }

    /**
     * Simulate touching a view and dragging it to a specified location.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param gravity Which part of the view to use for the initial down event. A combination of
     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
     * @param toX Final location of the view after dragging
     * @param toY Final location of the view after dragging
     *
     * @return distance in pixels covered by the drag
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static int dragViewTo(ActivityInstrumentationTestCase test, View v, int gravity, int toX,
            int toY) {
        return dragViewTo((InstrumentationTestCase) test, v, gravity, toX, toY);
    }

    /**
     * Simulate touching a view and dragging it to a specified location.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param gravity Which part of the view to use for the initial down event. A combination of
     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
     * @param toX Final location of the view after dragging
     * @param toY Final location of the view after dragging
     *
     * @return distance in pixels covered by the drag
     */
    public static int dragViewTo(InstrumentationTestCase test, View v, int gravity, int toX,
            int toY) {
        int[] xy = new int[2];

        getStartLocation(v, gravity, xy);

        final int fromX = xy[0];
        final int fromY = xy[1];

        int deltaX = fromX - toX;
        int deltaY = fromY - toY;

        int distance = (int)Math.hypot(deltaX, deltaY);
        drag(test, fromX, toX, fromY, toY, distance);

        return distance;
    }

    /**
     * Simulate touching a view and dragging it to a specified location. Only moves horizontally.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param gravity Which part of the view to use for the initial down event. A combination of
     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
     * @param toX Final location of the view after dragging
     *
     * @return distance in pixels covered by the drag
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static int dragViewToX(ActivityInstrumentationTestCase test, View v, int gravity,
            int toX) {
        return dragViewToX((InstrumentationTestCase) test, v, gravity, toX);
    }

    /**
     * Simulate touching a view and dragging it to a specified location. Only moves horizontally.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param gravity Which part of the view to use for the initial down event. A combination of
     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
     * @param toX Final location of the view after dragging
     *
     * @return distance in pixels covered by the drag
     */
    public static int dragViewToX(InstrumentationTestCase test, View v, int gravity, int toX) {
        int[] xy = new int[2];

        getStartLocation(v, gravity, xy);

        final int fromX = xy[0];
        final int fromY = xy[1];

        int deltaX = fromX - toX;

        drag(test, fromX, toX, fromY, fromY, deltaX);

        return deltaX;
    }

    /**
     * Simulate touching a view and dragging it to a specified location. Only moves vertically.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param gravity Which part of the view to use for the initial down event. A combination of
     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
     * @param toY Final location of the view after dragging
     *
     * @return distance in pixels covered by the drag
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static int dragViewToY(ActivityInstrumentationTestCase test, View v, int gravity,
            int toY) {
        return dragViewToY((InstrumentationTestCase) test, v, gravity, toY);
    }

    /**
     * Simulate touching a view and dragging it to a specified location. Only moves vertically.
     *
     * @param test The test case that is being run
     * @param v The view that should be dragged
     * @param gravity Which part of the view to use for the initial down event. A combination of
     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
     * @param toY Final location of the view after dragging
     *
     * @return distance in pixels covered by the drag
     */
    public static int dragViewToY(InstrumentationTestCase test, View v, int gravity, int toY) {
        int[] xy = new int[2];

        getStartLocation(v, gravity, xy);

        final int fromX = xy[0];
        final int fromY = xy[1];

        int deltaY = fromY - toY;

        drag(test, fromX, fromX, fromY, toY, deltaY);

        return deltaY;
    }


    /**
     * Simulate touching a specific location and dragging to a new location.
     *
     * @param test The test case that is being run
     * @param fromX X coordinate of the initial touch, in screen coordinates
     * @param toX Xcoordinate of the drag destination, in screen coordinates
     * @param fromY X coordinate of the initial touch, in screen coordinates
     * @param toY Y coordinate of the drag destination, in screen coordinates
     * @param stepCount How many move steps to include in the drag
     *
     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
     * configuring the Activity under test
     */
    @Deprecated
    public static void drag(ActivityInstrumentationTestCase test, float fromX, float toX,
            float fromY, float toY, int stepCount) {
        drag((InstrumentationTestCase) test, fromX, toX, fromY, toY, stepCount);
    }

    /**
     * Simulate touching a specific location and dragging to a new location.
     *
     * @param test The test case that is being run
     * @param fromX X coordinate of the initial touch, in screen coordinates
     * @param toX Xcoordinate of the drag destination, in screen coordinates
     * @param fromY X coordinate of the initial touch, in screen coordinates
     * @param toY Y coordinate of the drag destination, in screen coordinates
     * @param stepCount How many move steps to include in the drag
     */
    public static void drag(InstrumentationTestCase test, float fromX, float toX, float fromY,
            float toY, int stepCount) {
        Instrumentation inst = test.getInstrumentation();

        long downTime = SystemClock.uptimeMillis();
        long eventTime = SystemClock.uptimeMillis();

        float y = fromY;
        float x = fromX;

        float yStep = (toY - fromY) / stepCount;
        float xStep = (toX - fromX) / stepCount;

        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
                MotionEvent.ACTION_DOWN, x, y, 0);
        inst.sendPointerSync(event);
        for (int i = 0; i < stepCount; ++i) {
            y += yStep;
            x += xStep;
            eventTime = SystemClock.uptimeMillis();
            event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
            inst.sendPointerSync(event);
        }

        eventTime = SystemClock.uptimeMillis();
        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
        inst.sendPointerSync(event);
        inst.waitForIdleSync();
    }

    private static class ViewStateSnapshot {
        final View mFirst;
        final View mLast;
        final int mFirstTop;
        final int mLastBottom;
        final int mChildCount;
        private ViewStateSnapshot(ViewGroup viewGroup) {
            mChildCount = viewGroup.getChildCount();
            if (mChildCount == 0) {
                mFirst = mLast = null;
                mFirstTop = mLastBottom = Integer.MIN_VALUE;
            } else {
                mFirst = viewGroup.getChildAt(0);
                mLast = viewGroup.getChildAt(mChildCount - 1);
                mFirstTop = mFirst.getTop();
                mLastBottom = mLast.getBottom();
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            final ViewStateSnapshot that = (ViewStateSnapshot) o;
            return mFirstTop == that.mFirstTop &&
                    mLastBottom == that.mLastBottom &&
                    mFirst == that.mFirst &&
                    mLast == that.mLast &&
                    mChildCount == that.mChildCount;
        }

        @Override
        public int hashCode() {
            int result = mFirst != null ? mFirst.hashCode() : 0;
            result = 31 * result + (mLast != null ? mLast.hashCode() : 0);
            result = 31 * result + mFirstTop;
            result = 31 * result + mLastBottom;
            result = 31 * result + mChildCount;
            return result;
        }
    }
}
