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