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