1 /*
2  * Copyright (C) 2012 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 package com.android.uiautomator.core;
17 
18 import android.graphics.Rect;
19 import android.util.Log;
20 import android.view.accessibility.AccessibilityNodeInfo;
21 
22 /**
23  * UiScrollable is a {@link UiCollection} and provides support for searching
24  * for items in scrollable layout elements. This class can be used with
25  * horizontally or vertically scrollable controls.
26  * @since API Level 16
27  */
28 public class UiScrollable extends UiCollection {
29     private static final String LOG_TAG = UiScrollable.class.getSimpleName();
30 
31     // More steps slows the swipe and prevents contents from being flung too far
32     private static final int SCROLL_STEPS = 55;
33 
34     private static final int FLING_STEPS = 5;
35 
36     // Restrict a swipe's starting and ending points inside a 10% margin of the target
37     private static final double DEFAULT_SWIPE_DEADZONE_PCT = 0.1;
38 
39     // Limits the number of swipes/scrolls performed during a search
40     private static int mMaxSearchSwipes = 30;
41 
42     // Used in ScrollForward() and ScrollBackward() to determine swipe direction
43     private boolean mIsVerticalList = true;
44 
45     private double mSwipeDeadZonePercentage = DEFAULT_SWIPE_DEADZONE_PCT;
46 
47     /**
48      * Constructor.
49      *
50      * @param container a {@link UiSelector} selector to identify the scrollable
51      *     layout element.
52      * @since API Level 16
53      */
UiScrollable(UiSelector container)54     public UiScrollable(UiSelector container) {
55         // wrap the container selector with container so that QueryController can handle
56         // this type of enumeration search accordingly
57         super(container);
58     }
59 
60     /**
61      * Set the direction of swipes to be vertical when performing scroll actions.
62      * @return reference to itself
63      * @since API Level 16
64      */
setAsVerticalList()65     public UiScrollable setAsVerticalList() {
66         Tracer.trace();
67         mIsVerticalList = true;
68         return this;
69     }
70 
71     /**
72      * Set the direction of swipes to be horizontal when performing scroll actions.
73      * @return reference to itself
74      * @since API Level 16
75      */
setAsHorizontalList()76     public UiScrollable setAsHorizontalList() {
77         Tracer.trace();
78         mIsVerticalList = false;
79         return this;
80     }
81 
82     /**
83      * Used privately when performing swipe searches to decide if an element has become
84      * visible or not.
85      *
86      * @param selector
87      * @return true if found else false
88      * @since API Level 16
89      */
exists(UiSelector selector)90     protected boolean exists(UiSelector selector) {
91         if(getQueryController().findAccessibilityNodeInfo(selector) != null) {
92             return true;
93         }
94         return false;
95     }
96 
97     /**
98      * Searches for a child element in the present scrollable container.
99      * The search first looks for a child element that matches the selector
100      * you provided, then looks for the content-description in its children elements.
101      * If both search conditions are fulfilled, the method returns a {@ link UiObject}
102      * representing the element matching the selector (not the child element in its
103      * subhierarchy containing the content-description). By default, this method performs a
104      * scroll search.
105      * See {@link #getChildByDescription(UiSelector, String, boolean)}
106      *
107      * @param childPattern {@link UiSelector} for a child in a scollable layout element
108      * @param text Content-description to find in the children of
109      * the <code>childPattern</code> match
110      * @return {@link UiObject} representing the child element that matches the search conditions
111      * @throws UiObjectNotFoundException
112      * @since API Level 16
113      */
114     @Override
getChildByDescription(UiSelector childPattern, String text)115     public UiObject getChildByDescription(UiSelector childPattern, String text)
116             throws UiObjectNotFoundException {
117         Tracer.trace(childPattern, text);
118         return getChildByDescription(childPattern, text, true);
119     }
120 
121     /**
122      * Searches for a child element in the present scrollable container.
123      * The search first looks for a child element that matches the selector
124      * you provided, then looks for the content-description in its children elements.
125      * If both search conditions are fulfilled, the method returns a {@ link UiObject}
126      * representing the element matching the selector (not the child element in its
127      * subhierarchy containing the content-description).
128      *
129      * @param childPattern {@link UiSelector} for a child in a scollable layout element
130      * @param text Content-description to find in the children of
131      * the <code>childPattern</code> match (may be a partial match)
132      * @param allowScrollSearch set to true if scrolling is allowed
133      * @return {@link UiObject} representing the child element that matches the search conditions
134      * @throws UiObjectNotFoundException
135      * @since API Level 16
136      */
getChildByDescription(UiSelector childPattern, String text, boolean allowScrollSearch)137     public UiObject getChildByDescription(UiSelector childPattern, String text,
138             boolean allowScrollSearch) throws UiObjectNotFoundException {
139         Tracer.trace(childPattern, text, allowScrollSearch);
140         if (text != null) {
141             if (allowScrollSearch) {
142                 scrollIntoView(new UiSelector().descriptionContains(text));
143             }
144             return super.getChildByDescription(childPattern, text);
145         }
146         throw new UiObjectNotFoundException("for description= \"" + text + "\"");
147     }
148 
149     /**
150      * Searches for a child element in the present scrollable container that
151      * matches the selector you provided. The search is performed without
152      * scrolling and only on visible elements.
153      *
154      * @param childPattern {@link UiSelector} for a child in a scollable layout element
155      * @param instance int number representing the occurance of
156      * a <code>childPattern</code> match
157      * @return {@link UiObject} representing the child element that matches the search conditions
158      * @since API Level 16
159      */
160     @Override
getChildByInstance(UiSelector childPattern, int instance)161     public UiObject getChildByInstance(UiSelector childPattern, int instance)
162             throws UiObjectNotFoundException {
163         Tracer.trace(childPattern, instance);
164         UiSelector patternSelector = UiSelector.patternBuilder(getSelector(),
165                 UiSelector.patternBuilder(childPattern).instance(instance));
166         return new UiObject(patternSelector);
167     }
168 
169     /**
170      * Searches for a child element in the present scrollable
171      * container. The search first looks for a child element that matches the
172      * selector you provided, then looks for the text in its children elements.
173      * If both search conditions are fulfilled, the method returns a {@ link UiObject}
174      * representing the element matching the selector (not the child element in its
175      * subhierarchy containing the text). By default, this method performs a
176      * scroll search.
177      * See {@link #getChildByText(UiSelector, String, boolean)}
178      *
179      * @param childPattern {@link UiSelector} selector for a child in a scrollable layout element
180      * @param text String to find in the children of the <code>childPattern</code> match
181      * @return {@link UiObject} representing the child element that matches the search conditions
182      * @throws UiObjectNotFoundException
183      * @since API Level 16
184      */
185     @Override
getChildByText(UiSelector childPattern, String text)186     public UiObject getChildByText(UiSelector childPattern, String text)
187             throws UiObjectNotFoundException {
188         Tracer.trace(childPattern, text);
189         return getChildByText(childPattern, text, true);
190     }
191 
192     /**
193      * Searches for a child element in the present scrollable container. The
194      * search first looks for a child element that matches the
195      * selector you provided, then looks for the text in its children elements.
196      * If both search conditions are fulfilled, the method returns a {@ link UiObject}
197      * representing the element matching the selector (not the child element in its
198      * subhierarchy containing the text).
199      *
200      * @param childPattern {@link UiSelector} selector for a child in a scrollable layout element
201      * @param text String to find in the children of the <code>childPattern</code> match
202      * @param allowScrollSearch set to true if scrolling is allowed
203      * @return {@link UiObject} representing the child element that matches the search conditions
204      * @throws UiObjectNotFoundException
205      * @since API Level 16
206      */
getChildByText(UiSelector childPattern, String text, boolean allowScrollSearch)207     public UiObject getChildByText(UiSelector childPattern, String text, boolean allowScrollSearch)
208             throws UiObjectNotFoundException {
209         Tracer.trace(childPattern, text, allowScrollSearch);
210         if (text != null) {
211             if (allowScrollSearch) {
212                 scrollIntoView(new UiSelector().text(text));
213             }
214             return super.getChildByText(childPattern, text);
215         }
216         throw new UiObjectNotFoundException("for text= \"" + text + "\"");
217     }
218 
219     /**
220      * Performs a forward scroll action on the scrollable layout element until
221      * the content-description is found, or until swipe attempts have been exhausted.
222      * See {@link #setMaxSearchSwipes(int)}
223      *
224      * @param text content-description to find within the contents of this scrollable layout element.
225      * @return true if item is found; else, false
226      * @since API Level 16
227      */
scrollDescriptionIntoView(String text)228     public boolean scrollDescriptionIntoView(String text) throws UiObjectNotFoundException {
229         Tracer.trace(text);
230         return scrollIntoView(new UiSelector().description(text));
231     }
232 
233     /**
234      * Perform a forward scroll action to move through the scrollable layout element until
235      * a visible item that matches the {@link UiObject} is found.
236      *
237      * @param obj {@link UiObject}
238      * @return true if the item was found and now is in view else false
239      * @since API Level 16
240      */
scrollIntoView(UiObject obj)241     public boolean scrollIntoView(UiObject obj) throws UiObjectNotFoundException {
242         Tracer.trace(obj.getSelector());
243         return scrollIntoView(obj.getSelector());
244     }
245 
246     /**
247      * Perform a scroll forward action to move through the scrollable layout
248      * element until a visible item that matches the selector is found.
249      *
250      * See {@link #scrollDescriptionIntoView(String)} and {@link #scrollTextIntoView(String)}.
251      *
252      * @param selector {@link UiSelector} selector
253      * @return true if the item was found and now is in view; else, false
254      * @since API Level 16
255      */
scrollIntoView(UiSelector selector)256     public boolean scrollIntoView(UiSelector selector) throws UiObjectNotFoundException {
257         Tracer.trace(selector);
258         // if we happen to be on top of the text we want then return here
259         UiSelector childSelector = getSelector().childSelector(selector);
260         if (exists(childSelector)) {
261             return (true);
262         } else {
263             // we will need to reset the search from the beginning to start search
264             scrollToBeginning(mMaxSearchSwipes);
265             if (exists(childSelector)) {
266                 return (true);
267             }
268             for (int x = 0; x < mMaxSearchSwipes; x++) {
269                 boolean scrolled = scrollForward();
270                 if(exists(childSelector)) {
271                     return true;
272                 }
273                 if (!scrolled) {
274                     return false;
275                 }
276             }
277         }
278         return false;
279     }
280 
281     /**
282      * Scrolls forward until the UiObject is fully visible in the scrollable container.
283      * Use this method to make sure that the child item's edges are not offscreen.
284      *
285      * @param childObject {@link UiObject} representing the child element
286      * @return true if the child element is already fully visible, or
287      * if the method scrolled successfully until the child became fully visible;
288      * otherwise, false if the attempt to scroll failed.
289      * @throws UiObjectNotFoundException
290      * @hide
291      */
ensureFullyVisible(UiObject childObject)292     public boolean ensureFullyVisible(UiObject childObject) throws UiObjectNotFoundException {
293         Rect actual = childObject.getBounds();
294         Rect visible = childObject.getVisibleBounds();
295         if (visible.width() * visible.height() == actual.width() * actual.height()) {
296             // area match, item fully visible
297             return true;
298         }
299         boolean shouldSwipeForward = false;
300         if (mIsVerticalList) {
301             // if list is vertical, matching top edge implies obscured bottom edge
302             // so we need to scroll list forward
303             shouldSwipeForward = actual.top == visible.top;
304         } else {
305             // if list is horizontal, matching left edge implies obscured right edge,
306             // so we need to scroll list forward
307             shouldSwipeForward = actual.left == visible.left;
308         }
309         if (mIsVerticalList) {
310             if (shouldSwipeForward) {
311                 return swipeUp(10);
312             } else {
313                 return swipeDown(10);
314             }
315         } else {
316             if (shouldSwipeForward) {
317                 return swipeLeft(10);
318             } else {
319                 return swipeRight(10);
320             }
321         }
322     }
323 
324     /**
325      * Performs a forward scroll action on the scrollable layout element until
326      * the text you provided is visible, or until swipe attempts have been exhausted.
327      * See {@link #setMaxSearchSwipes(int)}
328      *
329      * @param text test to look for
330      * @return true if item is found; else, false
331      * @since API Level 16
332      */
scrollTextIntoView(String text)333     public boolean scrollTextIntoView(String text) throws UiObjectNotFoundException {
334         Tracer.trace(text);
335         return scrollIntoView(new UiSelector().text(text));
336     }
337 
338     /**
339      * Sets the maximum number of scrolls allowed when performing a
340      * scroll action in search of a child element.
341      * See {@link #getChildByDescription(UiSelector, String)} and
342      * {@link #getChildByText(UiSelector, String)}.
343      *
344      * @param swipes the number of search swipes to perform until giving up
345      * @return reference to itself
346      * @since API Level 16
347      */
setMaxSearchSwipes(int swipes)348     public UiScrollable setMaxSearchSwipes(int swipes) {
349         Tracer.trace(swipes);
350         mMaxSearchSwipes = swipes;
351         return this;
352     }
353 
354     /**
355      * Gets the maximum number of scrolls allowed when performing a
356      * scroll action in search of a child element.
357      * See {@link #getChildByDescription(UiSelector, String)} and
358      * {@link #getChildByText(UiSelector, String)}.
359      *
360      * @return max the number of search swipes to perform until giving up
361      * @since API Level 16
362      */
getMaxSearchSwipes()363     public int getMaxSearchSwipes() {
364         Tracer.trace();
365         return mMaxSearchSwipes;
366     }
367 
368     /**
369      * Performs a forward fling with the default number of fling steps (5).
370      * If the swipe direction is set to vertical, then the swipes will be
371      * performed from bottom to top. If the swipe
372      * direction is set to horizontal, then the swipes will be performed from
373      * right to left. Make sure to take into account devices configured with
374      * right-to-left languages like Arabic and Hebrew.
375      *
376      * @return true if scrolled, false if can't scroll anymore
377      * @since API Level 16
378      */
flingForward()379     public boolean flingForward() throws UiObjectNotFoundException {
380         Tracer.trace();
381         return scrollForward(FLING_STEPS);
382     }
383 
384     /**
385      * Performs a forward scroll with the default number of scroll steps (55).
386      * If the swipe direction is set to vertical,
387      * then the swipes will be performed from bottom to top. If the swipe
388      * direction is set to horizontal, then the swipes will be performed from
389      * right to left. Make sure to take into account devices configured with
390      * right-to-left languages like Arabic and Hebrew.
391      *
392      * @return true if scrolled, false if can't scroll anymore
393      * @since API Level 16
394      */
scrollForward()395     public boolean scrollForward() throws UiObjectNotFoundException {
396         Tracer.trace();
397         return scrollForward(SCROLL_STEPS);
398     }
399 
400     /**
401      * Performs a forward scroll. If the swipe direction is set to vertical,
402      * then the swipes will be performed from bottom to top. If the swipe
403      * direction is set to horizontal, then the swipes will be performed from
404      * right to left. Make sure to take into account devices configured with
405      * right-to-left languages like Arabic and Hebrew.
406      *
407      * @param steps number of steps. Use this to control the speed of the scroll action
408      * @return true if scrolled, false if can't scroll anymore
409      * @since API Level 16
410      */
scrollForward(int steps)411     public boolean scrollForward(int steps) throws UiObjectNotFoundException {
412         Tracer.trace(steps);
413         Log.d(LOG_TAG, "scrollForward() on selector = " + getSelector());
414         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
415         if(node == null) {
416             throw new UiObjectNotFoundException(getSelector().toString());
417         }
418         Rect rect = new Rect();
419         node.getBoundsInScreen(rect);
420 
421         int downX = 0;
422         int downY = 0;
423         int upX = 0;
424         int upY = 0;
425 
426         // scrolling is by default assumed vertically unless the object is explicitly
427         // set otherwise by setAsHorizontalContainer()
428         if(mIsVerticalList) {
429             int swipeAreaAdjust = (int)(rect.height() * getSwipeDeadZonePercentage());
430             // scroll vertically: swipe down -> up
431             downX = rect.centerX();
432             downY = rect.bottom - swipeAreaAdjust;
433             upX = rect.centerX();
434             upY = rect.top + swipeAreaAdjust;
435         } else {
436             int swipeAreaAdjust = (int)(rect.width() * getSwipeDeadZonePercentage());
437             // scroll horizontally: swipe right -> left
438             // TODO: Assuming device is not in right to left language
439             downX = rect.right - swipeAreaAdjust;
440             downY = rect.centerY();
441             upX = rect.left + swipeAreaAdjust;
442             upY = rect.centerY();
443         }
444         return getInteractionController().scrollSwipe(downX, downY, upX, upY, steps);
445     }
446 
447     /**
448      * Performs a backwards fling action with the default number of fling
449      * steps (5). If the swipe direction is set to vertical,
450      * then the swipe will be performed from top to bottom. If the swipe
451      * direction is set to horizontal, then the swipes will be performed from
452      * left to right. Make sure to take into account devices configured with
453      * right-to-left languages like Arabic and Hebrew.
454      *
455      * @return true if scrolled, and false if can't scroll anymore
456      * @since API Level 16
457      */
flingBackward()458     public boolean flingBackward() throws UiObjectNotFoundException {
459         Tracer.trace();
460         return scrollBackward(FLING_STEPS);
461     }
462 
463     /**
464      * Performs a backward scroll with the default number of scroll steps (55).
465      * If the swipe direction is set to vertical,
466      * then the swipes will be performed from top to bottom. If the swipe
467      * direction is set to horizontal, then the swipes will be performed from
468      * left to right. Make sure to take into account devices configured with
469      * right-to-left languages like Arabic and Hebrew.
470      *
471      * @return true if scrolled, and false if can't scroll anymore
472      * @since API Level 16
473      */
scrollBackward()474     public boolean scrollBackward() throws UiObjectNotFoundException {
475         Tracer.trace();
476         return scrollBackward(SCROLL_STEPS);
477     }
478 
479     /**
480      * Performs a backward scroll. If the swipe direction is set to vertical,
481      * then the swipes will be performed from top to bottom. If the swipe
482      * direction is set to horizontal, then the swipes will be performed from
483      * left to right. Make sure to take into account devices configured with
484      * right-to-left languages like Arabic and Hebrew.
485      *
486      * @param steps number of steps. Use this to control the speed of the scroll action.
487      * @return true if scrolled, false if can't scroll anymore
488      * @since API Level 16
489      */
scrollBackward(int steps)490     public boolean scrollBackward(int steps) throws UiObjectNotFoundException {
491         Tracer.trace(steps);
492         Log.d(LOG_TAG, "scrollBackward() on selector = " + getSelector());
493         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
494         if (node == null) {
495             throw new UiObjectNotFoundException(getSelector().toString());
496         }
497         Rect rect = new Rect();
498         node.getBoundsInScreen(rect);
499 
500         int downX = 0;
501         int downY = 0;
502         int upX = 0;
503         int upY = 0;
504 
505         // scrolling is by default assumed vertically unless the object is explicitly
506         // set otherwise by setAsHorizontalContainer()
507         if(mIsVerticalList) {
508             int swipeAreaAdjust = (int)(rect.height() * getSwipeDeadZonePercentage());
509             Log.d(LOG_TAG, "scrollToBegining() using vertical scroll");
510             // scroll vertically: swipe up -> down
511             downX = rect.centerX();
512             downY = rect.top + swipeAreaAdjust;
513             upX = rect.centerX();
514             upY = rect.bottom - swipeAreaAdjust;
515         } else {
516             int swipeAreaAdjust = (int)(rect.width() * getSwipeDeadZonePercentage());
517             Log.d(LOG_TAG, "scrollToBegining() using hotizontal scroll");
518             // scroll horizontally: swipe left -> right
519             // TODO: Assuming device is not in right to left language
520             downX = rect.left + swipeAreaAdjust;
521             downY = rect.centerY();
522             upX = rect.right - swipeAreaAdjust;
523             upY = rect.centerY();
524         }
525         return getInteractionController().scrollSwipe(downX, downY, upX, upY, steps);
526     }
527 
528     /**
529      * Scrolls to the beginning of a scrollable layout element. The beginning
530      * can be at the  top-most edge in the case of vertical controls, or the
531      * left-most edge for horizontal controls. Make sure to take into account
532      * devices configured with right-to-left languages like Arabic and Hebrew.
533      *
534      * @param steps use steps to control the speed, so that it may be a scroll, or fling
535      * @return true on scrolled else false
536      * @since API Level 16
537      */
scrollToBeginning(int maxSwipes, int steps)538     public boolean scrollToBeginning(int maxSwipes, int steps) throws UiObjectNotFoundException {
539         Tracer.trace(maxSwipes, steps);
540         Log.d(LOG_TAG, "scrollToBeginning() on selector = " + getSelector());
541         // protect against potential hanging and return after preset attempts
542         for(int x = 0; x < maxSwipes; x++) {
543             if(!scrollBackward(steps)) {
544                 break;
545             }
546         }
547         return true;
548     }
549 
550     /**
551      * Scrolls to the beginning of a scrollable layout element. The beginning
552      * can be at the  top-most edge in the case of vertical controls, or the
553      * left-most edge for horizontal controls. Make sure to take into account
554      * devices configured with right-to-left languages like Arabic and Hebrew.
555      *
556      * @param maxSwipes
557      * @return true on scrolled else false
558      * @since API Level 16
559      */
scrollToBeginning(int maxSwipes)560     public boolean scrollToBeginning(int maxSwipes) throws UiObjectNotFoundException {
561         Tracer.trace(maxSwipes);
562         return scrollToBeginning(maxSwipes, SCROLL_STEPS);
563     }
564 
565     /**
566      * Performs a fling gesture to reach the beginning of a scrollable layout element.
567      * The beginning can be at the  top-most edge in the case of vertical controls, or
568      * the left-most edge for horizontal controls. Make sure to take into
569      * account devices configured with right-to-left languages like Arabic and Hebrew.
570      *
571      * @param maxSwipes
572      * @return true on scrolled else false
573      * @since API Level 16
574      */
flingToBeginning(int maxSwipes)575     public boolean flingToBeginning(int maxSwipes) throws UiObjectNotFoundException {
576         Tracer.trace(maxSwipes);
577         return scrollToBeginning(maxSwipes, FLING_STEPS);
578     }
579 
580     /**
581      * Scrolls to the end of a scrollable layout element. The end can be at the
582      * bottom-most edge in the case of vertical controls, or the right-most edge for
583      * horizontal controls. Make sure to take into account devices configured with
584      * right-to-left languages like Arabic and Hebrew.
585      *
586      * @param steps use steps to control the speed, so that it may be a scroll, or fling
587      * @return true on scrolled else false
588      * @since API Level 16
589      */
scrollToEnd(int maxSwipes, int steps)590     public boolean scrollToEnd(int maxSwipes, int steps) throws UiObjectNotFoundException {
591         Tracer.trace(maxSwipes, steps);
592         // protect against potential hanging and return after preset attempts
593         for(int x = 0; x < maxSwipes; x++) {
594             if(!scrollForward(steps)) {
595                 break;
596             }
597         }
598         return true;
599     }
600 
601     /**
602      * Scrolls to the end of a scrollable layout element. The end can be at the
603      * bottom-most edge in the case of vertical controls, or the right-most edge for
604      * horizontal controls. Make sure to take into account devices configured with
605      * right-to-left languages like Arabic and Hebrew.
606      *
607      * @param maxSwipes
608      * @return true on scrolled, else false
609      * @since API Level 16
610      */
scrollToEnd(int maxSwipes)611     public boolean scrollToEnd(int maxSwipes) throws UiObjectNotFoundException {
612         Tracer.trace(maxSwipes);
613         return scrollToEnd(maxSwipes, SCROLL_STEPS);
614     }
615 
616     /**
617      * Performs a fling gesture to reach the end of a scrollable layout element.
618      * The end can be at the  bottom-most edge in the case of vertical controls, or
619      * the right-most edge for horizontal controls. Make sure to take into
620      * account devices configured with right-to-left languages like Arabic and Hebrew.
621      *
622      * @param maxSwipes
623      * @return true on scrolled, else false
624      * @since API Level 16
625      */
flingToEnd(int maxSwipes)626     public boolean flingToEnd(int maxSwipes) throws UiObjectNotFoundException {
627         Tracer.trace(maxSwipes);
628         return scrollToEnd(maxSwipes, FLING_STEPS);
629     }
630 
631     /**
632      * Returns the percentage of a widget's size that's considered as a no-touch
633      * zone when swiping. The no-touch zone is set as a percentage of a widget's total
634      * width or height, denoting a margin around the swipable area of the widget.
635      * Swipes must start and end inside this margin. This is important when the
636      * widget being swiped may not respond to the swipe if started at a point
637      * too near to the edge. The default is 10% from either edge.
638      *
639      * @return a value between 0 and 1
640      * @since API Level 16
641      */
getSwipeDeadZonePercentage()642     public double getSwipeDeadZonePercentage() {
643         Tracer.trace();
644         return mSwipeDeadZonePercentage;
645     }
646 
647     /**
648      * Sets the percentage of a widget's size that's considered as no-touch
649      * zone when swiping.
650      * The no-touch zone is set as percentage of a widget's total width or height,
651      * denoting a margin around the swipable area of the widget. Swipes must
652      * always start and end inside this margin. This is important when the
653      * widget being swiped may not respond to the swipe if started at a point
654      * too near to the edge. The default is 10% from either edge.
655      *
656      * @param swipeDeadZonePercentage is a value between 0 and 1
657      * @return reference to itself
658      * @since API Level 16
659      */
setSwipeDeadZonePercentage(double swipeDeadZonePercentage)660     public UiScrollable setSwipeDeadZonePercentage(double swipeDeadZonePercentage) {
661         Tracer.trace(swipeDeadZonePercentage);
662         mSwipeDeadZonePercentage = swipeDeadZonePercentage;
663         return this;
664     }
665 }
666