public class

UiObject2

extends java.lang.Object

implements androidx.test.uiautomator.Searchable

 java.lang.Object

↳androidx.test.uiautomator.UiObject2

Gradle dependencies

compile group: 'androidx.test.uiautomator', name: 'uiautomator', version: '2.2.0'

  • groupId: androidx.test.uiautomator
  • artifactId: uiautomator
  • version: 2.2.0

Artifact androidx.test.uiautomator:uiautomator:2.2.0 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test.uiautomator:uiautomator com.android.support.test.uiautomator:uiautomator

Androidx class mapping:

androidx.test.uiautomator.UiObject2 android.support.test.uiautomator.UiObject2

Overview

A UiObject2 represents a UI element. Unlike UiObject, it is bound to a particular view instance and can become stale if the underlying view object is destroyed. As a result, it may be necessary to call UiDevice.findObject(BySelector) to obtain a new UiObject2 instance if the UI changes significantly.

Summary

Methods
public voidclear()

Clears the text content if this object is an editable field.

public voidclick()

Clicks on this object.

public voidclick(long duration)

Performs a click on this object that lasts for duration milliseconds.

public java.lang.ObjectclickAndWait(EventCondition<java.lang.Object> condition, long timeout)

Clicks on this object, and waits for the given condition to become true.

public voiddrag(Point dest)

Drags this object to the specified location.

public voiddrag(Point dest, int speed)

Drags this object to the specified location.

public booleanequals(java.lang.Object object)

public UiObject2findObject(BySelector selector)

Searches all elements under this object and returns the first object to match the criteria, or null if no matching objects are found.

public java.util.List<UiObject2>findObjects(BySelector selector)

Searches all elements under this object and returns all objects that match the criteria.

public booleanfling(Direction direction)

Performs a fling gesture on this object.

public booleanfling(Direction direction, int speed)

Performs a fling gesture on this object.

public java.lang.StringgetApplicationPackage()

Returns the package name of the app that this object belongs to.

public intgetChildCount()

Returns the number of child elements directly under this object.

public java.util.List<UiObject2>getChildren()

Returns a collection of the child elements directly under this object.

public java.lang.StringgetClassName()

Returns the class name of the underlying android.view.View represented by this object.

public java.lang.StringgetContentDescription()

Returns the content description for this object.

public UiObject2getParent()

Returns this object's parent, or null if it has no parent.

public java.lang.StringgetResourceName()

Returns the fully qualified resource name for this object's id.

public java.lang.StringgetText()

Returns the text value for this object.

public RectgetVisibleBounds()

Returns the visible bounds of this object in screen coordinates.

public PointgetVisibleCenter()

Returns a point in the center of the visible bounds of this object.

public inthashCode()

public booleanhasObject(BySelector selector)

Returns whether there is a match for the given criteria under this object.

public booleanisCheckable()

Returns whether this object is checkable.

public booleanisChecked()

Returns whether this object is checked.

public booleanisClickable()

Returns whether this object is clickable.

public booleanisEnabled()

Returns whether this object is enabled.

public booleanisFocusable()

Returns whether this object is focusable.

public booleanisFocused()

Returns whether this object is focused.

public booleanisLongClickable()

Returns whether this object is long clickable.

public booleanisScrollable()

Returns whether this object is scrollable.

public booleanisSelected()

Returns whether this object is selected.

public voidlegacySetText(java.lang.String text)

Set the text content by sending individual key codes.

public voidlongClick()

Performs a long click on this object.

public voidpinchClose(float percent)

Performs a pinch close gesture on this object.

public voidpinchClose(float percent, int speed)

Performs a pinch close gesture on this object.

public voidpinchOpen(float percent)

Performs a pinch open gesture on this object.

public voidpinchOpen(float percent, int speed)

Performs a pinch open gesture on this object.

public voidrecycle()

Recycle this object.

public booleanscroll(Direction direction, float percent)

