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