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
17package com.android.uiautomator.core;
18
19import android.graphics.Point;
20import android.graphics.Rect;
21import android.os.SystemClock;
22import android.util.Log;
23import android.view.KeyEvent;
24import android.view.MotionEvent.PointerCoords;
25import android.view.accessibility.AccessibilityNodeInfo;
26
27/**
28 * A UiObject is a representation of a view. It is not in any way directly bound to a
29 * view as an object reference. A UiObject contains information to help it
30 * locate a matching view at runtime based on the {@link UiSelector} properties specified in
31 * its constructor. Once you create an instance of a UiObject, it can
32 * be reused for different views that match the selector criteria.
33 * @since API Level 16
34 */
35public class UiObject {
36    private static final String LOG_TAG = UiObject.class.getSimpleName();
37    /**
38     * @since API Level 16
39     * @deprecated use {@link Configurator#setWaitForSelectorTimeout(long)}
40     **/
41    @Deprecated
42    protected static final long WAIT_FOR_SELECTOR_TIMEOUT = 10 * 1000;
43    /**
44     * @since API Level 16
45     **/
46    protected static final long WAIT_FOR_SELECTOR_POLL = 1000;
47    // set a default timeout to 5.5s, since ANR threshold is 5s
48    /**
49     * @since API Level 16
50     **/
51    protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500;
52    /**
53     * @since API Level 16
54     **/
55    protected static final int SWIPE_MARGIN_LIMIT = 5;
56    /**
57     * @since API Level 17
58     * @deprecated use {@link Configurator#setScrollAcknowledgmentTimeout(long)}
59     **/
60    @Deprecated
61    protected static final long WAIT_FOR_EVENT_TMEOUT = 3 * 1000;
62    /**
63     * @since API Level 18
64     **/
65    protected static final int FINGER_TOUCH_HALF_WIDTH = 20;
66
67    private final UiSelector mSelector;
68
69    private final Configurator mConfig = Configurator.getInstance();
70
71    /**
72     * Constructs a UiObject to represent a view that matches the specified
73     * selector criteria.
74     * @param selector
75     * @since API Level 16
76     */
77    public UiObject(UiSelector selector) {
78        mSelector = selector;
79    }
80
81    /**
82     * Debugging helper. A test can dump the properties of a selector as a string
83     * to its logs if needed. <code>getSelector().toString();</code>
84     *
85     * @return {@link UiSelector}
86     * @since API Level 16
87     */
88    public final UiSelector getSelector() {
89        Tracer.trace();
90        return new UiSelector(mSelector);
91    }
92
93    /**
94     * Retrieves the {@link QueryController} to translate a {@link UiSelector} selector
95     * into an {@link AccessibilityNodeInfo}.
96     *
97     * @return {@link QueryController}
98     */
99    QueryController getQueryController() {
100        return UiDevice.getInstance().getAutomatorBridge().getQueryController();
101    }
102
103    /**
104     * Retrieves the {@link InteractionController} to perform finger actions such as tapping,
105     * swiping, or entering text.
106     *
107     * @return {@link InteractionController}
108     */
109    InteractionController getInteractionController() {
110        return UiDevice.getInstance().getAutomatorBridge().getInteractionController();
111    }
112
113    /**
114     * Creates a new UiObject for a child view that is under the present UiObject.
115     *
116     * @param selector for child view to match
117     * @return a new UiObject representing the child view
118     * @since API Level 16
119     */
120    public UiObject getChild(UiSelector selector) throws UiObjectNotFoundException {
121        Tracer.trace(selector);
122        return new UiObject(getSelector().childSelector(selector));
123    }
124
125    /**
126     * Creates a new UiObject for a sibling view or a child of the sibling view,
127     * relative to the present UiObject.
128     *
129     * @param selector for a sibling view or children of the sibling view
130     * @return a new UiObject representing the matched view
131     * @throws UiObjectNotFoundException
132     * @since API Level 16
133     */
134    public UiObject getFromParent(UiSelector selector) throws UiObjectNotFoundException {
135        Tracer.trace(selector);
136        return new UiObject(getSelector().fromParent(selector));
137    }
138
139    /**
140     * Counts the child views immediately under the present UiObject.
141     *
142     * @return the count of child views.
143     * @throws UiObjectNotFoundException
144     * @since API Level 16
145     */
146    public int getChildCount() throws UiObjectNotFoundException {
147        Tracer.trace();
148        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
149        if(node == null) {
150            throw new UiObjectNotFoundException(getSelector().toString());
151        }
152        return node.getChildCount();
153    }
154
155    /**
156     * Finds a matching UI element in the accessibility hierarchy, by
157     * using the selector for this UiObject.
158     *
159     * @param timeout in milliseconds
160     * @return AccessibilityNodeInfo if found else null
161     * @since API Level 16
162     */
163    protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout) {
164        AccessibilityNodeInfo node = null;
165        long startMills = SystemClock.uptimeMillis();
166        long currentMills = 0;
167        while (currentMills <= timeout) {
168            node = getQueryController().findAccessibilityNodeInfo(getSelector());
169            if (node != null) {
170                break;
171            } else {
172                // does nothing if we're reentering another runWatchers()
173                UiDevice.getInstance().runWatchers();
174            }
175            currentMills = SystemClock.uptimeMillis() - startMills;
176            if(timeout > 0) {
177                SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
178            }
179        }
180        return node;
181    }
182
183    /**
184     * Drags this object to a destination UiObject.
185     * The number of steps specified in your input parameter can influence the
186     * drag speed, and varying speeds may impact the results. Consider
187     * evaluating different speeds when using this method in your tests.
188     *
189     * @param destObj the destination UiObject.
190     * @param steps usually 40 steps. You can increase or decrease the steps to change the speed.
191     * @return true if successful
192     * @throws UiObjectNotFoundException
193     * @since API Level 18
194     */
195    public boolean dragTo(UiObject destObj, int steps) throws UiObjectNotFoundException {
196        Rect srcRect = getVisibleBounds();
197        Rect dstRect = destObj.getVisibleBounds();
198        return getInteractionController().swipe(srcRect.centerX(), srcRect.centerY(),
199                dstRect.centerX(), dstRect.centerY(), steps, true);
200    }
201
202    /**
203     * Drags this object to arbitrary coordinates.
204     * The number of steps specified in your input parameter can influence the
205     * drag speed, and varying speeds may impact the results. Consider
206     * evaluating different speeds when using this method in your tests.
207     *
208     * @param destX the X-axis coordinate.
209     * @param destY the Y-axis coordinate.
210     * @param steps usually 40 steps. You can increase or decrease the steps to change the speed.
211     * @return true if successful
212     * @throws UiObjectNotFoundException
213     * @since API Level 18
214     */
215    public boolean dragTo(int destX, int destY, int steps) throws UiObjectNotFoundException {
216        Rect srcRect = getVisibleBounds();
217        return getInteractionController().swipe(srcRect.centerX(), srcRect.centerY(), destX, destY,
218                steps, true);
219    }
220
221    /**
222     * Performs the swipe up action on the UiObject.
223     * See also:
224     * <ul>
225     * <li>{@link UiScrollable#scrollToBeginning(int)}</li>
226     * <li>{@link UiScrollable#scrollToEnd(int)}</li>
227     * <li>{@link UiScrollable#scrollBackward()}</li>
228     * <li>{@link UiScrollable#scrollForward()}</li>
229     * </ul>
230     *
231     * @param steps indicates the number of injected move steps into the system. Steps are
232     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
233     * @return true of successful
234     * @throws UiObjectNotFoundException
235     * @since API Level 16
236     */
237    public boolean swipeUp(int steps) throws UiObjectNotFoundException {
238        Tracer.trace(steps);
239        Rect rect = getVisibleBounds();
240        if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
241            return false; // too small to swipe
242        return getInteractionController().swipe(rect.centerX(),
243                rect.bottom - SWIPE_MARGIN_LIMIT, rect.centerX(), rect.top + SWIPE_MARGIN_LIMIT,
244                steps);
245    }
246
247    /**
248     * Performs the swipe down action on the UiObject.
249     * The swipe gesture can be performed over any surface. The targeted
250     * UI element does not need to be scrollable.
251     * See also:
252     * <ul>
253     * <li>{@link UiScrollable#scrollToBeginning(int)}</li>
254     * <li>{@link UiScrollable#scrollToEnd(int)}</li>
255     * <li>{@link UiScrollable#scrollBackward()}</li>
256     * <li>{@link UiScrollable#scrollForward()}</li>
257     * </ul>
258     *
259     * @param steps indicates the number of injected move steps into the system. Steps are
260     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
261     * @return true if successful
262     * @throws UiObjectNotFoundException
263     * @since API Level 16
264     */
265    public boolean swipeDown(int steps) throws UiObjectNotFoundException {
266        Tracer.trace(steps);
267        Rect rect = getVisibleBounds();
268        if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
269            return false; // too small to swipe
270        return getInteractionController().swipe(rect.centerX(),
271                rect.top + SWIPE_MARGIN_LIMIT, rect.centerX(),
272                rect.bottom - SWIPE_MARGIN_LIMIT, steps);
273    }
274
275    /**
276     * Performs the swipe left action on the UiObject.
277     * The swipe gesture can be performed over any surface. The targeted
278     * UI element does not need to be scrollable.
279     * See also:
280     * <ul>
281     * <li>{@link UiScrollable#scrollToBeginning(int)}</li>
282     * <li>{@link UiScrollable#scrollToEnd(int)}</li>
283     * <li>{@link UiScrollable#scrollBackward()}</li>
284     * <li>{@link UiScrollable#scrollForward()}</li>
285     * </ul>
286     *
287     * @param steps indicates the number of injected move steps into the system. Steps are
288     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
289     * @return true if successful
290     * @throws UiObjectNotFoundException
291     * @since API Level 16
292     */
293    public boolean swipeLeft(int steps) throws UiObjectNotFoundException {
294        Tracer.trace(steps);
295        Rect rect = getVisibleBounds();
296        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
297            return false; // too small to swipe
298        return getInteractionController().swipe(rect.right - SWIPE_MARGIN_LIMIT,
299                rect.centerY(), rect.left + SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
300    }
301
302    /**
303     * Performs the swipe right action on the UiObject.
304     * The swipe gesture can be performed over any surface. The targeted
305     * UI element does not need to be scrollable.
306     * See also:
307     * <ul>
308     * <li>{@link UiScrollable#scrollToBeginning(int)}</li>
309     * <li>{@link UiScrollable#scrollToEnd(int)}</li>
310     * <li>{@link UiScrollable#scrollBackward()}</li>
311     * <li>{@link UiScrollable#scrollForward()}</li>
312     * </ul>
313     *
314     * @param steps indicates the number of injected move steps into the system. Steps are
315     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
316     * @return true if successful
317     * @throws UiObjectNotFoundException
318     * @since API Level 16
319     */
320    public boolean swipeRight(int steps) throws UiObjectNotFoundException {
321        Tracer.trace(steps);
322        Rect rect = getVisibleBounds();
323        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
324            return false; // too small to swipe
325        return getInteractionController().swipe(rect.left + SWIPE_MARGIN_LIMIT,
326                rect.centerY(), rect.right - SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
327    }
328
329    /**
330     * Finds the visible bounds of a partially visible UI element
331     *
332     * @param node
333     * @return null if node is null, else a Rect containing visible bounds
334     */
335    private Rect getVisibleBounds(AccessibilityNodeInfo node) {
336        if (node == null) {
337            return null;
338        }
339
340        // targeted node's bounds
341        int w = UiDevice.getInstance().getDisplayWidth();
342        int h = UiDevice.getInstance().getDisplayHeight();
343        Rect nodeRect = AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node, w, h);
344
345        // is the targeted node within a scrollable container?
346        AccessibilityNodeInfo scrollableParentNode = getScrollableParent(node);
347        if(scrollableParentNode == null) {
348            // nothing to adjust for so return the node's Rect as is
349            return nodeRect;
350        }
351
352        // Scrollable parent's visible bounds
353        Rect parentRect = AccessibilityNodeInfoHelper
354                .getVisibleBoundsInScreen(scrollableParentNode, w, h);
355        // adjust for partial clipping of targeted by parent node if required
356        nodeRect.intersect(parentRect);
357        return nodeRect;
358    }
359
360    /**
361     * Walks up the layout hierarchy to find a scrollable parent. A scrollable parent
362     * indicates that this node might be in a container where it is partially
363     * visible due to scrolling. In this case, its clickable center might not be visible and
364     * the click coordinates should be adjusted.
365     *
366     * @param node
367     * @return The accessibility node info.
368     */
369    private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) {
370        AccessibilityNodeInfo parent = node;
371        while(parent != null) {
372            parent = parent.getParent();
373            if (parent != null && parent.isScrollable()) {
374                return parent;
375            }
376        }
377        return null;
378    }
379
380    /**
381     * Performs a click at the center of the visible bounds of the UI element represented
382     * by this UiObject.
383     *
384     * @return true id successful else false
385     * @throws UiObjectNotFoundException
386     * @since API Level 16
387     */
388    public boolean click() throws UiObjectNotFoundException {
389        Tracer.trace();
390        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
391        if(node == null) {
392            throw new UiObjectNotFoundException(getSelector().toString());
393        }
394        Rect rect = getVisibleBounds(node);
395        return getInteractionController().clickAndSync(rect.centerX(), rect.centerY(),
396                mConfig.getActionAcknowledgmentTimeout());
397    }
398
399    /**
400     * Waits for window transitions that would typically take longer than the
401     * usual default timeouts.
402     * See {@link #clickAndWaitForNewWindow(long)}
403     *
404     * @return true if the event was triggered, else false
405     * @throws UiObjectNotFoundException
406     * @since API Level 16
407     */
408    public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException {
409        Tracer.trace();
410        return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT);
411    }
412
413    /**
414     * Performs a click at the center of the visible bounds of the UI element represented
415     * by this UiObject and waits for window transitions.
416     *
417     * This method differ from {@link UiObject#click()} only in that this method waits for a
418     * a new window transition as a result of the click. Some examples of a window transition:
419     * <li>launching a new activity</li>
420     * <li>bringing up a pop-up menu</li>
421     * <li>bringing up a dialog</li>
422     *
423     * @param timeout timeout before giving up on waiting for a new window
424     * @return true if the event was triggered, else false
425     * @throws UiObjectNotFoundException
426     * @since API Level 16
427     */
428    public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException {
429        Tracer.trace(timeout);
430        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
431        if(node == null) {
432            throw new UiObjectNotFoundException(getSelector().toString());
433        }
434        Rect rect = getVisibleBounds(node);
435        return getInteractionController().clickAndWaitForNewWindow(rect.centerX(), rect.centerY(),
436                mConfig.getActionAcknowledgmentTimeout());
437    }
438
439    /**
440     * Clicks the top and left corner of the UI element
441     *
442     * @return true on success
443     * @throws UiObjectNotFoundException
444     * @since API Level 16
445     */
446    public boolean clickTopLeft() throws UiObjectNotFoundException {
447        Tracer.trace();
448        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
449        if(node == null) {
450            throw new UiObjectNotFoundException(getSelector().toString());
451        }
452        Rect rect = getVisibleBounds(node);
453        return getInteractionController().clickNoSync(rect.left + 5, rect.top + 5);
454    }
455
456    /**
457     * Long clicks bottom and right corner of the UI element
458     *
459     * @return true if operation was successful
460     * @throws UiObjectNotFoundException
461     * @since API Level 16
462     */
463    public boolean longClickBottomRight() throws UiObjectNotFoundException  {
464        Tracer.trace();
465        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
466        if(node == null) {
467            throw new UiObjectNotFoundException(getSelector().toString());
468        }
469        Rect rect = getVisibleBounds(node);
470        return getInteractionController().longTapNoSync(rect.right - 5, rect.bottom - 5);
471    }
472
473    /**
474     * Clicks the bottom and right corner of the UI element
475     *
476     * @return true on success
477     * @throws UiObjectNotFoundException
478     * @since API Level 16
479     */
480    public boolean clickBottomRight() throws UiObjectNotFoundException {
481        Tracer.trace();
482        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
483        if(node == null) {
484            throw new UiObjectNotFoundException(getSelector().toString());
485        }
486        Rect rect = getVisibleBounds(node);
487        return getInteractionController().clickNoSync(rect.right - 5, rect.bottom - 5);
488    }
489
490    /**
491     * Long clicks the center of the visible bounds of the UI element
492     *
493     * @return true if operation was successful
494     * @throws UiObjectNotFoundException
495     * @since API Level 16
496     */
497    public boolean longClick() throws UiObjectNotFoundException  {
498        Tracer.trace();
499        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
500        if(node == null) {
501            throw new UiObjectNotFoundException(getSelector().toString());
502        }
503        Rect rect = getVisibleBounds(node);
504        return getInteractionController().longTapNoSync(rect.centerX(), rect.centerY());
505    }
506
507    /**
508     * Long clicks on the top and left corner of the UI element
509     *
510     * @return true if operation was successful
511     * @throws UiObjectNotFoundException
512     * @since API Level 16
513     */
514    public boolean longClickTopLeft() throws UiObjectNotFoundException {
515        Tracer.trace();
516        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
517        if(node == null) {
518            throw new UiObjectNotFoundException(getSelector().toString());
519        }
520        Rect rect = getVisibleBounds(node);
521        return getInteractionController().longTapNoSync(rect.left + 5, rect.top + 5);
522    }
523
524    /**
525     * Reads the <code>text</code> property of the UI element
526     *
527     * @return text value of the current node represented by this UiObject
528     * @throws UiObjectNotFoundException if no match could be found
529     * @since API Level 16
530     */
531    public String getText() throws UiObjectNotFoundException {
532        Tracer.trace();
533        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
534        if(node == null) {
535            throw new UiObjectNotFoundException(getSelector().toString());
536        }
537        String retVal = safeStringReturn(node.getText());
538        Log.d(LOG_TAG, String.format("getText() = %s", retVal));
539        return retVal;
540    }
541
542    /**
543     * Retrieves the <code>className</code> property of the UI element.
544     *
545     * @return class name of the current node represented by this UiObject
546     * @throws UiObjectNotFoundException if no match was found
547     * @since API Level 18
548     */
549    public String getClassName() throws UiObjectNotFoundException {
550        Tracer.trace();
551        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
552        if(node == null) {
553            throw new UiObjectNotFoundException(getSelector().toString());
554        }
555        String retVal = safeStringReturn(node.getClassName());
556        Log.d(LOG_TAG, String.format("getClassName() = %s", retVal));
557        return retVal;
558    }
559
560    /**
561     * Reads the <code>content_desc</code> property of the UI element
562     *
563     * @return value of node attribute "content_desc"
564     * @throws UiObjectNotFoundException
565     * @since API Level 16
566     */
567    public String getContentDescription() throws UiObjectNotFoundException {
568        Tracer.trace();
569        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
570        if(node == null) {
571            throw new UiObjectNotFoundException(getSelector().toString());
572        }
573        return safeStringReturn(node.getContentDescription());
574    }
575
576    /**
577     * Sets the text in an editable field, after clearing the field's content.
578     *
579     * The {@link UiSelector} selector of this object must reference a UI element that is editable.
580     *
581     * When you call this method, the method first simulates a {@link #click()} on
582     * editable field to set focus. The method then clears the field's contents
583     * and injects your specified text into the field.
584     *
585     * If you want to capture the original contents of the field, call {@link #getText()} first.
586     * You can then modify the text and use this method to update the field.
587     *
588     * @param text string to set
589     * @return true if operation is successful
590     * @throws UiObjectNotFoundException
591     * @since API Level 16
592     */
593    public boolean setText(String text) throws UiObjectNotFoundException {
594        Tracer.trace(text);
595        clearTextField();
596        return getInteractionController().sendText(text);
597    }
598
599    /**
600     * Clears the existing text contents in an editable field.
601     *
602     * The {@link UiSelector} of this object must reference a UI element that is editable.
603     *
604     * When you call this method, the method first sets focus at the start edge of the field.
605     * The method then simulates a long-press to select the existing text, and deletes the
606     * selected text.
607     *
608     * If a "Select-All" option is displayed, the method will automatically attempt to use it
609     * to ensure full text selection.
610     *
611     * Note that it is possible that not all the text in the field is selected; for example,
612     * if the text contains separators such as spaces, slashes, at symbol etc.
613     * Also, not all editable fields support the long-press functionality.
614     *
615     * @throws UiObjectNotFoundException
616     * @since API Level 16
617     */
618    public void clearTextField() throws UiObjectNotFoundException {
619        Tracer.trace();
620        // long click left + center
621        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
622        if(node == null) {
623            throw new UiObjectNotFoundException(getSelector().toString());
624        }
625        Rect rect = getVisibleBounds(node);
626        getInteractionController().longTapNoSync(rect.left + 20, rect.centerY());
627        // check if the edit menu is open
628        UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));
629        if(selectAll.waitForExists(50))
630            selectAll.click();
631        // wait for the selection
632        SystemClock.sleep(250);
633        // delete it
634        getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
635    }
636
637    /**
638     * Check if the UI element's <code>checked</code> property is currently true
639     *
640     * @return true if it is else false
641     * @since API Level 16
642     */
643    public boolean isChecked() throws UiObjectNotFoundException {
644        Tracer.trace();
645        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
646        if(node == null) {
647            throw new UiObjectNotFoundException(getSelector().toString());
648        }
649        return node.isChecked();
650    }
651
652    /**
653     * Checks if the UI element's <code>selected</code> property is currently true.
654     *
655     * @return true if it is else false
656     * @throws UiObjectNotFoundException
657     * @since API Level 16
658     */
659    public boolean isSelected() throws UiObjectNotFoundException {
660        Tracer.trace();
661        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
662        if(node == null) {
663            throw new UiObjectNotFoundException(getSelector().toString());
664        }
665        return node.isSelected();
666    }
667
668    /**
669     * Checks if the UI element's <code>checkable</code> property is currently true.
670     *
671     * @return true if it is else false
672     * @throws UiObjectNotFoundException
673     * @since API Level 16
674     */
675    public boolean isCheckable() throws UiObjectNotFoundException {
676        Tracer.trace();
677        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
678        if(node == null) {
679            throw new UiObjectNotFoundException(getSelector().toString());
680        }
681        return node.isCheckable();
682    }
683
684    /**
685     * Checks if the UI element's <code>enabled</code> property is currently true.
686     *
687     * @return true if it is else false
688     * @throws UiObjectNotFoundException
689     * @since API Level 16
690     */
691    public boolean isEnabled() throws UiObjectNotFoundException {
692        Tracer.trace();
693        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
694        if(node == null) {
695            throw new UiObjectNotFoundException(getSelector().toString());
696        }
697        return node.isEnabled();
698    }
699
700    /**
701     * Checks if the UI element's <code>clickable</code> property is currently true.
702     *
703     * @return true if it is else false
704     * @throws UiObjectNotFoundException
705     * @since API Level 16
706     */
707    public boolean isClickable() throws UiObjectNotFoundException {
708        Tracer.trace();
709        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
710        if(node == null) {
711            throw new UiObjectNotFoundException(getSelector().toString());
712        }
713        return node.isClickable();
714    }
715
716    /**
717     * Check if the UI element's <code>focused</code> property is currently true
718     *
719     * @return true if it is else false
720     * @throws UiObjectNotFoundException
721     * @since API Level 16
722     */
723    public boolean isFocused() throws UiObjectNotFoundException {
724        Tracer.trace();
725        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
726        if(node == null) {
727            throw new UiObjectNotFoundException(getSelector().toString());
728        }
729        return node.isFocused();
730    }
731
732    /**
733     * Check if the UI element's <code>focusable</code> property is currently true.
734     *
735     * @return true if it is else false
736     * @throws UiObjectNotFoundException
737     * @since API Level 16
738     */
739    public boolean isFocusable() throws UiObjectNotFoundException {
740        Tracer.trace();
741        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
742        if(node == null) {
743            throw new UiObjectNotFoundException(getSelector().toString());
744        }
745        return node.isFocusable();
746    }
747
748    /**
749     * Check if the view's <code>scrollable</code> property is currently true
750     *
751     * @return true if it is else false
752     * @throws UiObjectNotFoundException
753     * @since API Level 16
754     */
755    public boolean isScrollable() throws UiObjectNotFoundException {
756        Tracer.trace();
757        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
758        if(node == null) {
759            throw new UiObjectNotFoundException(getSelector().toString());
760        }
761        return node.isScrollable();
762    }
763
764    /**
765     * Check if the view's <code>long-clickable</code> property is currently true
766     *
767     * @return true if it is else false
768     * @throws UiObjectNotFoundException
769     * @since API Level 16
770     */
771    public boolean isLongClickable() throws UiObjectNotFoundException {
772        Tracer.trace();
773        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
774        if(node == null) {
775            throw new UiObjectNotFoundException(getSelector().toString());
776        }
777        return node.isLongClickable();
778    }
779
780    /**
781     * Reads the view's <code>package</code> property
782     *
783     * @return true if it is else false
784     * @throws UiObjectNotFoundException
785     * @since API Level 16
786     */
787    public String getPackageName() throws UiObjectNotFoundException {
788        Tracer.trace();
789        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
790        if(node == null) {
791            throw new UiObjectNotFoundException(getSelector().toString());
792        }
793        return safeStringReturn(node.getPackageName());
794    }
795
796    /**
797     * Returns the visible bounds of the view.
798     *
799     * If a portion of the view is visible, only the bounds of the visible portion are
800     * reported.
801     *
802     * @return Rect
803     * @throws UiObjectNotFoundException
804     * @see {@link #getBounds()}
805     * @since API Level 17
806     */
807    public Rect getVisibleBounds() throws UiObjectNotFoundException {
808        Tracer.trace();
809        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
810        if(node == null) {
811            throw new UiObjectNotFoundException(getSelector().toString());
812        }
813        return getVisibleBounds(node);
814    }
815
816    /**
817     * Returns the view's <code>bounds</code> property. See {@link #getVisibleBounds()}
818     *
819     * @return Rect
820     * @throws UiObjectNotFoundException
821     * @since API Level 16
822     */
823    public Rect getBounds() throws UiObjectNotFoundException {
824        Tracer.trace();
825        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
826        if(node == null) {
827            throw new UiObjectNotFoundException(getSelector().toString());
828        }
829        Rect nodeRect = new Rect();
830        node.getBoundsInScreen(nodeRect);
831
832        return nodeRect;
833    }
834
835    /**
836     * Waits a specified length of time for a view to become visible.
837     *
838     * This method waits until the view becomes visible on the display, or
839     * until the timeout has elapsed. You can use this method in situations where
840     * the content that you want to select is not immediately displayed.
841     *
842     * @param timeout the amount of time to wait (in milliseconds)
843     * @return true if the view is displayed, else false if timeout elapsed while waiting
844     * @since API Level 16
845     */
846    public boolean waitForExists(long timeout) {
847        Tracer.trace(timeout);
848        if(findAccessibilityNodeInfo(timeout) != null) {
849            return true;
850        }
851        return false;
852    }
853
854    /**
855     * Waits a specified length of time for a view to become undetectable.
856     *
857     * This method waits until a view is no longer matchable, or until the
858     * timeout has elapsed.
859     *
860     * A view becomes undetectable when the {@link UiSelector} of the object is
861     * unable to find a match because the element has either changed its state or is no
862     * longer displayed.
863     *
864     * You can use this method when attempting to wait for some long operation
865     * to compete, such as downloading a large file or connecting to a remote server.
866     *
867     * @param timeout time to wait (in milliseconds)
868     * @return true if the element is gone before timeout elapsed, else false if timeout elapsed
869     * but a matching element is still found.
870     * @since API Level 16
871     */
872    public boolean waitUntilGone(long timeout) {
873        Tracer.trace(timeout);
874        long startMills = SystemClock.uptimeMillis();
875        long currentMills = 0;
876        while (currentMills <= timeout) {
877            if(findAccessibilityNodeInfo(0) == null)
878                return true;
879            currentMills = SystemClock.uptimeMillis() - startMills;
880            if(timeout > 0)
881                SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
882        }
883        return false;
884    }
885
886    /**
887     * Check if view exists.
888     *
889     * This methods performs a {@link #waitForExists(long)} with zero timeout. This
890     * basically returns immediately whether the view represented by this UiObject
891     * exists or not. If you need to wait longer for this view, then see
892     * {@link #waitForExists(long)}.
893     *
894     * @return true if the view represented by this UiObject does exist
895     * @since API Level 16
896     */
897    public boolean exists() {
898        Tracer.trace();
899        return waitForExists(0);
900    }
901
902    private String safeStringReturn(CharSequence cs) {
903        if(cs == null)
904            return "";
905        return cs.toString();
906    }
907
908    /**
909     * Performs a two-pointer gesture, where each pointer moves diagonally
910     * opposite across the other, from the center out towards the edges of the
911     * this UiObject.
912     * @param percent percentage of the object's diagonal length for the pinch gesture
913     * @param steps the number of steps for the gesture. Steps are injected
914     * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
915     * @return <code>true</code> if all touch events for this gesture are injected successfully,
916     *         <code>false</code> otherwise
917     * @throws UiObjectNotFoundException
918     * @since API Level 18
919     */
920    public boolean pinchOut(int percent, int steps) throws UiObjectNotFoundException {
921        // make value between 1 and 100
922        percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent;
923        float percentage = percent / 100f;
924
925        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
926        if (node == null) {
927            throw new UiObjectNotFoundException(getSelector().toString());
928        }
929
930        Rect rect = getVisibleBounds(node);
931        if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2)
932            throw new IllegalStateException("Object width is too small for operation");
933
934        // start from the same point at the center of the control
935        Point startPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY());
936        Point startPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY());
937
938        // End at the top-left and bottom-right corners of the control
939        Point endPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage),
940                rect.centerY());
941        Point endPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage),
942                rect.centerY());
943
944        return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
945    }
946
947    /**
948     * Performs a two-pointer gesture, where each pointer moves diagonally
949     * toward the other, from the edges to the center of this UiObject .
950     * @param percent percentage of the object's diagonal length for the pinch gesture
951     * @param steps the number of steps for the gesture. Steps are injected
952     * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
953     * @return <code>true</code> if all touch events for this gesture are injected successfully,
954     *         <code>false</code> otherwise
955     * @throws UiObjectNotFoundException
956     * @since API Level 18
957     */
958    public boolean pinchIn(int percent, int steps) throws UiObjectNotFoundException {
959        // make value between 1 and 100
960        percent = (percent < 0) ? 0 : (percent > 100) ? 100 : percent;
961        float percentage = percent / 100f;
962
963        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
964        if (node == null) {
965            throw new UiObjectNotFoundException(getSelector().toString());
966        }
967
968        Rect rect = getVisibleBounds(node);
969        if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2)
970            throw new IllegalStateException("Object width is too small for operation");
971
972        Point startPoint1 = new Point(rect.centerX() - (int)((rect.width()/2) * percentage),
973                rect.centerY());
974        Point startPoint2 = new Point(rect.centerX() + (int)((rect.width()/2) * percentage),
975                rect.centerY());
976
977        Point endPoint1 = new Point(rect.centerX() - FINGER_TOUCH_HALF_WIDTH, rect.centerY());
978        Point endPoint2 = new Point(rect.centerX() + FINGER_TOUCH_HALF_WIDTH, rect.centerY());
979
980        return performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
981    }
982
983    /**
984     * Generates a two-pointer gesture with arbitrary starting and ending points.
985     *
986     * @param startPoint1 start point of pointer 1
987     * @param startPoint2 start point of pointer 2
988     * @param endPoint1 end point of pointer 1
989     * @param endPoint2 end point of pointer 2
990     * @param steps the number of steps for the gesture. Steps are injected
991     * about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete.
992     * @return <code>true</code> if all touch events for this gesture are injected successfully,
993     *         <code>false</code> otherwise
994     * @since API Level 18
995     */
996    public boolean performTwoPointerGesture(Point startPoint1, Point startPoint2, Point endPoint1,
997            Point endPoint2, int steps) {
998
999        // avoid a divide by zero
1000        if(steps == 0)
1001            steps = 1;
1002
1003        final float stepX1 = (endPoint1.x - startPoint1.x) / steps;
1004        final float stepY1 = (endPoint1.y - startPoint1.y) / steps;
1005        final float stepX2 = (endPoint2.x - startPoint2.x) / steps;
1006        final float stepY2 = (endPoint2.y - startPoint2.y) / steps;
1007
1008        int eventX1, eventY1, eventX2, eventY2;
1009        eventX1 = startPoint1.x;
1010        eventY1 = startPoint1.y;
1011        eventX2 = startPoint2.x;
1012        eventY2 = startPoint2.y;
1013
1014        // allocate for steps plus first down and last up
1015        PointerCoords[] points1 = new PointerCoords[steps + 2];
1016        PointerCoords[] points2 = new PointerCoords[steps + 2];
1017
1018        // Include the first and last touch downs in the arrays of steps
1019        for (int i = 0; i < steps + 1; i++) {
1020            PointerCoords p1 = new PointerCoords();
1021            p1.x = eventX1;
1022            p1.y = eventY1;
1023            p1.pressure = 1;
1024            p1.size = 1;
1025            points1[i] = p1;
1026
1027            PointerCoords p2 = new PointerCoords();
1028            p2.x = eventX2;
1029            p2.y = eventY2;
1030            p2.pressure = 1;
1031            p2.size = 1;
1032            points2[i] = p2;
1033
1034            eventX1 += stepX1;
1035            eventY1 += stepY1;
1036            eventX2 += stepX2;
1037            eventY2 += stepY2;
1038        }
1039
1040        // ending pointers coordinates
1041        PointerCoords p1 = new PointerCoords();
1042        p1.x = endPoint1.x;
1043        p1.y = endPoint1.y;
1044        p1.pressure = 1;
1045        p1.size = 1;
1046        points1[steps + 1] = p1;
1047
1048        PointerCoords p2 = new PointerCoords();
1049        p2.x = endPoint2.x;
1050        p2.y = endPoint2.y;
1051        p2.pressure = 1;
1052        p2.size = 1;
1053        points2[steps + 1] = p2;
1054
1055        return performMultiPointerGesture(points1, points2);
1056    }
1057
1058    /**
1059     * Performs a multi-touch gesture. You must specify touch coordinates for
1060     * at least 2 pointers. Each pointer must have all of its touch steps
1061     * defined in an array of {@link PointerCoords}. You can use this method to
1062     * specify complex gestures, like circles and irregular shapes, where each
1063     * pointer may take a different path.
1064     *
1065     * To create a single point on a pointer's touch path:
1066     * <code>
1067     *       PointerCoords p = new PointerCoords();
1068     *       p.x = stepX;
1069     *       p.y = stepY;
1070     *       p.pressure = 1;
1071     *       p.size = 1;
1072     * </code>
1073     * @param touches represents the pointers' paths. Each {@link PointerCoords}
1074     * array represents a different pointer. Each {@link PointerCoords} in an
1075     * array element represents a touch point on a pointer's path.
1076     * @return <code>true</code> if all touch events for this gesture are injected successfully,
1077     *         <code>false</code> otherwise
1078     * @since API Level 18
1079     */
1080    public boolean performMultiPointerGesture(PointerCoords[] ...touches) {
1081        return getInteractionController().performMultiPointerGesture(touches);
1082    }
1083}
1084