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 
17 package android.test;
18 
19 import android.app.Activity;
20 import android.app.Instrumentation;
21 import android.graphics.Point;
22 import android.os.SystemClock;
23 import android.view.Display;
24 import android.view.Gravity;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.ViewConfiguration;
28 import 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  */
35 public 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
dragQuarterScreenDown(ActivityInstrumentationTestCase test)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      */
dragQuarterScreenDown(InstrumentationTestCase test, Activity activity)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
dragQuarterScreenUp(ActivityInstrumentationTestCase test)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      */
dragQuarterScreenUp(InstrumentationTestCase test, Activity activity)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
scrollToBottom(ActivityInstrumentationTestCase test, ViewGroup v)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      */
scrollToBottom(InstrumentationTestCase test, Activity activity, ViewGroup v)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
scrollToTop(ActivityInstrumentationTestCase test, ViewGroup v)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      */
scrollToTop(InstrumentationTestCase test, Activity activity, ViewGroup v)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
dragViewToBottom(ActivityInstrumentationTestCase test, View v)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      */
dragViewToBottom(InstrumentationTestCase test, Activity activity, View v)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
dragViewToBottom(ActivityInstrumentationTestCase test, View v, int stepCount)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      */
dragViewToBottom(InstrumentationTestCase test, Activity activity, View v, int stepCount)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      */
tapView(InstrumentationTestCase test, View v)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      */
touchAndCancelView(InstrumentationTestCase test, View v)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      */
clickView(InstrumentationTestCase test, View v)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
longClickView(ActivityInstrumentationTestCase test, View v)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      */
longClickView(InstrumentationTestCase test, View v)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
dragViewToTop(ActivityInstrumentationTestCase test, View v)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
dragViewToTop(ActivityInstrumentationTestCase test, View v, int stepCount)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      */
dragViewToTop(InstrumentationTestCase test, View v)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      */
dragViewToTop(InstrumentationTestCase test, View v, int stepCount)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      */
getStartLocation(View v, int gravity, int[] xy)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
dragViewBy(ActivityInstrumentationTestCase test, View v, int gravity, int deltaX, int deltaY)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
dragViewBy(InstrumentationTestCase test, View v, int gravity, int deltaX, int deltaY)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.sqrt(deltaX * deltaX + deltaY * 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
dragViewTo(ActivityInstrumentationTestCase test, View v, int gravity, int toX, int toY)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      */
dragViewTo(InstrumentationTestCase test, View v, int gravity, int toX, int toY)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.sqrt(deltaX * deltaX + deltaY * 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
dragViewToX(ActivityInstrumentationTestCase test, View v, int gravity, int toX)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      */
dragViewToX(InstrumentationTestCase test, View v, int gravity, int toX)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
dragViewToY(ActivityInstrumentationTestCase test, View v, int gravity, int toY)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      */
dragViewToY(InstrumentationTestCase test, View v, int gravity, int toY)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
drag(ActivityInstrumentationTestCase test, float fromX, float toX, float fromY, float toY, int stepCount)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      */
drag(InstrumentationTestCase test, float fromX, float toX, float fromY, float toY, int stepCount)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;
ViewStateSnapshot(ViewGroup viewGroup)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
equals(Object o)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
hashCode()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