Performs a scroll gesture on this object.

public booleanscroll(Direction direction, float percent, int speed)

Performs a scroll gesture on this object.

public voidsetGestureMargin(int margin)

Sets the margins used for gestures in pixels.

public voidsetGestureMargins(int left, int top, int right, int bottom)

Sets the margins used for gestures in pixels.

public voidsetText(java.lang.String text)

Sets the text content if this object is an editable field.

public voidswipe(Direction direction, float percent)

Performs a swipe gesture on this object.

public voidswipe(Direction direction, float percent, int speed)

Performs a swipe gesture on this object.

public java.lang.Objectwait(SearchCondition<java.lang.Object> condition, long timeout)

Waits for given the condition to be met.

public java.lang.Objectwait(UiObject2Condition<java.lang.Object> condition, long timeout)

Waits for given the condition to be met.

from java.lang.Objectclone, finalize, getClass, notify, notifyAll, toString, wait, wait, wait

Methods

public boolean equals(java.lang.Object object)

public int hashCode()

public void recycle()

Recycle this object.

public void setGestureMargin(int margin)

Sets the margins used for gestures in pixels.

public void setGestureMargins(int left, int top, int right, int bottom)

Sets the margins used for gestures in pixels.

public java.lang.Object wait(UiObject2Condition<java.lang.Object> condition, long timeout)

Waits for given the condition to be met.

Parameters:

condition: The UiObject2Condition to evaluate.
timeout: Maximum amount of time to wait in milliseconds.

Returns:

The final result returned by the condition, or null if the condition was not met before the timeout.

public java.lang.Object wait(SearchCondition<java.lang.Object> condition, long timeout)

Waits for given the condition to be met.

Parameters:

condition: The SearchCondition to evaluate.
timeout: Maximum amount of time to wait in milliseconds.

Returns:

The final result returned by the condition, or null if the condition was not met before the timeout.

public UiObject2 getParent()

Returns this object's parent, or null if it has no parent.

public int getChildCount()

Returns the number of child elements directly under this object.

public java.util.List<UiObject2> getChildren()

Returns a collection of the child elements directly under this object.

public boolean hasObject(BySelector selector)

Returns whether there is a match for the given criteria under this object.

public UiObject2 findObject(BySelector selector)

Searches all elements under this object and returns the first object to match the criteria, or null if no matching objects are found.

public java.util.List<UiObject2> findObjects(BySelector selector)

Searches all elements under this object and returns all objects that match the criteria.

public Rect getVisibleBounds()

Returns the visible bounds of this object in screen coordinates.

public Point getVisibleCenter()

Returns a point in the center of the visible bounds of this object.

public java.lang.String getClassName()

Returns the class name of the underlying android.view.View represented by this object.

public java.lang.String getContentDescription()

Returns the content description for this object.

public java.lang.String getApplicationPackage()

Returns the package name of the app that this object belongs to.

public java.lang.String getResourceName()

Returns the fully qualified resource name for this object's id.

public java.lang.String getText()

Returns the text value for this object.

public boolean isCheckable()

Returns whether this object is checkable.

public boolean isChecked()

Returns whether this object is checked.

public boolean isClickable()

Returns whether this object is clickable.

public boolean isEnabled()

Returns whether this object is enabled.

public boolean isFocusable()

Returns whether this object is focusable.

public boolean isFocused()

Returns whether this object is focused.

public boolean isLongClickable()

Returns whether this object is long clickable.

public boolean isScrollable()

Returns whether this object is scrollable.

public boolean isSelected()

Returns whether this object is selected.

public void clear()

Clears the text content if this object is an editable field.

public void click()

Clicks on this object.

public void click(long duration)

Performs a click on this object that lasts for duration milliseconds.

public java.lang.Object clickAndWait(EventCondition<java.lang.Object> condition, long timeout)

Clicks on this object, and waits for the given condition to become true.

public void drag(Point dest)

