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