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