UiObject.java revision 6088c8f9e34e34a4958b1601ed7c1bb34c95da21
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().clickAndWaitForEvent(rect.centerX(), rect.centerY(),
304                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, WAIT_FOR_EVENT_TMEOUT);
305    }
306
307    /**
308     * See {@link #clickAndWaitForNewWindow(long)}
309     * This method is intended to reliably wait for window transitions that would typically take
310     * longer than the usual default timeouts.
311     *
312     * @return true if the event was triggered, else false
313     * @throws UiObjectNotFoundException
314     */
315    public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException {
316        return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT);
317    }
318
319    /**
320     * Performs a click at the center of the visible bounds of the UI element represented
321     * by this UiObject and waits for window transitions.
322     *
323     * This method differ from {@link UiObject#click()} only in that this method waits for a
324     * a new window transition as a result of the click. Some examples of a window transition:
325     * <li>launching a new activity</li>
326     * <li>bringing up a pop-up menu</li>
327     * <li>bringing up a dialog</li>
328     *
329     * @param timeout timeout before giving up on waiting for a new window
330     * @return true if the event was triggered, else false
331     * @throws UiObjectNotFoundException
332     */
333    public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException {
334        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
335        if(node == null) {
336            throw new UiObjectNotFoundException(getSelector().toString());
337        }
338        Rect rect = getVisibleBounds(node);
339        return getInteractionController().clickAndWaitForNewWindow(
340                rect.centerX(), rect.centerY(), timeout);
341    }
342
343    /**
344     * Clicks the top and left corner of the UI element
345     *
346     * @return true on success
347     * @throws Exception
348     */
349    public boolean clickTopLeft() throws UiObjectNotFoundException {
350        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
351        if(node == null) {
352            throw new UiObjectNotFoundException(getSelector().toString());
353        }
354        Rect rect = getVisibleBounds(node);
355        return getInteractionController().click(rect.left + 5, rect.top + 5);
356    }
357
358    /**
359     * Long clicks bottom and right corner of the UI element
360     *
361     * @return true if operation was successful
362     * @throws UiObjectNotFoundException
363     */
364    public boolean longClickBottomRight() throws UiObjectNotFoundException  {
365        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
366        if(node == null) {
367            throw new UiObjectNotFoundException(getSelector().toString());
368        }
369        Rect rect = getVisibleBounds(node);
370        return getInteractionController().longTap(rect.right - 5, rect.bottom - 5);
371    }
372
373    /**
374     * Clicks the bottom and right corner of the UI element
375     *
376     * @return true on success
377     * @throws Exception
378     */
379    public boolean clickBottomRight() throws UiObjectNotFoundException {
380        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
381        if(node == null) {
382            throw new UiObjectNotFoundException(getSelector().toString());
383        }
384        Rect rect = getVisibleBounds(node);
385        return getInteractionController().click(rect.right - 5, rect.bottom - 5);
386    }
387
388    /**
389     * Long clicks the center of the visible bounds of the UI element
390     *
391     * @return true if operation was successful
392     * @throws UiObjectNotFoundException
393     */
394    public boolean longClick() throws UiObjectNotFoundException  {
395        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
396        if(node == null) {
397            throw new UiObjectNotFoundException(getSelector().toString());
398        }
399        Rect rect = getVisibleBounds(node);
400        return getInteractionController().longTap(rect.centerX(), rect.centerY());
401    }
402
403    /**
404     * Long clicks on the top and left corner of the UI element
405     *
406     * @return true if operation was successful
407     * @throws UiObjectNotFoundException
408     */
409    public boolean longClickTopLeft() throws UiObjectNotFoundException {
410        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
411        if(node == null) {
412            throw new UiObjectNotFoundException(getSelector().toString());
413        }
414        Rect rect = getVisibleBounds(node);
415        return getInteractionController().longTap(rect.left + 5, rect.top + 5);
416    }
417
418    /**
419     * Reads the <code>text</code> property of the UI element
420     *
421     * @return text value of the current node represented by this UiObject
422     * @throws UiObjectNotFoundException if no match could be found
423     */
424    public String getText() throws UiObjectNotFoundException {
425        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
426        if(node == null) {
427            throw new UiObjectNotFoundException(getSelector().toString());
428        }
429        String retVal = safeStringReturn(node.getText());
430        Log.d(LOG_TAG, String.format("getText() = %s", retVal));
431        return retVal;
432    }
433
434    /**
435     * Reads the <code>content_desc</code> property of the UI element
436     *
437     * @return value of node attribute "content_desc"
438     * @throws UiObjectNotFoundException
439     */
440    public String getContentDescription() throws UiObjectNotFoundException {
441        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
442        if(node == null) {
443            throw new UiObjectNotFoundException(getSelector().toString());
444        }
445        return safeStringReturn(node.getContentDescription());
446    }
447
448    /**
449     * Sets the text in an editable field, after clearing the field's content.
450     *
451     * The {@link UiSelector} selector of this object must reference a UI element that is editable.
452     *
453     * When you call this method, the method first simulates a {@link #click()} on
454     * editable field to set focus. The method then clears the field's contents
455     * and injects your specified text into the field.
456     *
457     * If you want to capture the original contents of the field, call {@link #getText()} first.
458     * You can then modify the text and use this method to update the field.
459     *
460     * @param text string to set
461     * @return true if operation is successful
462     * @throws UiObjectNotFoundException
463     */
464    public boolean setText(String text) throws UiObjectNotFoundException {
465        clearTextField();
466        return getInteractionController().sendText(text);
467    }
468
469    /**
470     * Clears the existing text contents in an editable field.
471     *
472     * The {@link UiSelector} of this object must reference a UI element that is editable.
473     *
474     * When you call this method, the method first sets focus at the start edge of the field.
475     * The method then simulates a long-press to select the existing text, and deletes the
476     * selected text.
477     *
478     * If a "Select-All" option is displayed, the method will automatically attempt to use it
479     * to ensure full text selection.
480     *
481     * Note that it is possible that not all the text in the field is selected; for example,
482     * if the text contains separators such as spaces, slashes, at symbol etc.
483     * Also, not all editable fields support the long-press functionality.
484     *
485     * @throws UiObjectNotFoundException
486     */
487    public void clearTextField() throws UiObjectNotFoundException {
488        // long click left + center
489        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
490        if(node == null) {
491            throw new UiObjectNotFoundException(getSelector().toString());
492        }
493        Rect rect = getVisibleBounds(node);
494        getInteractionController().longTap(rect.left + 20, rect.centerY());
495        // check if the edit menu is open
496        UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));
497        if(selectAll.waitForExists(50))
498            selectAll.click();
499        // wait for the selection
500        SystemClock.sleep(250);
501        // delete it
502        getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
503    }
504
505    /**
506     * Check if the UI element's <code>checked</code> property is currently true
507     *
508     * @return true if it is else false
509     */
510    public boolean isChecked() throws UiObjectNotFoundException {
511        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
512        if(node == null) {
513            throw new UiObjectNotFoundException(getSelector().toString());
514        }
515        return node.isChecked();
516    }
517
518    /**
519     * Check if the UI element's <code>selected</code> property is currently true
520     *
521     * @return true if it is else false
522     * @throws UiObjectNotFoundException
523     */
524    public boolean isSelected() throws UiObjectNotFoundException {
525        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
526        if(node == null) {
527            throw new UiObjectNotFoundException(getSelector().toString());
528        }
529        return node.isSelected();
530    }
531
532    /**
533     * Check if the UI element's <code>checkable</code> property is currently true
534     *
535     * @return true if it is else false
536     * @throws UiObjectNotFoundException
537     */
538    public boolean isCheckable() throws UiObjectNotFoundException {
539        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
540        if(node == null) {
541            throw new UiObjectNotFoundException(getSelector().toString());
542        }
543        return node.isCheckable();
544    }
545
546    /**
547     * Check if the UI element's <code>enabled</code> property is currently true
548     *
549     * @return true if it is else false
550     * @throws UiObjectNotFoundException
551     */
552    public boolean isEnabled() throws UiObjectNotFoundException {
553        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
554        if(node == null) {
555            throw new UiObjectNotFoundException(getSelector().toString());
556        }
557        return node.isEnabled();
558    }
559
560    /**
561     * Check if the UI element's <code>clickable</code> property is currently true
562     *
563     * @return true if it is else false
564     * @throws UiObjectNotFoundException
565     */
566    public boolean isClickable() throws UiObjectNotFoundException {
567        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
568        if(node == null) {
569            throw new UiObjectNotFoundException(getSelector().toString());
570        }
571        return node.isClickable();
572    }
573
574    /**
575     * Check if the UI element's <code>focused</code> property is currently true
576     *
577     * @return true if it is else false
578     * @throws UiObjectNotFoundException
579     */
580    public boolean isFocused() throws UiObjectNotFoundException {
581        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
582        if(node == null) {
583            throw new UiObjectNotFoundException(getSelector().toString());
584        }
585        return node.isFocused();
586    }
587
588    /**
589     * Check if the UI element's <code>focusable</code> property is currently true
590     *
591     * @return true if it is else false
592     * @throws UiObjectNotFoundException
593     */
594    public boolean isFocusable() throws UiObjectNotFoundException {
595        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
596        if(node == null) {
597            throw new UiObjectNotFoundException(getSelector().toString());
598        }
599        return node.isFocusable();
600    }
601
602    /**
603     * Check if the UI element's <code>scrollable</code> property is currently true
604     *
605     * @return true if it is else false
606     * @throws UiObjectNotFoundException
607     */
608    public boolean isScrollable() throws UiObjectNotFoundException {
609        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
610        if(node == null) {
611            throw new UiObjectNotFoundException(getSelector().toString());
612        }
613        return node.isScrollable();
614    }
615
616    /**
617     * Check if the UI element's <code>long-clickable</code> property is currently true
618     *
619     * @return true if it is else false
620     * @throws UiObjectNotFoundException
621     */
622    public boolean isLongClickable() throws UiObjectNotFoundException {
623        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
624        if(node == null) {
625            throw new UiObjectNotFoundException(getSelector().toString());
626        }
627        return node.isLongClickable();
628    }
629
630    /**
631     * Reads the UI element's <code>package</code> property
632     *
633     * @return true if it is else false
634     * @throws UiObjectNotFoundException
635     */
636    public String getPackageName() throws UiObjectNotFoundException {
637        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
638        if(node == null) {
639            throw new UiObjectNotFoundException(getSelector().toString());
640        }
641        return safeStringReturn(node.getPackageName());
642    }
643
644    /**
645     * Returns the visible bounds of the UI element.
646     *
647     * If a portion of the UI element is visible, only the bounds of the visible portion are
648     * reported.
649     *
650     * @return Rect
651     * @throws UiObjectNotFoundException
652     * @see {@link #getBound()}
653     */
654    public Rect getVisibleBounds() throws UiObjectNotFoundException {
655        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
656        if(node == null) {
657            throw new UiObjectNotFoundException(getSelector().toString());
658        }
659        return getVisibleBounds(node);
660    }
661
662    /**
663     * Returns the UI element's <code>bounds</code> property. See {@link #getVisibleBounds()}
664     *
665     * @return Rect
666     * @throws UiObjectNotFoundException
667     */
668    public Rect getBounds() throws UiObjectNotFoundException {
669        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
670        if(node == null) {
671            throw new UiObjectNotFoundException(getSelector().toString());
672        }
673        Rect nodeRect = new Rect();
674        node.getBoundsInScreen(nodeRect);
675
676        return nodeRect;
677    }
678
679    /**
680     * Waits a specified length of time for a UI element to become visible.
681     *
682     * This method waits until the UI element becomes visible on the display, or
683     * until the timeout has elapsed. You can use this method in situations where
684     * the content that you want to select is not immediately displayed.
685     *
686     * @param timeout the amount of time to wait (in milliseconds)
687     * @return true if the UI element is displayed, else false if timeout elapsed while waiting
688     */
689    public boolean waitForExists(long timeout) {
690        if(findAccessibilityNodeInfo(timeout) != null) {
691            return true;
692        }
693        return false;
694    }
695
696    /**
697     * Waits a specified length of time for a UI element to become undetectable.
698     *
699     * This method waits until a UI element is no longer matchable, or until the
700     * timeout has elapsed.
701     *
702     * A UI element becomes undetectable when the {@link UiSelector} of the object is
703     * unable to find a match because the element has either changed its state or is no
704     * longer displayed.
705     *
706     * You can use this method when attempting to wait for some long operation
707     * to compete, such as downloading a large file or connecting to a remote server.
708     *
709     * @param timeout time to wait (in milliseconds)
710     * @return true if the element is gone before timeout elapsed, else false if timeout elapsed
711     * but a matching element is still found.
712     */
713    public boolean waitUntilGone(long timeout) {
714        long startMills = SystemClock.uptimeMillis();
715        long currentMills = 0;
716        while (currentMills <= timeout) {
717            if(findAccessibilityNodeInfo(0) == null)
718                return true;
719            currentMills = SystemClock.uptimeMillis() - startMills;
720            if(timeout > 0)
721                SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
722        }
723        return false;
724    }
725
726    /**
727     * Check if UI element exists.
728     *
729     * This methods performs a {@link #waitForExists(long)} with zero timeout. This
730     * basically returns immediately whether the UI element represented by this UiObject
731     * exists or not. If you need to wait longer for this UI element, then see
732     * {@link #waitForExists(long)}.
733     *
734     * @return true if the UI element represented by this UiObject does exist
735     */
736    public boolean exists() {
737        return waitForExists(0);
738    }
739
740    private String safeStringReturn(CharSequence cs) {
741        if(cs == null)
742            return "";
743        return cs.toString();
744    }
745}
746