UiScrollable.java revision 18b892c723e812a7e111f102d2b0c0782b116bb6
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 */
16package com.android.uiautomator.core;
17
18import android.graphics.Rect;
19import android.util.Log;
20import 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 */
28public 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     */
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     */
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     */
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     */
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
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     */
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
660    public UiScrollable setSwipeDeadZonePercentage(double swipeDeadZonePercentage) {
661        Tracer.trace(swipeDeadZonePercentage);
662        mSwipeDeadZonePercentage = swipeDeadZonePercentage;
663        return this;
664    }
665}
666