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