UiObject.java revision 4ab790eccf6d5c27f542056b87d26d38f7caeeb3
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.AccessibilityNodeInfo;
24
25/**
26 * UiObject is designed to be a representation of displayed UI element. It is not in any way
27 * directly bound to a specific UI element. It holds information to find any displayed UI element
28 * that matches its selectors. This means it can be reused on any screen where a UI element
29 * exists to match its selector criteria. This help tests define a single UiObject say for
30 * an "OK" or tool-bar button and use it across many activities.
31 */
32public class UiObject {
33    private static final String LOG_TAG = UiObject.class.getSimpleName();
34    protected static final long WAIT_FOR_SELECTOR_TIMEOUT = 10 * 1000;
35    protected static final long WAIT_FOR_SELECTOR_POLL = 1000;
36    // set a default timeout to 5.5s, since ANR threshold is 5s
37    protected static final long WAIT_FOR_WINDOW_TMEOUT = 5500;
38    protected static final int SWIPE_MARGIN_LIMIT = 5;
39
40    protected UiSelector mSelector;
41    protected final UiDevice mDevice;
42    protected final UiAutomatorBridge mUiAutomationBridge;
43
44    /**
45     * Constructs a UiObject that references any UI element that may match the specified
46     * {@link UiSelector} selector. UiObject can be pre-constructed and reused across an
47     * application where applicable. A good example is a tool bar and its buttons.
48     * A toolbar may remain visible on the various views the application is displaying but
49     * may have different contextual buttons displayed for each. The same UiObject that
50     * describes the toolbar can be reused.<p/>
51     * It is a good idea in certain cases before using any operations of this UiObject is to
52     * call {@link #exists()} to verify if the UI element matching this object is actually
53     * visible on the screen. This way the test can gracefully handle this situation else
54     * a {@link UiObjectNotFoundException} will be thrown when using this object.
55     * @param selector
56     */
57    public UiObject(UiSelector selector) {
58        mUiAutomationBridge = UiDevice.getInstance().getAutomatorBridge();
59        mDevice = UiDevice.getInstance();
60        mSelector = selector;
61    }
62
63    /**
64     * Helper for debugging. During testing a test can dump the selector of the object into
65     * its logs of needed. <code>getSelector().toString();</code>
66     * @return {@link UiSelector}
67     */
68    public final UiSelector getSelector() {
69        return new UiSelector(mSelector);
70    }
71
72    /**
73     * Used in test operations to retrieve the {@link QueryController} to translate
74     * a {@link UiSelector} selector into an {@link AccessibilityNodeInfo}.
75     * @return {@link QueryController}
76     */
77    protected QueryController getQueryController() {
78        return mUiAutomationBridge.getQueryController();
79    }
80
81    /**
82     * Used in test operations to retrieve the {@link InteractionController} to perform
83     * finger actions such as tapping, swiping or entering text.
84     * @return {@link InteractionController}
85     */
86    protected InteractionController getInteractionController() {
87        return mUiAutomationBridge.getInteractionController();
88    }
89
90    /**
91     * Creates a new UiObject that points at a child UI element of the currently pointed
92     * to element by this object. UI element are considered layout elements as well as UI
93     * widgets. A layout element could have child widgets like buttons and text labels.
94     * @param selector
95     * @return a new UiObject with a new selector. It doesn't test if the object exists.
96     */
97    public UiObject getChild(UiSelector selector) throws UiObjectNotFoundException {
98        return new UiObject(getSelector().childSelector(selector));
99    }
100
101    /**
102     * Creates a new UiObject that points at a child UI element of the parent of this object.
103     * Essentially this is starting the search from any one of the siblings UI element of this
104     * element.
105     * @param selector
106     * @return
107     * @throws UiObjectNotFoundException
108     */
109    public UiObject getFromParent(UiSelector selector) throws UiObjectNotFoundException {
110        return new UiObject(getSelector().fromParent(selector));
111    }
112
113    /**
114     * Counts the child UI elements immediately under the UI element currently referenced by
115     * this UiObject.
116     * @return the count of child UI elements.
117     * @throws UiObjectNotFoundException
118     */
119    public int getChildCount() throws UiObjectNotFoundException {
120        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
121        if(node == null) {
122            throw new UiObjectNotFoundException(getSelector().toString());
123        }
124        return node.getChildCount();
125    }
126
127    /**
128     * Helper method to allow for a specific content to become present on the
129     * screen before moving on to do other operations.
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 object. Also see
163     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
164     * {@link #scrollForward()}. This method will perform the swipe gesture over any
165     * surface. The targeted UI element does not need to have the attribute
166     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
167     * @param steps indicates the number of injected move steps into the system. More steps
168     * injected the smoother the motion and slower.
169     * @return
170     * @throws UiObjectNotFoundException
171     */
172    public boolean swipeUp(int steps) throws UiObjectNotFoundException {
173        Rect rect = getBounds();
174        if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
175            return false; // too small to swipe
176        return getInteractionController().swipe(rect.centerX(),
177                rect.bottom - SWIPE_MARGIN_LIMIT, rect.centerX(), rect.top + SWIPE_MARGIN_LIMIT,
178                steps);
179    }
180
181    /**
182     * Perform the action on the UI element that is represented by this object, Also see
183     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
184     * {@link #scrollForward()}. This method will perform the swipe gesture over any
185     * surface. The targeted UI element does not need to have the attribute
186     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
187     * @param steps indicates the number of injected move steps into the system. More steps
188     * injected the smoother the motion and slower.
189     * @return
190     * @throws UiObjectNotFoundException
191     */
192    public boolean swipeDown(int steps) throws UiObjectNotFoundException {
193        Rect rect = getBounds();
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     * @param steps indicates the number of injected move steps into the system. More steps
208     * injected the smoother the motion and slower.
209     * @return
210     * @throws UiObjectNotFoundException
211     */
212    public boolean swipeLeft(int steps) throws UiObjectNotFoundException {
213        Rect rect = getBounds();
214        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
215            return false; // too small to swipe
216        return getInteractionController().swipe(rect.right - SWIPE_MARGIN_LIMIT,
217                rect.centerY(), rect.left + SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
218    }
219
220    /**
221     * Perform the action on the UI element that is represented by this object. Also see
222     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
223     * {@link #scrollForward()}. This method will perform the swipe gesture over any
224     * surface. The targeted UI element does not need to have the attribute
225     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
226     * @param steps indicates the number of injected move steps into the system. More steps
227     * injected the smoother the motion and slower.
228     * @return
229     * @throws UiObjectNotFoundException
230     */
231    public boolean swipeRight(int steps) throws UiObjectNotFoundException {
232        Rect rect = getBounds();
233        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
234            return false; // too small to swipe
235        return getInteractionController().swipe(rect.left + SWIPE_MARGIN_LIMIT,
236                rect.centerY(), rect.right - SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
237    }
238
239    /**
240     * In rare situations, the node hierarchy returned from accessibility will
241     * return items that are slightly OFF the screen (list view contents). This method
242     * validate that the item is visible to avoid click operation failures. It will adjust
243     * the center of the click as much as possible to be within visible bounds to make
244     * the click successful.
245     * @param node
246     * @return the same AccessibilityNodeInfo passed in as argument
247     */
248    private Rect getVisibleBounds(AccessibilityNodeInfo node) {
249        if (node == null) {
250            return null;
251        }
252
253        // targeted node's bounds
254        Rect nodeRect = new Rect();
255        node.getBoundsInScreen(nodeRect);
256
257        // is the targeted node within a scrollable container?
258        AccessibilityNodeInfo scrollableParentNode = getScrollableParent(node);
259        if(scrollableParentNode == null) {
260            // nothing to adjust for so return the node's Rect as is
261            return nodeRect;
262        }
263
264        // Scrollable parent's visible bounds
265        Rect parentRect = new Rect();
266        scrollableParentNode.getBoundsInScreen(parentRect);
267        // adjust for partial clipping of targeted by parent node if required
268        nodeRect.intersect(parentRect);
269        return nodeRect;
270    }
271
272    /**
273     * Walk the hierarchy up to find a scrollable parent. A scrollable parent indicates that
274     * this node may be in a content where it is partially visible due to scrolling. its
275     * clickable center maybe invisible and adjustments should be made to the click coordinates.
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 over the UI element this object represents. </p>
292     * Take note that the UI element directly represented by this UiObject may not have
293     * its attribute <code>clickable</code> set to <code>true</code> and yet still perform
294     * the click successfully. This is because all clicks are performed in the center of the
295     * targeted UI element and if this element is a child or a parent that wraps the clickable
296     * element the operation will still succeed. This is the reason this operation does not
297     * not validate the targeted UI element is clickable or not before operating.
298     * @return true id successful else false
299     * @throws UiObjectNotFoundException
300     */
301    public boolean click() throws UiObjectNotFoundException {
302        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
303        if(node == null) {
304            throw new UiObjectNotFoundException(getSelector().toString());
305        }
306        Rect rect = getVisibleBounds(node);
307        return getInteractionController().click(rect.centerX(), rect.centerY());
308    }
309
310    /**
311    *
312    * Performs a click over the represented UI element, and expect window transition as
313    * a result.</p>
314    *
315    * This method is similar to the plain {@link UiObject#click()}, with an important difference
316    * that the method goes further to expect a window transition as a result of the tap. Some
317    * examples of a window transition:
318    * <li>launching a new activity</li>
319    * <li>bringing up a pop-up menu</li>
320    * <li>bringing up a dialog</li>
321    * This method is intended for reliably handling window transitions that would typically lasts
322    * longer than the usual preset timeouts.
323    *
324    * @return true if the event was triggered, else false
325    * @throws UiObjectNotFoundException
326    */
327    public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException {
328        return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT);
329    }
330
331    /**
332     *
333     * Performs a click over the represented UI element, and expect window transition as
334     * a result.</p>
335     *
336     * This method is similar to the plain {@link UiObject#click()}, with an important difference
337     * that the method goes further to expect a window transition as a result of the tap. Some
338     * examples of a window transition:
339     * <li>launching a new activity</li>
340     * <li>bringing up a pop-up menu</li>
341     * <li>bringing up a dialog</li>
342     * This method is intended for reliably handling window transitions that would typically lasts
343     * longer than the usual preset timeouts.
344     *
345     * @param timeout timeout before giving up on waiting for new window
346     * @return true if the event was triggered, else false
347     * @throws UiObjectNotFoundException
348     */
349    public boolean clickAndWaitForNewWindow(long timeout) 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().clickAndWaitForNewWindow(
356                rect.centerX(), rect.centerY(), timeout);
357    }
358
359    /**
360     * Clicks the top and left corner of the UI element.
361     * @return true on success
362     * @throws Exception
363     */
364    public boolean clickTopLeft() 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().click(rect.left + 5, rect.top + 5);
371    }
372
373    /**
374     * Long clicks a UI element pointed to by the {@link UiSelector} selector of this object at the
375     * bottom right corner. The duration of what will be considered as a long click is dynamically
376     * retrieved from the system and used in the operation.
377     * @return true if operation was successful
378     * @throws UiObjectNotFoundException
379     */
380    public boolean longClickBottomRight() 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().longTap(rect.right - 5, rect.bottom - 5);
387    }
388
389    /**
390     * Clicks the bottom and right corner of the UI element.
391     * @return true on success
392     * @throws Exception
393     */
394    public boolean clickBottomRight() 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().click(rect.right - 5, rect.bottom - 5);
401    }
402
403    /**
404     * Long clicks a UI element pointed to by the {@link UiSelector} selector of this object. The
405     * duration of what will be considered as a long click is dynamically retrieved from the
406     * system and used in the operation.
407     * @return true if operation was successful
408     * @throws UiObjectNotFoundException
409     */
410    public boolean longClick() 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.centerX(), rect.centerY());
417    }
418
419    /**
420     * Long clicks a UI element pointed to by the {@link UiSelector} selector of this object at the
421     * top left corner. The duration of what will be considered as a long click is dynamically
422     * retrieved from the system and used in the operation.
423     * @return true if operation was successful
424     * @throws UiObjectNotFoundException
425     */
426    public boolean longClickTopLeft() throws UiObjectNotFoundException {
427        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
428        if(node == null) {
429            throw new UiObjectNotFoundException(getSelector().toString());
430        }
431        Rect rect = getVisibleBounds(node);
432        return getInteractionController().longTap(rect.left + 5, rect.top + 5);
433    }
434
435    /**
436     * This function can be used to return the UI element's displayed text. This applies to
437     * UI element that are displaying labels or edit fields.
438     * @return text value of the current node represented by this UiObject
439     * @throws UiObjectNotFoundException if no match could be found
440     */
441    public String getText() throws UiObjectNotFoundException {
442        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
443        if(node == null) {
444            throw new UiObjectNotFoundException(getSelector().toString());
445        }
446        String retVal = safeStringReturn(node.getText());
447        Log.d(LOG_TAG, String.format("getText() = %s", retVal));
448        return retVal;
449    }
450
451    /**
452     * Retrieves the content-description value set for the UI element. In Accessibility, the
453     * spoken text to speech is usually the <code>text</code> property of the UI element. If that
454     * is not present, then the content-description is spoken. Many UI element such as buttons on
455     * a toolbar may be too small to incorporate a visible text on their surfaces, so in such
456     * cases, these UI elements must have their content-description fields populated to describe
457     * them when accessibility is active.
458     * @return value of node attribute "content_desc"
459     * @throws UiObjectNotFoundException
460     */
461    public String getContentDescription() throws UiObjectNotFoundException {
462        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
463        if(node == null) {
464            throw new UiObjectNotFoundException(getSelector().toString());
465        }
466        return safeStringReturn(node.getContentDescription());
467    }
468
469    /**
470     * First this function clears the existing text from the field. If this is not the intended
471     * behavior, do a {@link #getText()} first, modify the text and then use this function.
472     * The {@link UiSelector} selector of this object MUST be pointing directly at a UI element
473     * that can accept edits. The way this method works is by first performing a {@link #click()}
474     * on the edit field to set focus then it begins injecting the content of the text param
475     * into the system. Since the targeted field is in focus, the text contents should be
476     * inserted into the field.<p/>
477     * @param text
478     * @return true if operation is successful
479     * @throws UiObjectNotFoundException
480     */
481    public boolean setText(String text) throws UiObjectNotFoundException {
482        clearTextField();
483        return getInteractionController().sendText(text);
484    }
485
486    /**
487     * The object targeted must be an edit field capable of performing text insert. This
488     * method sets focus at the left edge of the field and long presses to select
489     * existing text. It will then follow that with delete press. Note: It is possible
490     * that not all the text is selected especially if the text contained separators
491     * such as spaces, slashes, at signs etc... The function will attempt to use the
492     * Select-All option if one is displayed to ensure full text selection.
493     * @throws UiObjectNotFoundException
494     */
495    public void clearTextField() throws UiObjectNotFoundException {
496        // long click left + center
497        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
498        if(node == null) {
499            throw new UiObjectNotFoundException(getSelector().toString());
500        }
501        Rect rect = getVisibleBounds(node);
502        getInteractionController().longTap(rect.left + 20, rect.centerY());
503        // check if the edit menu is open
504        UiObject selectAll = new UiObject(new UiSelector().descriptionContains("Select all"));
505        if(selectAll.waitForExists(50))
506            selectAll.click();
507        // wait for the selection
508        SystemClock.sleep(250);
509        // delete it
510        getInteractionController().sendKey(KeyEvent.KEYCODE_DEL, 0);
511    }
512
513    /**
514     * Check if item pointed to by this object's selector is currently checked. <p/>
515     * Take note that the {@link UiSelector} specified for this UiObjecy must be pointing
516     * directly at the element you wish to query for this attribute.
517     * @return true if it is else false
518     */
519    public boolean isChecked() throws UiObjectNotFoundException {
520        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
521        if(node == null) {
522            throw new UiObjectNotFoundException(getSelector().toString());
523        }
524        return node.isChecked();
525    }
526
527    /**
528     * Check if item pointed to by this object's selector is currently selected.<p/>
529     * Take note that the {@link UiSelector} specified for this UiObjecy must be pointing
530     * directly at the element you wish to query for this attribute.
531     * @return true if it is else false
532     * @throws UiObjectNotFoundException
533     */
534    public boolean isSelected() throws UiObjectNotFoundException {
535        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
536        if(node == null) {
537            throw new UiObjectNotFoundException(getSelector().toString());
538        }
539        return node.isSelected();
540    }
541
542    /**
543     * Check if item pointed to by this object's selector can be checked and unchecked. <p/>
544     * Take note that the {@link UiSelector} specified for this UiObjecy must be pointing
545     * directly at the element you wish to query for this attribute.
546     * @return true if it is else false
547     * @throws UiObjectNotFoundException
548     */
549    public boolean isCheckable() throws UiObjectNotFoundException {
550        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
551        if(node == null) {
552            throw new UiObjectNotFoundException(getSelector().toString());
553        }
554        return node.isCheckable();
555    }
556
557    /**
558     * Check if item pointed to by this object's selector is currently not grayed out. <p/>
559     * Take note that the {@link UiSelector} specified for this UiObjecy must be pointing
560     * directly at the element you wish to query for this attribute.
561     * @return true if it is else false
562     * @throws UiObjectNotFoundException
563     */
564    public boolean isEnabled() throws UiObjectNotFoundException {
565        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
566        if(node == null) {
567            throw new UiObjectNotFoundException(getSelector().toString());
568        }
569        return node.isEnabled();
570    }
571
572    /**
573     * Check if item pointed to by this object's selector responds to clicks <p/>
574     * Take note that the {@link UiSelector} specified for this UiObjecy must be pointing
575     * directly at the element you wish to query for this attribute.
576     * @return true if it is else false
577     * @throws UiObjectNotFoundException
578     */
579    public boolean isClickable() throws UiObjectNotFoundException {
580        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
581        if(node == null) {
582            throw new UiObjectNotFoundException(getSelector().toString());
583        }
584        return node.isClickable();
585    }
586
587    /**
588     * Check if item pointed to by this object's selector is currently focused.
589     * Objects with focus will receive key stroke events when key events
590     * are fired
591     * @return true if it is else false
592     * @throws UiObjectNotFoundException
593     */
594    public boolean isFocused() throws UiObjectNotFoundException {
595        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
596        if(node == null) {
597            throw new UiObjectNotFoundException(getSelector().toString());
598        }
599        return node.isFocused();
600    }
601
602    /**
603     * Check if item pointed to by this object's selector is capable of receiving focus. <p/>
604     * Take note that the {@link UiSelector} selector specified for this UiObjecy must be pointing
605     * directly at the element you wish to query for this attribute.
606     * @return true if it is else false
607     * @throws UiObjectNotFoundException
608     */
609    public boolean isFocusable() throws UiObjectNotFoundException {
610        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
611        if(node == null) {
612            throw new UiObjectNotFoundException(getSelector().toString());
613        }
614        return node.isFocusable();
615    }
616
617    /**
618     * Check if item pointed to by this object's selector can be scrolled.<p/>
619     * Take note that the {@link UiSelector} selector specified for this UiObjecy must be pointing
620     * directly at the element you wish to query for this attribute.
621     * @return true if it is else false
622     * @throws UiObjectNotFoundException
623     */
624    public boolean isScrollable() throws UiObjectNotFoundException {
625        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
626        if(node == null) {
627            throw new UiObjectNotFoundException(getSelector().toString());
628        }
629        return node.isScrollable();
630    }
631
632    /**
633     * Check if item pointed to by this object's selector responds to long clicks.<p/>
634     * Take note that the {@link UiSelector} selector specified for this UiObjecy must be pointing
635     * directly at the element you wish to query for this attribute.
636     * @return true if it is else false
637     * @throws UiObjectNotFoundException
638     */
639    public boolean isLongClickable() throws UiObjectNotFoundException {
640        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
641        if(node == null) {
642            throw new UiObjectNotFoundException(getSelector().toString());
643        }
644        return node.isLongClickable();
645    }
646
647    /**
648     * This method retrieves the package name of the currently displayed content on the screen.
649     * This can be helpful when verifying that the expected package is on the screen before
650     * proceeding with further test operations.
651     * @return String package name
652     * @throws UiObjectNotFoundException
653     */
654    public String getPackageName() throws UiObjectNotFoundException {
655        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
656        if(node == null) {
657            throw new UiObjectNotFoundException(getSelector().toString());
658        }
659        return safeStringReturn(node.getPackageName());
660    }
661
662    /**
663     * Reports the absolute visible screen bounds of the object. If a portion of the UI element
664     * is visible, only the bounds of the visible portion of the UI element are reported. This
665     * becomes important when using bounds to calculate exact coordinates for tapping the element.
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        return getVisibleBounds(node);
675    }
676
677    /**
678     * This method will wait for a UI element to become visible on the display. It
679     * can be used for situations where the content to be selected is not yet displayed
680     * and the time it will be present is unknown.
681     * @param timeout
682     * @return true if the UI element exists else false for timeout while waiting
683     */
684    public boolean waitForExists(long timeout) {
685        if(findAccessibilityNodeInfo(timeout) != null) {
686            return true;
687        }
688        return false;
689    }
690
691    /**
692     * Helper to wait for a specified object to no longer be detectable. This can be
693     * useful when having to wait for a progress dialog to finish.
694     * @param timeout
695     * @return true if gone before timeout else false for still present at timeout
696     */
697    public boolean waitUntilGone(long timeout) {
698        long startMills = SystemClock.uptimeMillis();
699        long currentMills = 0;
700        while (currentMills <= timeout) {
701            if(findAccessibilityNodeInfo(0) == null)
702                return true;
703            currentMills = SystemClock.uptimeMillis() - startMills;
704            if(timeout > 0)
705                SystemClock.sleep(WAIT_FOR_SELECTOR_POLL);
706        }
707        return false;
708    }
709
710    /**
711     * This methods performs a {@link #waitForExists(long)} with zero timeout. This
712     * basically returns immediately whether the UI element represented by this UiObject
713     * exists or not. If you need to wait longer for this UI element, then see
714     * {@link #waitForExists(long)}.
715     * @return true if the UI element represented by this UiObject does exist
716     */
717    public boolean exists() {
718        return waitForExists(0);
719    }
720
721    private String safeStringReturn(CharSequence cs) {
722        if(cs == null)
723            return "";
724        return cs.toString();
725    }
726}
727