Drags this object to the specified location.

Parameters:

dest: The end point that this object should be dragged to.

public void drag(Point dest, int speed)

Drags this object to the specified location.

Parameters:

dest: The end point that this object should be dragged to.
speed: The speed at which to perform this gesture in pixels per second.

public void longClick()

Performs a long click on this object.

public void pinchClose(float percent)

Performs a pinch close gesture on this object.

Parameters:

percent: The size of the pinch as a percentage of this object's size.

public void pinchClose(float percent, int speed)

Performs a pinch close gesture on this object.

Parameters:

percent: The size of the pinch as a percentage of this object's size.
speed: The speed at which to perform this gesture in pixels per second.

public void pinchOpen(float percent)

Performs a pinch open gesture on this object.

Parameters:

percent: The size of the pinch as a percentage of this object's size.

public void pinchOpen(float percent, int speed)

Performs a pinch open gesture on this object.

Parameters:

percent: The size of the pinch as a percentage of this object's size.
speed: The speed at which to perform this gesture in pixels per second.

public void swipe(Direction direction, float percent)

Performs a swipe gesture on this object.

Parameters:

direction: The direction in which to swipe.
percent: The length of the swipe as a percentage of this object's size.

public void swipe(Direction direction, float percent, int speed)

Performs a swipe gesture on this object.

Parameters:

direction: The direction in which to swipe.
percent: The length of the swipe as a percentage of this object's size.
speed: The speed at which to perform this gesture in pixels per second.

public boolean scroll(Direction direction, float percent)

Performs a scroll gesture on this object.

Parameters:

direction: The direction in which to scroll.
percent: The distance to scroll as a percentage of this object's visible size.

Returns:

Whether the object can still scroll in the given direction.

public boolean scroll(Direction direction, float percent, int speed)

Performs a scroll gesture on this object.

Parameters:

direction: The direction in which to scroll.
percent: The distance to scroll as a percentage of this object's visible size.
speed: The speed at which to perform this gesture in pixels per second.

Returns:

Whether the object can still scroll in the given direction.

public boolean fling(Direction direction)

Performs a fling gesture on this object.

Parameters:

direction: The direction in which to fling.

Returns:

Whether the object can still scroll in the given direction.

public boolean fling(Direction direction, int speed)

Performs a fling gesture on this object.

Parameters:

direction: The direction in which to fling.
speed: The speed at which to perform this gesture in pixels per second.

Returns:

Whether the object can still scroll in the given direction.

public void legacySetText(java.lang.String text)

Set the text content by sending individual key codes.

public void setText(java.lang.String text)

Sets the text content if this object is an editable field.

Source

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.test.uiautomator;

import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityNodeInfo;

import java.util.ArrayList;
import java.util.List;

/**
 * A {@link UiObject2} represents a UI element. Unlike {@link UiObject}, it is bound to a particular
 * view instance and can become stale if the underlying view object is destroyed. As a result, it
 * may be necessary to call {@link UiDevice#findObject(BySelector)} to obtain a new
 * {@link UiObject2} instance if the UI changes significantly.
 */
public class UiObject2 implements Searchable {

    private static final String TAG = UiObject2.class.getSimpleName();

    private UiDevice mDevice;
    private Gestures mGestures;
    private GestureController mGestureController;
    private BySelector mSelector;  // Hold this mainly for debugging
    private AccessibilityNodeInfo mCachedNode;
    private DisplayMetrics mDisplayMetrics;

    // Margins
    private int mMarginLeft   = 5;
    private int mMarginTop    = 5;
    private int mMarginRight  = 5;
    private int mMarginBottom = 5;

