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.Rect;
20import android.os.SystemClock;
21import android.util.Log;
22import android.view.KeyEvent;
23import android.view.accessibility.AccessibilityEvent;
24import android.view.accessibility.AccessibilityNodeInfo;
25
26/**
27 * A UiObject is a representation of a UI element. It is not in any way directly bound to a
28 * UI element as an object reference. A UiObject holds information to help it
29 * locate a matching UI element at runtime based on the {@link UiSelector} properties specified in
30 * its constructor. Since a UiObject is a representative for a UI element, it can
31 * be reused for different views with matching UI elements.
32 */
33public class UiObject {
34    private static final String LOG_TAG = UiObject.class.getSimpleName();
35    protected static final long WAIT_FOR_SELECTOR_TIMEOUT = 10 * 1000;
36    protected static final long WAIT_FOR_SELECTOR_POLL = 1000;
37    // set a default timeout to 5.5s, since ANR threshold is 5s
38    protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500;
39    protected static final long WAIT_FOR_EVENT_TMEOUT = 3 * 1000;
40    protected static final int SWIPE_MARGIN_LIMIT = 5;
41
42    private final UiSelector mSelector;
43    private final UiAutomatorBridge mUiAutomationBridge;
44
45    /**
46     * Constructs a UiObject to represent a specific UI element matched by the specified
47     * {@link UiSelector} selector properties.
48     *
49     * @param selector
50     */
51    public UiObject(UiSelector selector) {
52        mUiAutomationBridge = UiDevice.getInstance().getAutomatorBridge();
53        mSelector = selector;
54    }
55
56    /**
57     * Debugging helper. A test can dump the properties of a selector as a string
58     * to its logs if needed. <code>getSelector().toString();</code>
59     *
60     * @return {@link UiSelector}
61     */
62    public final UiSelector getSelector() {
63        return new UiSelector(mSelector);
64    }
65
66    /**
67     * Retrieves the {@link QueryController} to translate a {@link UiSelector} selector
68     * into an {@link AccessibilityNodeInfo}.
69     *
70     * @return {@link QueryController}
71     */
72    QueryController getQueryController() {
73        return mUiAutomationBridge.getQueryController();
74    }
75
76    /**
77     * Retrieves the {@link InteractionController} to perform finger actions such as tapping,
78     * swiping or entering text.
79     *
80     * @return {@link InteractionController}
81     */
82    InteractionController getInteractionController() {
83        return mUiAutomationBridge.getInteractionController();
84    }
85
86    /**
87     * Creates a new UiObject representing a child UI element of the element currently represented
88     * by this UiObject.
89     *
90     * @param selector for UI element to match
91     * @return a new UiObject representing the matched UI element
92     */
93    public UiObject getChild(UiSelector selector) throws UiObjectNotFoundException {
94        return new UiObject(getSelector().childSelector(selector));
95    }
96
97    /**
98     * Creates a new UiObject representing a child UI element from the parent element currently
99     * represented by this object. Essentially this is starting the search from the parent
100     * element and can also be used to find sibling UI elements to the one currently represented
101     * by this UiObject.
102     *
103     * @param selector for the UI element to match
104     * @return a new UiObject representing the matched UI element
105     * @throws UiObjectNotFoundException
106     */
107    public UiObject getFromParent(UiSelector selector) throws UiObjectNotFoundException {
108        return new UiObject(getSelector().fromParent(selector));
109    }
110
111    /**
112     * Counts the child UI elements immediately under the UI element currently represented by
113     * this UiObject.
114     *
115     * @return the count of child UI elements.
116     * @throws UiObjectNotFoundException
117     */
118    public int getChildCount() throws UiObjectNotFoundException {
119        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
120        if(node == null) {
121            throw new UiObjectNotFoundException(getSelector().toString());
122        }
123        return node.getChildCount();
124    }
125
126    /**
127     * Uses the member UiSelector properties to find a matching UI element reported in
128     * the accessibility hierarchy.
129     *
130     * @param selector {@link UiSelector}
131     * @param timeout in milliseconds
132     * @return AccessibilityNodeInfo if found else null
133     */
134    protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout) {
135        AccessibilityNodeInfo node = null;
136        if(UiDevice.getInstance().isInWatcherContext()) {
137            // we will NOT run watchers or do any sort of polling if the
138            // reason we're here is because of a watcher is executing. Watchers
139            // will not have other watchers run for them so they should not block
140            // while they poll for items to become present. We disable polling for them.
141            node = getQueryController().findAccessibilityNodeInfo(getSelector());
142        } else {
143            long startMills = SystemClock.uptimeMillis();
144            long currentMills = 0;
145            while (currentMills <= timeout) {
146                node = getQueryController().findAccessibilityNodeInfo(getSelector());
147                if (node != null) {
148                    break;
149                } else {
150                    UiDevice.getInstance().runWatchers();
151                }
152                currentMills = SystemClock.uptimeMillis() - startMills;
153                if(timeout > 0) {
154                    SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
155                }
156            }
157        }
158        return node;
159    }
160
161    /**
162     * Perform the action on the UI element that is represented by this UiObject. Also see
163     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
164     * {@link #scrollForward()}.
165     *
166     * @param steps indicates the number of injected move steps into the system. Steps are
167     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
168     * @return true of successful
169     * @throws UiObjectNotFoundException
170     */
171    public boolean swipeUp(int steps) throws UiObjectNotFoundException {
172        Rect rect = getVisibleBounds();
173        if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
174            return false; // too small to swipe
175        return getInteractionController().swipe(rect.centerX(),
176                rect.bottom - SWIPE_MARGIN_LIMIT, rect.centerX(), rect.top + SWIPE_MARGIN_LIMIT,
177                steps);
178    }
179
180    /**
181     * Perform the action on the UI element that is represented by this object, Also see
182     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
183     * {@link #scrollForward()}. This method will perform the swipe gesture over any
184     * surface. The targeted UI element does not need to have the attribute
185     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
186     *
187     * @param steps indicates the number of injected move steps into the system. Steps are
188     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
189     * @return true if successful
190     * @throws UiObjectNotFoundException
191     */
192    public boolean swipeDown(int steps) throws UiObjectNotFoundException {
193        Rect rect = getVisibleBounds();
194        if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
195            return false; // too small to swipe
196        return getInteractionController().swipe(rect.centerX(),
197                rect.top + SWIPE_MARGIN_LIMIT, rect.centerX(),
198                rect.bottom - SWIPE_MARGIN_LIMIT, steps);
199    }
200
201    /**
202     * Perform the action on the UI element that is represented by this object. Also see
203     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
204     * {@link #scrollForward()}. This method will perform the swipe gesture over any
205     * surface. The targeted UI element does not need to have the attribute
206     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
207     *
208     * @param steps indicates the number of injected move steps into the system. Steps are
209     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
210     * @return true if successful
211     * @throws UiObjectNotFoundException
212     */
213    public boolean swipeLeft(int steps) throws UiObjectNotFoundException {
214        Rect rect = getVisibleBounds();
215        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
216            return false; // too small to swipe
217        return getInteractionController().swipe(rect.right - SWIPE_MARGIN_LIMIT,
218                rect.centerY(), rect.left + SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
219    }
220
221    /**
222     * Perform the action on the UI element that is represented by this object. Also see
223     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
224     * {@link #scrollForward()}. This method will perform the swipe gesture over any
225     * surface. The targeted UI element does not need to have the attribute
226     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
227     *
228     * @param steps indicates the number of injected move steps into the system. Steps are
229     * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
230     * @return true if successful
231     * @throws UiObjectNotFoundException
232     */
233    public boolean swipeRight(int steps) throws UiObjectNotFoundException {
234        Rect rect = getVisibleBounds();
235        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
236            return false; // too small to swipe
237        return getInteractionController().swipe(rect.left + SWIPE_MARGIN_LIMIT,
238                rect.centerY(), rect.right - SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
239    }
240
241    /**
242     * Finds the visible bounds of a partially visible UI element
243     *
244     * @param node
245     * @return null if node is null, else a Rect containing visible bounds
246     */
247    private Rect getVisibleBounds(AccessibilityNodeInfo node) {
248        if (node == null) {
249            return null;
250        }
251
252        // targeted node's bounds
253        Rect nodeRect = AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(node);
254
255        // is the targeted node within a scrollable container?
256        AccessibilityNodeInfo scrollableParentNode = getScrollableParent(node);
257        if(scrollableParentNode == null) {
258            // nothing to adjust for so return the node's Rect as is
259            return nodeRect;
260        }
261
262        // Scrollable parent's visible bounds
263        Rect parentRect = AccessibilityNodeInfoHelper
264                .getVisibleBoundsInScreen(scrollableParentNode);
265        // adjust for partial clipping of targeted by parent node if required
266        nodeRect.intersect(parentRect);
267        return nodeRect;
268    }
269
270    /**
271     * Walk the hierarchy up to find a scrollable parent. A scrollable parent
272     * indicates that this node may be in a content where it is partially
273     * visible due to scrolling. its clickable center maybe invisible and
274     * adjustments should be made to the click coordinates.
275     *
276     * @param node
277     * @return
278     */
279    private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) {
280        AccessibilityNodeInfo parent = node;
281        while(parent != null) {
282            parent = parent.getParent();
283            if (parent != null && parent.isScrollable()) {
284                return parent;
285            }
286        }
287        return null;
288    }
289
290    /**
291     * Performs a click at the center of the visible bounds of the UI element represented
292     * by this UiObject.
293     *
294     * @return true id successful else false
295     * @throws UiObjectNotFoundException
296     */
297    public boolean click() throws UiObjectNotFoundException {
298        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
299        if(node == null) {
300            throw new UiObjectNotFoundException(getSelector().toString());
301        }
302        Rect rect = getVisibleBounds(node);
303        return getInteractionController().clickAndWaitForEvents(rect.centerX(), rect.centerY(),
304                WAIT_FOR_EVENT_TMEOUT, false, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED +
305                AccessibilityEvent.TYPE_VIEW_SELECTED);
306    }
307
308    /**
309     * See {@link #clickAndWaitForNewWindow(long)}
310     * This method is intended to reliably wait for window transitions that would typically take
311     * longer than the usual default timeouts.
312     *
313     * @return true if the event was triggered, else false
314     * @throws UiObjectNotFoundException
315     */
316    public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException {
317        return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT);
318    }
319
320    /**
321     * Performs a click at the center of the visible bounds of the UI element represented
322     * by this UiObject and waits for window transitions.
323     *
324     * This method differ from {@link UiObject#click()} only in that this method waits for a
325     * a new window transition as a result of the click. Some examples of a window transition:
326     * <li>launching a new activity</li>
327     * <li>bringing up a pop-up menu</li>
328     * <li>bringing up a dialog</li>
329     *
330     * @param timeout timeout before giving up on waiting for a new window
331     * @return true if the event was triggered, else false
332     * @throws UiObjectNotFoundException
333     */
334    public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException {
335        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
336        if(node == null) {
337            throw new UiObjectNotFoundException(getSelector().toString());
338        }
339        Rect rect = getVisibleBounds(node);
340        return getInteractionController().clickAndWaitForNewWindow(
341                rect.centerX(), rect.centerY(), timeout);
342    }
343
344    /**
345     * Clicks the top and left corner of the UI element
346     *
347     * @return true on success
348     * @throws Exception
349     */
350    public boolean clickTopLeft() throws UiObjectNotFoundException {
351        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
352        if(node == null) {
353            throw new UiObjectNotFoundException(getSelector().toString());
354        }
355        Rect rect = getVisibleBounds(node);
356        return getInteractionController().click(rect.left + 5, rect.top + 5);
357    }
358
359    /**
360     * Long clicks bottom and right corner of the UI element
361     *
362     * @return true if operation was successful
363     * @throws UiObjectNotFoundException
364     */
365    public boolean longClickBottomRight() throws UiObjectNotFoundException  {
366        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
367        if(node == null) {
368            throw new UiObjectNotFoundException(getSelector().toString());
369        }
370        Rect rect = getVisibleBounds(node);
371        return getInteractionController().longTap(rect.right - 5, rect.bottom - 5);
372    }
373
374    /**
375     * Clicks the bottom and right corner of the UI element
376     *
377     * @return true on success
378     * @throws Exception
379     */
380    public boolean clickBottomRight() throws UiObjectNotFoundException {
381        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
382        if(node == null) {
383            throw new UiObjectNotFoundException(getSelector().toString());
384        }
385        Rect rect = getVisibleBounds(node);
386        return getInteractionController().click(rect.right - 5, rect.bottom - 5);
387    }
388
389    /**
390     * Long clicks the center of the visible bounds of the UI element
391     *
392     * @return true if operation was successful
393     * @throws UiObjectNotFoundException
394     */
395    public boolean longClick() throws UiObjectNotFoundException  {
396        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
397        if(node == null) {
398            throw new UiObjectNotFoundException(getSelector().toString());
399        }
400        Rect rect = getVisibleBounds(node);
401        return getInteractionController().longTap(rect.centerX(), rect.centerY());
402    }
403
404    /**
405     * Long clicks on the top and left corner of the UI element
406     *
407     * @return true if operation was successful
408     * @throws UiObjectNotFoundException
409     */
410    public boolean longClickTopLeft() throws UiObjectNotFoundException {
411        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
412        if(node == null) {
413            throw new UiObjectNotFoundException(getSelector().toString());
414        }
415        Rect rect = getVisibleBounds(node);
416        return getInteractionController().longTap(rect.left + 5, rect.top + 5);
417    }
418
419    /**
420     * Reads the <code>text</code> property of the UI element
421     *
422     * @return text value of the current node represented by this UiObject
423     * @throws UiObjectNotFoundException if no match could be found
424     */
425    public String getText() throws UiObjectNotFoundException {
426        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
427        if(node == null) {
428            throw new UiObjectNotFoundException(getSelector().toString());
429        }
430        String retVal = safeStringReturn(node.getText());
431        Log.d(LOG_TAG, String.format("getText() = %s", retVal));
432        return retVal;
433    }
434
435    /**
436     * Reads the <code>content_desc</code> property of the UI element
437     *
438     * @return value of node attribute "content_desc"
439     * @throws UiObjectNotFoundException
440     */
441    public String getContentDescription() throws UiObjectNotFoundException {
442        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
443        if(node == null) {
444            throw new UiObjectNotFoundException(getSelector().toString());
445        }
446        return safeStringReturn(node.getContentDescription());
447    }
448
449    /**
450     * Sets the text in an editable field, after clearing the field's content.
451     *
452     * The {@link UiSelector} selector of this object must reference a UI element that is editable.
453     *
454     * When you call this method, the method first simulates a {@link #click()} on
455     * editable field to set focus. The method then clears the field's contents
456     * and injects your specified text into the field.
457     *
458     * If you want to capture the original contents of the field, call {@link #getText()} first.
459     * You can then modify the text and use this method to update the field.
460     *
461     * @param text string to set
462     * @return true if operation is successful
463     * @throws UiObjectNotFoundException
464     */
465    public boolean setText(String text) throws UiObjectNotFoundException {
466        clearTextField();
467        return getInteractionController().sendText(text);
468    }
469
470    /**
471     * Clears the existing text contents in an editable field.
472     *
473     * The {@link UiSelector} of this object must reference a UI element that is editable.
474     *
475     * When you call this method, the method first sets focus at the start edge of the field.
476     * The method then simulates a long-press to select the existing text, and deletes the
477     * selected text.
478     *
479     * If a "Select-All" option is displayed, the method will automatically attempt to use it
480     * to ensure full text selection.
481     *
482     * Note that it is possible that not all the text in the field is selected; for example,
483     * if the text contains separators such as spaces, slashes, at symbol etc.
484     * Also, not all editable fields support the long-press functionality.
485     *
486     * @throws UiObjectNotFoundException
487     */
488    public void clearTextField() throws UiObjectNotFoundException {
489        // long click left + center
490        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
491        if(node == null) {
492            throw new UiObjectNotFoundException(getSelector().toString());
493        }
494        Rect rect = getVisibleBounds(node);
495        getInteractionController().longTap(rect.left + 20, rect.centerY());
496        // check if the edit menu is open
497        UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));
498        if(selectAll.waitForExists(50))
499            selectAll.click();
500        // wait for the selection
501        SystemClock.sleep(250);
502        // delete it
503        getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
504    }
505
506    /**
507     * Check if the UI element's <code>checked</code> property is currently true
508     *
509     * @return true if it is else false
510     */
511    public boolean isChecked() throws UiObjectNotFoundException {
512        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
513        if(node == null) {
514            throw new UiObjectNotFoundException(getSelector().toString());
515        }
516        return node.isChecked();
517    }
518
519    /**
520     * Check if the UI element's <code>selected</code> property is currently true
521     *
522     * @return true if it is else false
523     * @throws UiObjectNotFoundException
524     */
525    public boolean isSelected() throws UiObjectNotFoundException {
526        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
527        if(node == null) {
528            throw new UiObjectNotFoundException(getSelector().toString());
529        }
530        return node.isSelected();
531    }
532
533    /**
534     * Check if the UI element's <code>checkable</code> property is currently true
535     *
536     * @return true if it is else false
537     * @throws UiObjectNotFoundException
538     */
539    public boolean isCheckable() throws UiObjectNotFoundException {
540        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
541        if(node == null) {
542            throw new UiObjectNotFoundException(getSelector().toString());
543        }
544        return node.isCheckable();
545    }
546
547    /**
548     * Check if the UI element's <code>enabled</code> property is currently true
549     *
550     * @return true if it is else false
551     * @throws UiObjectNotFoundException
552     */
553    public boolean isEnabled() throws UiObjectNotFoundException {
554        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
555        if(node == null) {
556            throw new UiObjectNotFoundException(getSelector().toString());
557        }
558        return node.isEnabled();
559    }
560
561    /**
562     * Check if the UI element's <code>clickable</code> property is currently true
563     *
564     * @return true if it is else false
565     * @throws UiObjectNotFoundException
566     */
567    public boolean isClickable() throws UiObjectNotFoundException {
568        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
569        if(node == null) {
570            throw new UiObjectNotFoundException(getSelector().toString());
571        }
572        return node.isClickable();
573    }
574
575    /**
576     * Check if the UI element's <code>focused</code> property is currently true
577     *
578     * @return true if it is else false
579     * @throws UiObjectNotFoundException
580     */
581    public boolean isFocused() throws UiObjectNotFoundException {
582        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
583        if(node == null) {
584            throw new UiObjectNotFoundException(getSelector().toString());
585        }
586        return node.isFocused();
587    }
588
589    /**
590     * Check if the UI element's <code>focusable</code> property is currently true
591     *
592     * @return true if it is else false
593     * @throws UiObjectNotFoundException
594     */
595    public boolean isFocusable() throws UiObjectNotFoundException {
596        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
597        if(node == null) {
598            throw new UiObjectNotFoundException(getSelector().toString());
599        }
600        return node.isFocusable();
601    }
602
603    /**
604     * Check if the UI element's <code>scrollable</code> property is currently true
605     *
606     * @return true if it is else false
607     * @throws UiObjectNotFoundException
608     */
609    public boolean isScrollable() throws UiObjectNotFoundException {
610        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
611        if(node == null) {
612            throw new UiObjectNotFoundException(getSelector().toString());
613        }
614        return node.isScrollable();
615    }
616
617    /**
618     * Check if the UI element's <code>long-clickable</code> property is currently true
619     *
620     * @return true if it is else false
621     * @throws UiObjectNotFoundException
622     */
623    public boolean isLongClickable() throws UiObjectNotFoundException {
624        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
625        if(node == null) {
626            throw new UiObjectNotFoundException(getSelector().toString());
627        }
628        return node.isLongClickable();
629    }
630
631    /**
632     * Reads the UI element's <code>package</code> property
633     *
634     * @return true if it is else false
635     * @throws UiObjectNotFoundException
636     */
637    public String getPackageName() throws UiObjectNotFoundException {
638        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
639        if(node == null) {
640            throw new UiObjectNotFoundException(getSelector().toString());
641        }
642        return safeStringReturn(node.getPackageName());
643    }
644
645    /**
646     * Returns the visible bounds of the UI element.
647     *
648     * If a portion of the UI element is visible, only the bounds of the visible portion are
649     * reported.
650     *
651     * @return Rect
652     * @throws UiObjectNotFoundException
653     * @see {@link #getBound()}
654     */
655    public Rect getVisibleBounds() throws UiObjectNotFoundException {
656        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
657        if(node == null) {
658            throw new UiObjectNotFoundException(getSelector().toString());
659        }
660        return getVisibleBounds(node);
661    }
662
663    /**
664     * Returns the UI element's <code>bounds</code> property. See {@link #getVisibleBounds()}
665     *
666     * @return Rect
667     * @throws UiObjectNotFoundException
668     */
669    public Rect getBounds() throws UiObjectNotFoundException {
670        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
671        if(node == null) {
672            throw new UiObjectNotFoundException(getSelector().toString());
673        }
674        Rect nodeRect = new Rect();
675        node.getBoundsInScreen(nodeRect);
676
677        return nodeRect;
678    }
679
680    /**
681     * Waits a specified length of time for a UI element to become visible.
682     *
683     * This method waits until the UI element becomes visible on the display, or
684     * until the timeout has elapsed. You can use this method in situations where
685     * the content that you want to select is not immediately displayed.
686     *
687     * @param timeout the amount of time to wait (in milliseconds)
688     * @return true if the UI element is displayed, else false if timeout elapsed while waiting
689     */
690    public boolean waitForExists(long timeout) {
691        if(findAccessibilityNodeInfo(timeout) != null) {
692            return true;
693        }
694        return false;
695    }
696
697    /**
698     * Waits a specified length of time for a UI element to become undetectable.
699     *
700     * This method waits until a UI element is no longer matchable, or until the
701     * timeout has elapsed.
702     *
703     * A UI element becomes undetectable when the {@link UiSelector} of the object is
704     * unable to find a match because the element has either changed its state or is no
705     * longer displayed.
706     *
707     * You can use this method when attempting to wait for some long operation
708     * to compete, such as downloading a large file or connecting to a remote server.
709     *
710     * @param timeout time to wait (in milliseconds)
711     * @return true if the element is gone before timeout elapsed, else false if timeout elapsed
712     * but a matching element is still found.
713     */
714    public boolean waitUntilGone(long timeout) {
715        long startMills = SystemClock.uptimeMillis();
716        long currentMills = 0;
717        while (currentMills <= timeout) {
718            if(findAccessibilityNodeInfo(0) == null)
719                return true;
720            currentMills = SystemClock.uptimeMillis() - startMills;
721            if(timeout > 0)
722                SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
723        }
724        return false;
725    }
726
727    /**
728     * Check if UI element exists.
729     *
730     * This methods performs a {@link #waitForExists(long)} with zero timeout. This
731     * basically returns immediately whether the UI element represented by this UiObject
732     * exists or not. If you need to wait longer for this UI element, then see
733     * {@link #waitForExists(long)}.
734     *
735     * @return true if the UI element represented by this UiObject does exist
736     */
737    public boolean exists() {
738        return waitForExists(0);
739    }
740
741    private String safeStringReturn(CharSequence cs) {
742        if(cs == null)
743            return "";
744        return cs.toString();
745    }
746}
747