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 * @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
31public 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     */
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     */
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     */
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     */
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
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     */
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
663    public UiScrollable setSwipeDeadZonePercentage(double swipeDeadZonePercentage) {
664        Tracer.trace(swipeDeadZonePercentage);
665        mSwipeDeadZonePercentage = swipeDeadZonePercentage;
666        return this;
667    }
668}
669