    // Default gesture speeds
    private static final int DEFAULT_SWIPE_SPEED  = 5000;
    private static final int DEFAULT_SCROLL_SPEED = 5000;
    private static final int DEFAULT_FLING_SPEED = 7500;
    private static final int DEFAULT_DRAG_SPEED = 2500;
    private static final int DEFAULT_PINCH_SPEED = 2500;
    // Short, since we should stop scrolling after the gesture completes.
    private final long SCROLL_TIMEOUT = 1000;
    // Longer, since we may continue to scroll after the gesture completes.
    private final long FLING_TIMEOUT = 5000;

    // Get wait functionality from a mixin
    private WaitMixin<UiObject2> mWaitMixin = new WaitMixin<UiObject2>(this);


    /** Package-private constructor. Used by {@link UiDevice#findObject(BySelector)}. */
    UiObject2(UiDevice device, BySelector selector, AccessibilityNodeInfo cachedNode) {
        mDevice = device;
        mSelector = selector;
        mCachedNode = cachedNode;
        mGestures = Gestures.getInstance(device);
        mGestureController = GestureController.getInstance(device);
        mDisplayMetrics = mDevice.getInstrumentation().getContext().getResources()
                .getDisplayMetrics();
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object == null) {
            return false;
        }
        if (getClass() != object.getClass()) {
            return false;
        }
        try {
            UiObject2 other = (UiObject2)object;
            return getAccessibilityNodeInfo().equals(other.getAccessibilityNodeInfo());
        } catch (StaleObjectException e) {
            return false;
        }
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        return getAccessibilityNodeInfo().hashCode();
    }

    /** Recycle this object. */
    public void recycle() {
        mCachedNode.recycle();
        mCachedNode = null;
    }


    // Settings

    /** Sets the margins used for gestures in pixels. */
    public void setGestureMargin(int margin) {
        setGestureMargins(margin, margin, margin, margin);
    }

    /** Sets the margins used for gestures in pixels. */
    public void setGestureMargins(int left, int top, int right, int bottom) {
        mMarginLeft = left;
        mMarginTop = top;
        mMarginRight = right;
        mMarginBottom = bottom;
    }


    // Wait functions

    /**
     * Waits for given the {@code condition} to be met.
     *
     * @param condition The {@link UiObject2Condition} to evaluate.
     * @param timeout Maximum amount of time to wait in milliseconds.
     * @return The final result returned by the {@code condition}, or null if the {@code condition}
     * was not met before the {@code timeout}.
     */
    public <R> R wait(UiObject2Condition<R> condition, long timeout) {
        return mWaitMixin.wait(condition, timeout);
    }

    /**
     * Waits for given the {@code condition} to be met.
     *
     * @param condition The {@link SearchCondition} to evaluate.
     * @param timeout Maximum amount of time to wait in milliseconds.
     * @return The final result returned by the {@code condition}, or null if the {@code condition}
     * was not met before the {@code timeout}.
     */
    public <R> R wait(SearchCondition<R> condition, long timeout) {
        return mWaitMixin.wait(condition, timeout);
    }

    // Search functions

    /** Returns this object's parent, or null if it has no parent. */
    public UiObject2 getParent() {
        AccessibilityNodeInfo parent = getAccessibilityNodeInfo().getParent();
        return parent != null ? new UiObject2(getDevice(), mSelector, parent) : null;
    }

    /** Returns the number of child elements directly under this object. */
    public int getChildCount() {
        return getAccessibilityNodeInfo().getChildCount();
    }

    /** Returns a collection of the child elements directly under this object. */
    public List<UiObject2> getChildren() {
        return findObjects(By.depth(1));
    }

    /** Returns whether there is a match for the given criteria under this object. */
    public boolean hasObject(BySelector selector) {
        AccessibilityNodeInfo node =
                ByMatcher.findMatch(getDevice(), selector, getAccessibilityNodeInfo());
        if (node != null) {
            node.recycle();
            return true;
        }
        return false;
    }

    /**
     * Searches all elements under this object and returns the first object to match the criteria,
     * or null if no matching objects are found.
     */
    public UiObject2 findObject(BySelector selector) {
        AccessibilityNodeInfo node =
                ByMatcher.findMatch(getDevice(), selector, getAccessibilityNodeInfo());
        return node != null ? new UiObject2(getDevice(), selector, node) : null;
    }

    /** Searches all elements under this object and returns all objects that match the criteria. */
    public List<UiObject2> findObjects(BySelector selector) {
        List<UiObject2> ret = new ArrayList<UiObject2>();
        for (AccessibilityNodeInfo node :
                ByMatcher.findMatches(getDevice(), selector, getAccessibilityNodeInfo())) {

            ret.add(new UiObject2(getDevice(), selector, node));
        }

        return ret;
    }


    // Attribute accessors

    /** Returns the visible bounds of this object in screen coordinates. */
    public Rect getVisibleBounds() {
        return getVisibleBounds(getAccessibilityNodeInfo());
    }

    /** Returns the visible bounds of this object with the margins removed. */
    private Rect getVisibleBoundsForGestures() {
        Rect ret = getVisibleBounds();
        ret.left = ret.left + mMarginLeft;
        ret.top = ret.top + mMarginTop;
        ret.right = ret.right - mMarginRight;
        ret.bottom = ret.bottom - mMarginBottom;
        return ret;
    }

    /** Returns the visible bounds of {@code node} in screen coordinates. */
    private Rect getVisibleBounds(AccessibilityNodeInfo node) {
        // Get the object bounds in screen coordinates
        Rect ret = new Rect();
        node.getBoundsInScreen(ret);

        // Trim any portion of the bounds that are not on the screen
        Rect screen = new Rect(0, 0, getDevice().getDisplayWidth(), getDevice().getDisplayHeight());
        ret.intersect(screen);

        // On platforms that give us access to the node's window
        if (UiDevice.API_LEVEL_ACTUAL >= Build.VERSION_CODES.LOLLIPOP) {
            // Trim any portion of the bounds that are outside the window
            Rect window = new Rect();
            if (node.getWindow() != null) {
                node.getWindow().getBoundsInScreen(window);
                ret.intersect(window);
            }
        }

        // Find the visible bounds of our first scrollable ancestor
        AccessibilityNodeInfo ancestor = null;
        for (ancestor = node.getParent(); ancestor != null; ancestor = ancestor.getParent()) {
            // If this ancestor is scrollable
            if (ancestor.isScrollable()) {
                // Trim any portion of the bounds that are hidden by the non-visible portion of our
                // ancestor
                Rect ancestorRect = getVisibleBounds(ancestor);
                ret.intersect(ancestorRect);
                break;
            }
        }

        return ret;
    }

    /** Returns a point in the center of the visible bounds of this object. */
    public Point getVisibleCenter() {
        Rect bounds = getVisibleBounds();
        return new Point(bounds.centerX(), bounds.centerY());
    }

    /**
     * Returns the class name of the underlying {@link android.view.View} represented by this
     * object.
     */
    public String getClassName() {
        CharSequence chars = getAccessibilityNodeInfo().getClassName();
        return chars != null ? chars.toString() : null;
    }

    /** Returns the content description for this object. */
    public String getContentDescription() {
        CharSequence chars = getAccessibilityNodeInfo().getContentDescription();
        return chars != null ? chars.toString() : null;
    }

    /** Returns the package name of the app that this object belongs to. */
    public String getApplicationPackage() {
        CharSequence chars = getAccessibilityNodeInfo().getPackageName();
        return chars != null ? chars.toString() : null;
    }

    /** Returns the fully qualified resource name for this object's id. */
    public String getResourceName() {
        CharSequence chars = getAccessibilityNodeInfo().getViewIdResourceName();
        return chars != null ? chars.toString() : null;
    }

    /** Returns the text value for this object. */
    public String getText() {
        CharSequence chars = getAccessibilityNodeInfo().getText();
        return chars != null ? chars.toString() : null;
    }

    /** Returns whether this object is checkable. */
    public boolean isCheckable() {
        return getAccessibilityNodeInfo().isCheckable();
    }

    /** Returns whether this object is checked. */
    public boolean isChecked() {
        return getAccessibilityNodeInfo().isChecked();
    }

    /** Returns whether this object is clickable. */
    public boolean isClickable() {
        return getAccessibilityNodeInfo().isClickable();
    }

    /** Returns whether this object is enabled. */
    public boolean isEnabled() {
        return getAccessibilityNodeInfo().isEnabled();
    }

    /** Returns whether this object is focusable. */
    public boolean isFocusable() {
        return getAccessibilityNodeInfo().isFocusable();
    }

    /** Returns whether this object is focused. */
    public boolean isFocused() {
        return getAccessibilityNodeInfo().isFocused();
    }

    /** Returns whether this object is long clickable. */
    public boolean isLongClickable() {
        return getAccessibilityNodeInfo().isLongClickable();
    }

    /** Returns whether this object is scrollable. */
    public boolean isScrollable() {
        return getAccessibilityNodeInfo().isScrollable();
    }

    /** Returns whether this object is selected. */
    public boolean isSelected() {
        return getAccessibilityNodeInfo().isSelected();
    }


    // Actions

    /** Clears the text content if this object is an editable field. */
    public void clear() {
        setText("");
    }

    /** Clicks on this object. */
    public void click() {
        mGestureController.performGesture(mGestures.click(getVisibleCenter()));
    }

    /** Performs a click on this object that lasts for {@code duration} milliseconds. */
    public void click(long duration) {
        mGestureController.performGesture(mGestures.click(getVisibleCenter(), duration));
    }

    /** Clicks on this object, and waits for the given condition to become true. */
    public <R> R clickAndWait(EventCondition<R> condition, long timeout) {
        return mGestureController.performGestureAndWait(condition, timeout,
                mGestures.click(getVisibleCenter()));
    }

    /**
     * Drags this object to the specified location.
     *
     * @param dest The end point that this object should be dragged to.
     */
    public void drag(Point dest) {
        drag(dest, (int)(DEFAULT_DRAG_SPEED * mDisplayMetrics.density));
    }

    /**
     * Drags this object to the specified location.
     *
     * @param dest The end point that this object should be dragged to.
     * @param speed The speed at which to perform this gesture in pixels per second.
     */
    public void drag(Point dest, int speed) {
        if (speed < 0) {
            throw new IllegalArgumentException("Speed cannot be negative");
        }
        mGestureController.performGesture(mGestures.drag(getVisibleCenter(), dest, speed));
    }

    /** Performs a long click on this object. */
    public void longClick() {
        mGestureController.performGesture(mGestures.longClick(getVisibleCenter()));
    }

    /**
     * Performs a pinch close gesture on this object.
     *
     * @param percent The size of the pinch as a percentage of this object's size.
     */
    public void pinchClose(float percent) {
        pinchClose(percent, (int)(DEFAULT_PINCH_SPEED * mDisplayMetrics.density));
    }

    /**
     * Performs a pinch close gesture on this object.
     *
     * @param percent The size of the pinch as a percentage of this object's size.
     * @param speed The speed at which to perform this gesture in pixels per second.
     */
    public void pinchClose(float percent, int speed) {
        if (percent < 0.0f || percent > 1.0f) {
            throw new IllegalArgumentException("Percent must be between 0.0f and 1.0f");
        }
        if (speed < 0) {
            throw new IllegalArgumentException("Speed cannot be negative");
        }
        mGestureController.performGesture(
                mGestures.pinchClose(getVisibleBoundsForGestures(), percent, speed));
    }

    /**
     * Performs a pinch open gesture on this object.
     *
     * @param percent The size of the pinch as a percentage of this object's size.
     */
    public void pinchOpen(float percent) {
        pinchOpen(percent, (int)(DEFAULT_PINCH_SPEED * mDisplayMetrics.density));
    }

    /**
     * Performs a pinch open gesture on this object.
     *
     * @param percent The size of the pinch as a percentage of this object's size.
     * @param speed The speed at which to perform this gesture in pixels per second.
     */
    public void pinchOpen(float percent, int speed) {
        if (percent < 0.0f || percent > 1.0f) {
            throw new IllegalArgumentException("Percent must be between 0.0f and 1.0f");
        }
        if (speed < 0) {
            throw new IllegalArgumentException("Speed cannot be negative");
        }
        mGestureController.performGesture(
                mGestures.pinchOpen(getVisibleBoundsForGestures(), percent, speed));
    }

    /**
     * Performs a swipe gesture on this object.
     *
     * @param direction The direction in which to swipe.
     * @param percent The length of the swipe as a percentage of this object's size.
     */
    public void swipe(Direction direction, float percent) {
        swipe(direction, percent, (int)(DEFAULT_SWIPE_SPEED * mDisplayMetrics.density));
    }

    /**
     * Performs a swipe gesture on this object.
     *
     * @param direction The direction in which to swipe.
     * @param percent The length of the swipe as a percentage of this object's size.
     * @param speed The speed at which to perform this gesture in pixels per second.
     */
    public void swipe(Direction direction, float percent, int speed) {
        if (percent < 0.0f || percent > 1.0f) {
            throw new IllegalArgumentException("Percent must be between 0.0f and 1.0f");
        }
        if (speed < 0) {
            throw new IllegalArgumentException("Speed cannot be negative");
        }
        Rect bounds = getVisibleBoundsForGestures();
        mGestureController.performGesture(mGestures.swipeRect(bounds, direction, percent, speed));
    }

    /**
     * Performs a scroll gesture on this object.
     *
     * @param direction The direction in which to scroll.
     * @param percent The distance to scroll as a percentage of this object's visible size.
     * @return Whether the object can still scroll in the given direction.
     */
    public boolean scroll(final Direction direction, final float percent) {
        return scroll(direction, percent, (int)(DEFAULT_SCROLL_SPEED * mDisplayMetrics.density));
    }

    /**
     * Performs a scroll gesture on this object.
     *
     * @param direction The direction in which to scroll.
     * @param percent The distance to scroll as a percentage of this object's visible size.
     * @param speed The speed at which to perform this gesture in pixels per second.
     * @return Whether the object can still scroll in the given direction.
     */
    public boolean scroll(Direction direction, float percent, final int speed) {
        if (percent < 0.0f) {
            throw new IllegalArgumentException("Percent must be greater than 0.0f");
        }
        if (speed < 0) {
            throw new IllegalArgumentException("Speed cannot be negative");
        }

        // To scroll, we swipe in the opposite direction
        final Direction swipeDirection = Direction.reverse(direction);

        // Scroll by performing repeated swipes
        Rect bounds = getVisibleBoundsForGestures();
        for (; percent > 0.0f; percent -= 1.0f) {
            float segment = percent > 1.0f ? 1.0f : percent;
            PointerGesture swipe =
                    mGestures.swipeRect(bounds, swipeDirection, segment, speed).pause(250);

            // Perform the gesture and return early if we reached the end
            if (mGestureController.performGestureAndWait(
                    Until.scrollFinished(direction), SCROLL_TIMEOUT, swipe)) {
                return false;
            }
        }
        // We never reached the end
        return true;
    }

    /**
     * Performs a fling gesture on this object.
     *
     * @param direction The direction in which to fling.
     * @return Whether the object can still scroll in the given direction.
     */
    public boolean fling(final Direction direction) {
        return fling(direction, (int)(DEFAULT_FLING_SPEED * mDisplayMetrics.density));
    }

    /**
     * Performs a fling gesture on this object.
     *
     * @param direction The direction in which to fling.
     * @param speed The speed at which to perform this gesture in pixels per second.
     * @return Whether the object can still scroll in the given direction.
     */
    public boolean fling(final Direction direction, final int speed) {
        ViewConfiguration vc = ViewConfiguration.get(getDevice().getInstrumentation().getContext());
        if (speed < vc.getScaledMinimumFlingVelocity()) {
            throw new IllegalArgumentException("Speed is less than the minimum fling velocity");
        }

        // To fling, we swipe in the opposite direction
        final Direction swipeDirection = Direction.reverse(direction);

        Rect bounds = getVisibleBoundsForGestures();
        PointerGesture swipe = mGestures.swipeRect(bounds, swipeDirection, 1.0f, speed);

        // Perform the gesture and return true if we did not reach the end
        return !mGestureController.performGestureAndWait(
                Until.scrollFinished(direction), FLING_TIMEOUT, swipe);
    }

    /**
     * Set the text content by sending individual key codes.
     * @hide
     */
    public void legacySetText(String text) {
        AccessibilityNodeInfo node = getAccessibilityNodeInfo();

        // Per framework convention, setText(null) means clearing it
        if (text == null) {
            text = "";
        }

        CharSequence currentText = node.getText();
        if (!text.equals(currentText)) {
            InteractionController ic = getDevice().getInteractionController();

            // Long click left + center
            Rect rect = getVisibleBounds();
            ic.longTapNoSync(rect.left + 20, rect.centerY());

            // Select existing text
            getDevice().wait(Until.findObject(By.descContains("Select all")), 50).click();
            // Wait for the selection
            SystemClock.sleep(250);
            // Delete it
            ic.sendKey(KeyEvent.KEYCODE_DEL, 0);

            // Send new text
            ic.sendText(text);
        }
    }

    /** Sets the text content if this object is an editable field. */
    public void setText(String text) {
        AccessibilityNodeInfo node = getAccessibilityNodeInfo();

        // Per framework convention, setText(null) means clearing it
        if (text == null) {
            text = "";
        }

        if (UiDevice.API_LEVEL_ACTUAL > Build.VERSION_CODES.KITKAT) {
            // do this for API Level above 19 (exclusive)
            Bundle args = new Bundle();
            args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
            if (!node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)) {
                // TODO: Decide if we should throw here
                Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_SET_TEXT) failed");
            }
        } else {
            CharSequence currentText = node.getText();
            if (!text.equals(currentText)) {
                // Give focus to the object. Expect this to fail if the object already has focus.
                if (!node.performAction(AccessibilityNodeInfo.ACTION_FOCUS) && !node.isFocused()) {
                    // TODO: Decide if we should throw here
                    Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_FOCUS) failed");
                }
                // Select the existing text. Expect this to fail if there is no existing text.
                Bundle args = new Bundle();
                args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
                args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, text.length());
                if (!node.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, args) &&
                        currentText != null && currentText.length() > 0) {
                    // TODO: Decide if we should throw here
                    Log.w(TAG, "AccessibilityNodeInfo#performAction(ACTION_SET_SELECTION) failed");
                }
                // Send the delete key to clear the existing text, then send the new text
                InteractionController ic = getDevice().getInteractionController();
                ic.sendKey(KeyEvent.KEYCODE_DEL, 0);
                ic.sendText(text);
            }
        }
    }


    /**
     * Returns an up-to-date {@link AccessibilityNodeInfo} corresponding to the {@link android.view.View} that
     * this object represents.
     */
    private AccessibilityNodeInfo getAccessibilityNodeInfo() {
        if (mCachedNode == null) {
            throw new IllegalStateException("This object has already been recycled");
        }

        getDevice().waitForIdle();
        if (!mCachedNode.refresh()) {
            getDevice().runWatchers();

            if (!mCachedNode.refresh()) {
                throw new StaleObjectException();
            }
        }
        return mCachedNode;
    }

    UiDevice getDevice() {
        return mDevice;
    }
}