1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.test;
18
19import android.app.Activity;
20import android.app.Instrumentation;
21import android.graphics.Point;
22import android.os.SystemClock;
23import android.view.Display;
24import android.view.Gravity;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.ViewConfiguration;
28import android.view.ViewGroup;
29
30/**
31 * Reusable methods for generating touch events. These methods can be used with
32 * InstrumentationTestCase or ActivityInstrumentationTestCase2 to simulate user interaction with
33 * the application through a touch screen.
34 */
35public class TouchUtils {
36
37    /**
38     * Simulate touching in the center of the screen and dragging one quarter of the way down
39     * @param test The test case that is being run
40     *
41     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
42     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
43     * configuring the Activity under test
44     */
45    @Deprecated
46    public static void dragQuarterScreenDown(ActivityInstrumentationTestCase test) {
47        dragQuarterScreenDown(test, test.getActivity());
48    }
49
50    /**
51     * Simulate touching in the center of the screen and dragging one quarter of the way down
52     * @param test The test case that is being run
53     * @param activity The activity that is in the foreground of the test case
54     */
55    public static void dragQuarterScreenDown(InstrumentationTestCase test, Activity activity) {
56        Display display = activity.getWindowManager().getDefaultDisplay();
57        final Point size = new Point();
58        display.getSize(size);
59
60        final float x = size.x / 2.0f;
61        final float fromY = size.y * 0.5f;
62        final float toY = size.y * 0.75f;
63
64        drag(test, x, x, fromY, toY, 4);
65    }
66
67    /**
68     * Simulate touching in the center of the screen and dragging one quarter of the way up
69     * @param test The test case that is being run
70     *
71     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
72     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
73     * configuring the Activity under test
74     */
75    @Deprecated
76    public static void dragQuarterScreenUp(ActivityInstrumentationTestCase test) {
77        dragQuarterScreenUp(test, test.getActivity());
78    }
79
80    /**
81     * Simulate touching in the center of the screen and dragging one quarter of the way up
82     * @param test The test case that is being run
83     * @param activity The activity that is in the foreground of the test case
84     */
85    public static void dragQuarterScreenUp(InstrumentationTestCase test, Activity activity) {
86        Display display = activity.getWindowManager().getDefaultDisplay();
87        final Point size = new Point();
88        display.getSize(size);
89
90        final float x = size.x / 2.0f;
91        final float fromY = size.y * 0.5f;
92        final float toY = size.y * 0.25f;
93
94        drag(test, x, x, fromY, toY, 4);
95    }
96
97    /**
98     * Scroll a ViewGroup to the bottom by repeatedly calling
99     * {@link #dragQuarterScreenUp(InstrumentationTestCase, Activity)}
100     *
101     * @param test The test case that is being run
102     * @param v The ViewGroup that should be dragged
103     *
104     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
105     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
106     * configuring the Activity under test
107     */
108    @Deprecated
109    public static void scrollToBottom(ActivityInstrumentationTestCase test, ViewGroup v) {
110        scrollToBottom(test, test.getActivity(), v);
111    }
112
113    /**
114     * Scroll a ViewGroup to the bottom by repeatedly calling
115     * {@link #dragQuarterScreenUp(InstrumentationTestCase, Activity)}
116     *
117     * @param test The test case that is being run
118     * @param activity The activity that is in the foreground of the test case
119     * @param v The ViewGroup that should be dragged
120     */
121    public static void scrollToBottom(InstrumentationTestCase test, Activity activity,
122            ViewGroup v) {
123        ViewStateSnapshot prev;
124        ViewStateSnapshot next = new ViewStateSnapshot(v);
125        do {
126            prev = next;
127            TouchUtils.dragQuarterScreenUp(test, activity);
128            next = new ViewStateSnapshot(v);
129        } while (!prev.equals(next));
130    }
131
132    /**
133     * Scroll a ViewGroup to the top by repeatedly calling
134     * {@link #dragQuarterScreenDown(InstrumentationTestCase, Activity)}
135     *
136     * @param test The test case that is being run
137     * @param v The ViewGroup that should be dragged
138     *
139     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
140     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
141     * configuring the Activity under test
142     */
143    @Deprecated
144    public static void scrollToTop(ActivityInstrumentationTestCase test, ViewGroup v) {
145        scrollToTop(test, test.getActivity(), v);
146    }
147
148    /**
149     * Scroll a ViewGroup to the top by repeatedly calling
150     * {@link #dragQuarterScreenDown(InstrumentationTestCase, Activity)}
151     *
152     * @param test The test case that is being run
153     * @param activity The activity that is in the foreground of the test case
154     * @param v The ViewGroup that should be dragged
155     */
156    public static void scrollToTop(InstrumentationTestCase test, Activity activity, ViewGroup v) {
157        ViewStateSnapshot prev;
158        ViewStateSnapshot next = new ViewStateSnapshot(v);
159        do {
160            prev = next;
161            TouchUtils.dragQuarterScreenDown(test, activity);
162            next = new ViewStateSnapshot(v);
163        } while (!prev.equals(next));
164    }
165
166    /**
167     * Simulate touching the center of a view and dragging to the bottom of the screen.
168     *
169     * @param test The test case that is being run
170     * @param v The view that should be dragged
171     *
172     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
173     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
174     * configuring the Activity under test
175     */
176    @Deprecated
177    public static void dragViewToBottom(ActivityInstrumentationTestCase test, View v) {
178        dragViewToBottom(test, test.getActivity(), v, 4);
179    }
180
181    /**
182     * Simulate touching the center of a view and dragging to the bottom of the screen.
183     *
184     * @param test The test case that is being run
185     * @param activity The activity that is in the foreground of the test case
186     * @param v The view that should be dragged
187     */
188    public static void dragViewToBottom(InstrumentationTestCase test, Activity activity, View v) {
189        dragViewToBottom(test, activity, v, 4);
190    }
191
192    /**
193     * Simulate touching the center of a view and dragging to the bottom of the screen.
194     *
195     * @param test The test case that is being run
196     * @param v The view that should be dragged
197     * @param stepCount How many move steps to include in the drag
198     *
199     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
200     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
201     * configuring the Activity under test
202     */
203    @Deprecated
204    public static void dragViewToBottom(ActivityInstrumentationTestCase test, View v,
205            int stepCount) {
206        dragViewToBottom(test, test.getActivity(), v, stepCount);
207    }
208
209    /**
210     * Simulate touching the center of a view and dragging to the bottom of the screen.
211     *
212     * @param test The test case that is being run
213     * @param activity The activity that is in the foreground of the test case
214     * @param v The view that should be dragged
215     * @param stepCount How many move steps to include in the drag
216     */
217    public static void dragViewToBottom(InstrumentationTestCase test, Activity activity, View v,
218            int stepCount) {
219        int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
220
221        int[] xy = new int[2];
222        v.getLocationOnScreen(xy);
223
224        final int viewWidth = v.getWidth();
225        final int viewHeight = v.getHeight();
226
227        final float x = xy[0] + (viewWidth / 2.0f);
228        float fromY = xy[1] + (viewHeight / 2.0f);
229        float toY = screenHeight - 1;
230
231        drag(test, x, x, fromY, toY, stepCount);
232    }
233
234    /**
235     * Simulate touching the center of a view and releasing quickly (before the tap timeout).
236     *
237     * @param test The test case that is being run
238     * @param v The view that should be clicked
239     */
240    public static void tapView(InstrumentationTestCase test, View v) {
241        int[] xy = new int[2];
242        v.getLocationOnScreen(xy);
243
244        final int viewWidth = v.getWidth();
245        final int viewHeight = v.getHeight();
246
247        final float x = xy[0] + (viewWidth / 2.0f);
248        float y = xy[1] + (viewHeight / 2.0f);
249
250        Instrumentation inst = test.getInstrumentation();
251
252        long downTime = SystemClock.uptimeMillis();
253        long eventTime = SystemClock.uptimeMillis();
254
255        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
256                MotionEvent.ACTION_DOWN, x, y, 0);
257        inst.sendPointerSync(event);
258        inst.waitForIdleSync();
259
260        eventTime = SystemClock.uptimeMillis();
261        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
262        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
263                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
264        inst.sendPointerSync(event);
265        inst.waitForIdleSync();
266
267        eventTime = SystemClock.uptimeMillis();
268        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
269        inst.sendPointerSync(event);
270        inst.waitForIdleSync();
271    }
272
273    /**
274     * Simulate touching the center of a view and cancelling (so no onClick should
275     * fire, etc).
276     *
277     * @param test The test case that is being run
278     * @param v The view that should be clicked
279     */
280    public static void touchAndCancelView(InstrumentationTestCase test, View v) {
281        int[] xy = new int[2];
282        v.getLocationOnScreen(xy);
283
284        final int viewWidth = v.getWidth();
285        final int viewHeight = v.getHeight();
286
287        final float x = xy[0] + (viewWidth / 2.0f);
288        float y = xy[1] + (viewHeight / 2.0f);
289
290        Instrumentation inst = test.getInstrumentation();
291
292        long downTime = SystemClock.uptimeMillis();
293        long eventTime = SystemClock.uptimeMillis();
294
295        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
296                MotionEvent.ACTION_DOWN, x, y, 0);
297        inst.sendPointerSync(event);
298        inst.waitForIdleSync();
299
300        eventTime = SystemClock.uptimeMillis();
301        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
302        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL,
303                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
304        inst.sendPointerSync(event);
305        inst.waitForIdleSync();
306
307    }
308
309    /**
310     * Simulate touching the center of a view and releasing.
311     *
312     * @param test The test case that is being run
313     * @param v The view that should be clicked
314     */
315    public static void clickView(InstrumentationTestCase test, View v) {
316        int[] xy = new int[2];
317        v.getLocationOnScreen(xy);
318
319        final int viewWidth = v.getWidth();
320        final int viewHeight = v.getHeight();
321
322        final float x = xy[0] + (viewWidth / 2.0f);
323        float y = xy[1] + (viewHeight / 2.0f);
324
325        Instrumentation inst = test.getInstrumentation();
326
327        long downTime = SystemClock.uptimeMillis();
328        long eventTime = SystemClock.uptimeMillis();
329
330        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
331                MotionEvent.ACTION_DOWN, x, y, 0);
332        inst.sendPointerSync(event);
333        inst.waitForIdleSync();
334
335
336        eventTime = SystemClock.uptimeMillis();
337        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
338        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
339                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
340        inst.sendPointerSync(event);
341        inst.waitForIdleSync();
342
343        eventTime = SystemClock.uptimeMillis();
344        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
345        inst.sendPointerSync(event);
346        inst.waitForIdleSync();
347
348        try {
349            Thread.sleep(1000);
350        } catch (InterruptedException e) {
351            e.printStackTrace();
352        }
353    }
354
355    /**
356     * Simulate touching the center of a view, holding until it is a long press, and then releasing.
357     *
358     * @param test The test case that is being run
359     * @param v The view that should be clicked
360     *
361     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
362     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
363     * configuring the Activity under test
364     */
365    @Deprecated
366    public static void longClickView(ActivityInstrumentationTestCase test, View v) {
367        longClickView((InstrumentationTestCase) test, v);
368    }
369
370    /**
371     * Simulate touching the center of a view, holding until it is a long press, and then releasing.
372     *
373     * @param test The test case that is being run
374     * @param v The view that should be clicked
375     */
376    public static void longClickView(InstrumentationTestCase test, View v) {
377        int[] xy = new int[2];
378        v.getLocationOnScreen(xy);
379
380        final int viewWidth = v.getWidth();
381        final int viewHeight = v.getHeight();
382
383        final float x = xy[0] + (viewWidth / 2.0f);
384        float y = xy[1] + (viewHeight / 2.0f);
385
386        Instrumentation inst = test.getInstrumentation();
387
388        long downTime = SystemClock.uptimeMillis();
389        long eventTime = SystemClock.uptimeMillis();
390
391        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
392                MotionEvent.ACTION_DOWN, x, y, 0);
393        inst.sendPointerSync(event);
394        inst.waitForIdleSync();
395
396        eventTime = SystemClock.uptimeMillis();
397        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
398        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
399                x + touchSlop / 2, y + touchSlop / 2, 0);
400        inst.sendPointerSync(event);
401        inst.waitForIdleSync();
402
403        try {
404            Thread.sleep((long)(ViewConfiguration.getLongPressTimeout() * 1.5f));
405        } catch (InterruptedException e) {
406            e.printStackTrace();
407        }
408
409        eventTime = SystemClock.uptimeMillis();
410        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
411        inst.sendPointerSync(event);
412        inst.waitForIdleSync();
413    }
414
415    /**
416     * Simulate touching the center of a view and dragging to the top of the screen.
417     *
418     * @param test The test case that is being run
419     * @param v The view that should be dragged
420     *
421     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
422     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
423     * configuring the Activity under test
424     */
425    @Deprecated
426    public static void dragViewToTop(ActivityInstrumentationTestCase test, View v) {
427        dragViewToTop((InstrumentationTestCase) test, v, 4);
428    }
429
430    /**
431     * Simulate touching the center of a view and dragging to the top of the screen.
432     *
433     * @param test The test case that is being run
434     * @param v The view that should be dragged
435     * @param stepCount How many move steps to include in the drag
436     *
437     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
438     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
439     * configuring the Activity under test
440     */
441    @Deprecated
442    public static void dragViewToTop(ActivityInstrumentationTestCase test, View v, int stepCount) {
443        dragViewToTop((InstrumentationTestCase) test, v, stepCount);
444    }
445
446    /**
447     * Simulate touching the center of a view and dragging to the top of the screen.
448     *
449     * @param test The test case that is being run
450     * @param v The view that should be dragged
451     */
452    public static void dragViewToTop(InstrumentationTestCase test, View v) {
453        dragViewToTop(test, v, 4);
454    }
455
456    /**
457     * Simulate touching the center of a view and dragging to the top of the screen.
458     *
459     * @param test The test case that is being run
460     * @param v The view that should be dragged
461     * @param stepCount How many move steps to include in the drag
462     */
463    public static void dragViewToTop(InstrumentationTestCase test, View v, int stepCount) {
464        int[] xy = new int[2];
465        v.getLocationOnScreen(xy);
466
467        final int viewWidth = v.getWidth();
468        final int viewHeight = v.getHeight();
469
470        final float x = xy[0] + (viewWidth / 2.0f);
471        float fromY = xy[1] + (viewHeight / 2.0f);
472        float toY = 0;
473
474        drag(test, x, x, fromY, toY, stepCount);
475    }
476
477    /**
478     * Get the location of a view. Use the gravity param to specify which part of the view to
479     * return.
480     *
481     * @param v View to find
482     * @param gravity A combination of (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL,
483     *        RIGHT)
484     * @param xy Result
485     */
486    private static void getStartLocation(View v, int gravity, int[] xy) {
487        v.getLocationOnScreen(xy);
488
489        final int viewWidth = v.getWidth();
490        final int viewHeight = v.getHeight();
491
492        switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
493        case Gravity.TOP:
494            break;
495        case Gravity.CENTER_VERTICAL:
496            xy[1] += viewHeight / 2;
497            break;
498        case Gravity.BOTTOM:
499            xy[1] += viewHeight - 1;
500            break;
501        default:
502            // Same as top -- do nothing
503        }
504
505        switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
506        case Gravity.LEFT:
507            break;
508        case Gravity.CENTER_HORIZONTAL:
509            xy[0] += viewWidth / 2;
510            break;
511        case Gravity.RIGHT:
512            xy[0] += viewWidth - 1;
513            break;
514        default:
515            // Same as left -- do nothing
516        }
517    }
518
519    /**
520     * Simulate touching a view and dragging it by the specified amount.
521     *
522     * @param test The test case that is being run
523     * @param v The view that should be dragged
524     * @param gravity Which part of the view to use for the initial down event. A combination of
525     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
526     * @param deltaX Amount to drag horizontally in pixels
527     * @param deltaY Amount to drag vertically in pixels
528     *
529     * @return distance in pixels covered by the drag
530     *
531     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
532     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
533     * configuring the Activity under test
534     */
535    @Deprecated
536    public static int dragViewBy(ActivityInstrumentationTestCase test, View v, int gravity,
537            int deltaX, int deltaY) {
538        return dragViewBy((InstrumentationTestCase) test, v, gravity, deltaX, deltaY);
539    }
540
541    /**
542     * Simulate touching a view and dragging it by the specified amount.
543     *
544     * @param test The test case that is being run
545     * @param v The view that should be dragged
546     * @param gravity Which part of the view to use for the initial down event. A combination of
547     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
548     * @param deltaX Amount to drag horizontally in pixels
549     * @param deltaY Amount to drag vertically in pixels
550     *
551     * @return distance in pixels covered by the drag
552     *
553     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
554     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
555     * configuring the Activity under test
556     */
557    @Deprecated
558    public static int dragViewBy(InstrumentationTestCase test, View v, int gravity, int deltaX,
559            int deltaY) {
560        int[] xy = new int[2];
561
562        getStartLocation(v, gravity, xy);
563
564        final int fromX = xy[0];
565        final int fromY = xy[1];
566
567        int distance = (int) Math.hypot(deltaX, deltaY);
568
569        drag(test, fromX, fromX + deltaX, fromY, fromY + deltaY, distance);
570
571        return distance;
572    }
573
574    /**
575     * Simulate touching a view and dragging it to a specified location.
576     *
577     * @param test The test case that is being run
578     * @param v The view that should be dragged
579     * @param gravity Which part of the view to use for the initial down event. A combination of
580     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
581     * @param toX Final location of the view after dragging
582     * @param toY Final location of the view after dragging
583     *
584     * @return distance in pixels covered by the drag
585     *
586     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
587     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
588     * configuring the Activity under test
589     */
590    @Deprecated
591    public static int dragViewTo(ActivityInstrumentationTestCase test, View v, int gravity, int toX,
592            int toY) {
593        return dragViewTo((InstrumentationTestCase) test, v, gravity, toX, toY);
594    }
595
596    /**
597     * Simulate touching a view and dragging it to a specified location.
598     *
599     * @param test The test case that is being run
600     * @param v The view that should be dragged
601     * @param gravity Which part of the view to use for the initial down event. A combination of
602     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
603     * @param toX Final location of the view after dragging
604     * @param toY Final location of the view after dragging
605     *
606     * @return distance in pixels covered by the drag
607     */
608    public static int dragViewTo(InstrumentationTestCase test, View v, int gravity, int toX,
609            int toY) {
610        int[] xy = new int[2];
611
612        getStartLocation(v, gravity, xy);
613
614        final int fromX = xy[0];
615        final int fromY = xy[1];
616
617        int deltaX = fromX - toX;
618        int deltaY = fromY - toY;
619
620        int distance = (int)Math.hypot(deltaX, deltaY);
621        drag(test, fromX, toX, fromY, toY, distance);
622
623        return distance;
624    }
625
626    /**
627     * Simulate touching a view and dragging it to a specified location. Only moves horizontally.
628     *
629     * @param test The test case that is being run
630     * @param v The view that should be dragged
631     * @param gravity Which part of the view to use for the initial down event. A combination of
632     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
633     * @param toX Final location of the view after dragging
634     *
635     * @return distance in pixels covered by the drag
636     *
637     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
638     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
639     * configuring the Activity under test
640     */
641    @Deprecated
642    public static int dragViewToX(ActivityInstrumentationTestCase test, View v, int gravity,
643            int toX) {
644        return dragViewToX((InstrumentationTestCase) test, v, gravity, toX);
645    }
646
647    /**
648     * Simulate touching a view and dragging it to a specified location. Only moves horizontally.
649     *
650     * @param test The test case that is being run
651     * @param v The view that should be dragged
652     * @param gravity Which part of the view to use for the initial down event. A combination of
653     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
654     * @param toX Final location of the view after dragging
655     *
656     * @return distance in pixels covered by the drag
657     */
658    public static int dragViewToX(InstrumentationTestCase test, View v, int gravity, int toX) {
659        int[] xy = new int[2];
660
661        getStartLocation(v, gravity, xy);
662
663        final int fromX = xy[0];
664        final int fromY = xy[1];
665
666        int deltaX = fromX - toX;
667
668        drag(test, fromX, toX, fromY, fromY, deltaX);
669
670        return deltaX;
671    }
672
673    /**
674     * Simulate touching a view and dragging it to a specified location. Only moves vertically.
675     *
676     * @param test The test case that is being run
677     * @param v The view that should be dragged
678     * @param gravity Which part of the view to use for the initial down event. A combination of
679     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
680     * @param toY Final location of the view after dragging
681     *
682     * @return distance in pixels covered by the drag
683     *
684     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
685     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
686     * configuring the Activity under test
687     */
688    @Deprecated
689    public static int dragViewToY(ActivityInstrumentationTestCase test, View v, int gravity,
690            int toY) {
691        return dragViewToY((InstrumentationTestCase) test, v, gravity, toY);
692    }
693
694    /**
695     * Simulate touching a view and dragging it to a specified location. Only moves vertically.
696     *
697     * @param test The test case that is being run
698     * @param v The view that should be dragged
699     * @param gravity Which part of the view to use for the initial down event. A combination of
700     *        (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
701     * @param toY Final location of the view after dragging
702     *
703     * @return distance in pixels covered by the drag
704     */
705    public static int dragViewToY(InstrumentationTestCase test, View v, int gravity, int toY) {
706        int[] xy = new int[2];
707
708        getStartLocation(v, gravity, xy);
709
710        final int fromX = xy[0];
711        final int fromY = xy[1];
712
713        int deltaY = fromY - toY;
714
715        drag(test, fromX, fromX, fromY, toY, deltaY);
716
717        return deltaY;
718    }
719
720
721    /**
722     * Simulate touching a specific location and dragging to a new location.
723     *
724     * @param test The test case that is being run
725     * @param fromX X coordinate of the initial touch, in screen coordinates
726     * @param toX Xcoordinate of the drag destination, in screen coordinates
727     * @param fromY X coordinate of the initial touch, in screen coordinates
728     * @param toY Y coordinate of the drag destination, in screen coordinates
729     * @param stepCount How many move steps to include in the drag
730     *
731     * @deprecated {@link android.test.ActivityInstrumentationTestCase} is deprecated in favor of
732     * {@link android.test.ActivityInstrumentationTestCase2}, which provides more options for
733     * configuring the Activity under test
734     */
735    @Deprecated
736    public static void drag(ActivityInstrumentationTestCase test, float fromX, float toX,
737            float fromY, float toY, int stepCount) {
738        drag((InstrumentationTestCase) test, fromX, toX, fromY, toY, stepCount);
739    }
740
741    /**
742     * Simulate touching a specific location and dragging to a new location.
743     *
744     * @param test The test case that is being run
745     * @param fromX X coordinate of the initial touch, in screen coordinates
746     * @param toX Xcoordinate of the drag destination, in screen coordinates
747     * @param fromY X coordinate of the initial touch, in screen coordinates
748     * @param toY Y coordinate of the drag destination, in screen coordinates
749     * @param stepCount How many move steps to include in the drag
750     */
751    public static void drag(InstrumentationTestCase test, float fromX, float toX, float fromY,
752            float toY, int stepCount) {
753        Instrumentation inst = test.getInstrumentation();
754
755        long downTime = SystemClock.uptimeMillis();
756        long eventTime = SystemClock.uptimeMillis();
757
758        float y = fromY;
759        float x = fromX;
760
761        float yStep = (toY - fromY) / stepCount;
762        float xStep = (toX - fromX) / stepCount;
763
764        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
765                MotionEvent.ACTION_DOWN, x, y, 0);
766        inst.sendPointerSync(event);
767        for (int i = 0; i < stepCount; ++i) {
768            y += yStep;
769            x += xStep;
770            eventTime = SystemClock.uptimeMillis();
771            event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
772            inst.sendPointerSync(event);
773        }
774
775        eventTime = SystemClock.uptimeMillis();
776        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
777        inst.sendPointerSync(event);
778        inst.waitForIdleSync();
779    }
780
781    private static class ViewStateSnapshot {
782        final View mFirst;
783        final View mLast;
784        final int mFirstTop;
785        final int mLastBottom;
786        final int mChildCount;
787        private ViewStateSnapshot(ViewGroup viewGroup) {
788            mChildCount = viewGroup.getChildCount();
789            if (mChildCount == 0) {
790                mFirst = mLast = null;
791                mFirstTop = mLastBottom = Integer.MIN_VALUE;
792            } else {
793                mFirst = viewGroup.getChildAt(0);
794                mLast = viewGroup.getChildAt(mChildCount - 1);
795                mFirstTop = mFirst.getTop();
796                mLastBottom = mLast.getBottom();
797            }
798        }
799
800        @Override
801        public boolean equals(Object o) {
802            if (this == o) {
803                return true;
804            }
805            if (o == null || getClass() != o.getClass()) {
806                return false;
807            }
808
809            final ViewStateSnapshot that = (ViewStateSnapshot) o;
810            return mFirstTop == that.mFirstTop &&
811                    mLastBottom == that.mLastBottom &&
812                    mFirst == that.mFirst &&
813                    mLast == that.mLast &&
814                    mChildCount == that.mChildCount;
815        }
816
817        @Override
818        public int hashCode() {
819            int result = mFirst != null ? mFirst.hashCode() : 0;
820            result = 31 * result + (mLast != null ? mLast.hashCode() : 0);
821            result = 31 * result + mFirstTop;
822            result = 31 * result + mLastBottom;
823            result = 31 * result + mChildCount;
824            return result;
825        }
826    }
827}
828