FileDocCategorySizeDatePackage
InteractionController.javaAPI DocAndroid 5.1 API29797Thu Mar 12 22:22:08 GMT 2015com.android.uiautomator.core

InteractionController

public class InteractionController extends Object
The InteractionProvider is responsible for injecting user events such as touch events (includes swipes) and text key events into the system. To do so, all it needs to know about are coordinates of the touch events and text for the text input events. The InteractionController performs no synchronization. It will fire touch and text input events as fast as it receives them. All idle synchronization is performed prior to querying the hierarchy. See {@link QueryController}

Fields Summary
private static final String
LOG_TAG
private static final boolean
DEBUG
private final android.view.KeyCharacterMap
mKeyCharacterMap
private final UiAutomatorBridge
mUiAutomatorBridge
private static final long
REGULAR_CLICK_LENGTH
private long
mDownTime
private static final int
MOTION_EVENT_INJECTION_DELAY_MILLIS
Constructors Summary
public InteractionController(UiAutomatorBridge bridge)


       
        mUiAutomatorBridge = bridge;
    
Methods Summary
public booleanclickAndSync(int x, int y, long timeout)
Click at coordinates and blocks until either accessibility event TYPE_WINDOW_CONTENT_CHANGED or TYPE_VIEW_SELECTED are received.

param
x
param
y
param
timeout waiting for event
return
true if events are received, else false if timeout.


        String logString = String.format("clickAndSync(%d, %d)", x, y);
        Log.d(LOG_TAG, logString);

        return runAndWaitForEvents(clickRunnable(x, y), new WaitForAnyEventPredicate(
                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED |
                AccessibilityEvent.TYPE_VIEW_SELECTED), timeout) != null;
    
public booleanclickAndWaitForNewWindow(int x, int y, long timeout)
Clicks at coordinates and waits for for a TYPE_WINDOW_STATE_CHANGED event followed by TYPE_WINDOW_CONTENT_CHANGED. If timeout occurs waiting for TYPE_WINDOW_STATE_CHANGED, no further waits will be performed and the function returns.

param
x
param
y
param
timeout waiting for event
return
true if both events occurred in the expected order

        String logString = String.format("clickAndWaitForNewWindow(%d, %d)", x, y);
        Log.d(LOG_TAG, logString);

        return runAndWaitForEvents(clickRunnable(x, y), new WaitForAllEventPredicate(
                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED |
                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED), timeout) != null;
    
public booleanclickNoSync(int x, int y)
Clicks at coordinates without waiting for device idle. This may be used for operations that require stressing the target.

param
x
param
y
return
true if the click executed successfully

        Log.d(LOG_TAG, "clickNoSync (" + x + ", " + y + ")");

        if (touchDown(x, y)) {
            SystemClock.sleep(REGULAR_CLICK_LENGTH);
            if (touchUp(x, y))
                return true;
        }
        return false;
    
private java.lang.RunnableclickRunnable(int x, int y)
Returns a Runnable for use in {@link #runAndWaitForEvents(Runnable, Predicate, long) to perform a click.

param
x coordinate
param
y coordinate
return
Runnable

        return new Runnable() {
            @Override
            public void run() {
                if(touchDown(x, y)) {
                    SystemClock.sleep(REGULAR_CLICK_LENGTH);
                    touchUp(x, y);
                }
            }
        };
    
public voidfreezeRotation()
Disables the sensors and freezes the device rotation at its current rotation state.

throws
RemoteException

        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
    
private android.view.accessibility.AccessibilityEventgetLastMatchingEvent(java.util.List events, int type)

        for (int x = events.size(); x > 0; x--) {
            AccessibilityEvent event = events.get(x - 1);
            if (event.getEventType() == type)
                return event;
        }
        return null;
    
private intgetPointerAction(int motionEnvent, int index)

        return motionEnvent + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
    
private booleaninjectEventSync(android.view.InputEvent event)

        return mUiAutomatorBridge.injectInputEvent(event, true);
    
public booleanisScreenOn()
Checks the power manager if the screen is ON

return
true if the screen is ON else false
throws
RemoteException

        return mUiAutomatorBridge.isScreenOn();
    
public booleanlongTapNoSync(int x, int y)
Touches down for a long press at the specified coordinates.

param
x
param
y
return
true if successful.

        if (DEBUG) {
            Log.d(LOG_TAG, "longTapNoSync (" + x + ", " + y + ")");
        }

        if (touchDown(x, y)) {
            SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
            if(touchUp(x, y)) {
                return true;
            }
        }
        return false;
    
public booleanopenNotification()
Opens the notification shade

return
true if successful, else return false
since
API Level 18

        return mUiAutomatorBridge.performGlobalAction(
                AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
    
public booleanopenQuickSettings()
Opens the quick settings shade

return
true if successful, else return false
since
API Level 18

        return mUiAutomatorBridge.performGlobalAction(
                AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
    
public booleanperformMultiPointerGesture(android.view.MotionEvent.PointerCoords[] touches)
Performs a multi-touch gesture Takes a series of touch coordinates for at least 2 pointers. Each pointer must have all of its touch steps defined in an array of {@link PointerCoords}. By having the ability to specify the touch points along the path of a pointer, the caller is able to specify complex gestures like circles, irregular shapes etc, where each pointer may take a different path. To create a single point on a pointer's touch path PointerCoords p = new PointerCoords(); p.x = stepX; p.y = stepY; p.pressure = 1; p.size = 1;

param
touches each array of {@link PointerCoords} constitute a single pointer's touch path. Multiple {@link PointerCoords} arrays constitute multiple pointers, each with its own path. Each {@link PointerCoords} in an array constitute a point on a pointer's path.
return
true if all points on all paths are injected successfully, false otherwise
since
API Level 18

        boolean ret = true;
        if (touches.length < 2) {
            throw new IllegalArgumentException("Must provide coordinates for at least 2 pointers");
        }

        // Get the pointer with the max steps to inject.
        int maxSteps = 0;
        for (int x = 0; x < touches.length; x++)
            maxSteps = (maxSteps < touches[x].length) ? touches[x].length : maxSteps;

        // specify the properties for each pointer as finger touch
        PointerProperties[] properties = new PointerProperties[touches.length];
        PointerCoords[] pointerCoords = new PointerCoords[touches.length];
        for (int x = 0; x < touches.length; x++) {
            PointerProperties prop = new PointerProperties();
            prop.id = x;
            prop.toolType = MotionEvent.TOOL_TYPE_FINGER;
            properties[x] = prop;

            // for each pointer set the first coordinates for touch down
            pointerCoords[x] = touches[x][0];
        }

        // Touch down all pointers
        long downTime = SystemClock.uptimeMillis();
        MotionEvent event;
        event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1,
                properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
        ret &= injectEventSync(event);

        for (int x = 1; x < touches.length; x++) {
            event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
                    getPointerAction(MotionEvent.ACTION_POINTER_DOWN, x), x + 1, properties,
                    pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
            ret &= injectEventSync(event);
        }

        // Move all pointers
        for (int i = 1; i < maxSteps - 1; i++) {
            // for each pointer
            for (int x = 0; x < touches.length; x++) {
                // check if it has coordinates to move
                if (touches[x].length > i)
                    pointerCoords[x] = touches[x][i];
                else
                    pointerCoords[x] = touches[x][touches[x].length - 1];
            }

            event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
                    MotionEvent.ACTION_MOVE, touches.length, properties, pointerCoords, 0, 0, 1, 1,
                    0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);

            ret &= injectEventSync(event);
            SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
        }

        // For each pointer get the last coordinates
        for (int x = 0; x < touches.length; x++)
            pointerCoords[x] = touches[x][touches[x].length - 1];

        // touch up
        for (int x = 1; x < touches.length; x++) {
            event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
                    getPointerAction(MotionEvent.ACTION_POINTER_UP, x), x + 1, properties,
                    pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
            ret &= injectEventSync(event);
        }

        Log.i(LOG_TAG, "x " + pointerCoords[0].x);
        // first to touch down is last up
        event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 1,
                properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
        ret &= injectEventSync(event);
        return ret;
    
private voidrecycleAccessibilityEvents(java.util.List events)

        for (AccessibilityEvent event : events)
            event.recycle();
        events.clear();
    
private android.view.accessibility.AccessibilityEventrunAndWaitForEvents(java.lang.Runnable command, android.app.UiAutomation.AccessibilityEventFilter filter, long timeout)
Helper used by methods to perform actions and wait for any accessibility events and return predicated on predefined filter.

param
command
param
filter
param
timeout
return


        try {
            return mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(command, filter,
                    timeout);
        } catch (TimeoutException e) {
            Log.w(LOG_TAG, "runAndwaitForEvent timedout waiting for events");
            return null;
        } catch (Exception e) {
            Log.e(LOG_TAG, "exception from executeCommandAndWaitForAccessibilityEvent", e);
            return null;
        }
    
public booleanscrollSwipe(int downX, int downY, int upX, int upY, int steps)
Handle swipes in any direction where the result is a scroll event. This call blocks until the UI has fired a scroll event or timeout.

param
downX
param
downY
param
upX
param
upY
param
steps
return
true if we are not at the beginning or end of the scrollable view.

        Log.d(LOG_TAG, "scrollSwipe (" +  downX + ", " + downY + ", " + upX + ", "
                + upY + ", " + steps +")");

        Runnable command = new Runnable() {
            @Override
            public void run() {
                swipe(downX, downY, upX, upY, steps);
            }
        };

        // Collect all accessibility events generated during the swipe command and get the
        // last event
        ArrayList<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
        runAndWaitForEvents(command,
                new EventCollectingPredicate(AccessibilityEvent.TYPE_VIEW_SCROLLED, events),
                Configurator.getInstance().getScrollAcknowledgmentTimeout());

        AccessibilityEvent event = getLastMatchingEvent(events,
                AccessibilityEvent.TYPE_VIEW_SCROLLED);

        if (event == null) {
            // end of scroll since no new scroll events received
            recycleAccessibilityEvents(events);
            return false;
        }

        // AdapterViews have indices we can use to check for the beginning.
        boolean foundEnd = false;
        if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
            foundEnd = event.getFromIndex() == 0 ||
                    (event.getItemCount() - 1) == event.getToIndex();
            Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
        } else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
            // Determine if we are scrolling vertically or horizontally.
            if (downX == upX) {
                // Vertical
                foundEnd = event.getScrollY() == 0 ||
                        event.getScrollY() == event.getMaxScrollY();
                Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: " + foundEnd);
            } else if (downY == upY) {
                // Horizontal
                foundEnd = event.getScrollX() == 0 ||
                        event.getScrollX() == event.getMaxScrollX();
                Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: " + foundEnd);
            }
        }
        recycleAccessibilityEvents(events);
        return !foundEnd;
    
public booleansendKey(int keyCode, int metaState)

        if (DEBUG) {
            Log.d(LOG_TAG, "sendKey (" + keyCode + ", " + metaState + ")");
        }

        final long eventTime = SystemClock.uptimeMillis();
        KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
                keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
                InputDevice.SOURCE_KEYBOARD);
        if (injectEventSync(downEvent)) {
            KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
                    keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
                    InputDevice.SOURCE_KEYBOARD);
            if(injectEventSync(upEvent)) {
                return true;
            }
        }
        return false;
    
public booleansendKeyAndWaitForEvent(int keyCode, int metaState, int eventType, long timeout)
Send keys and blocks until the first specified accessibility event. Most key presses will cause some UI change to occur. If the device is busy, this will block until the device begins to process the key press at which point the call returns and normal wait for idle processing may begin. If no events are detected for the timeout period specified, the call will return anyway with false.

param
keyCode
param
metaState
param
eventType
param
timeout
return
true if events is received, otherwise false.

        Runnable command = new Runnable() {
            @Override
            public void run() {
                final long eventTime = SystemClock.uptimeMillis();
                KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
                        keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
                        InputDevice.SOURCE_KEYBOARD);
                if (injectEventSync(downEvent)) {
                    KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
                            keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
                            InputDevice.SOURCE_KEYBOARD);
                    injectEventSync(upEvent);
                }
            }
        };

        return runAndWaitForEvents(command, new WaitForAnyEventPredicate(eventType), timeout)
                != null;
    
public booleansendText(java.lang.String text)

        if (DEBUG) {
            Log.d(LOG_TAG, "sendText (" + text + ")");
        }

        KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray());

        if (events != null) {
            long keyDelay = Configurator.getInstance().getKeyInjectionDelay();
            for (KeyEvent event2 : events) {
                // We have to change the time of an event before injecting it because
                // all KeyEvents returned by KeyCharacterMap.getEvents() have the same
                // time stamp and the system rejects too old events. Hence, it is
                // possible for an event to become stale before it is injected if it
                // takes too long to inject the preceding ones.
                KeyEvent event = KeyEvent.changeTimeRepeat(event2,
                        SystemClock.uptimeMillis(), 0);
                if (!injectEventSync(event)) {
                    return false;
                }
                SystemClock.sleep(keyDelay);
            }
        }
        return true;
    
public voidsetRotationLeft()
Rotates left and also freezes rotation in that position by disabling the sensors. If you want to un-freeze the rotation and re-enable the sensors see {@link #unfreezeRotation()}. Note that doing so may cause the screen contents to rotate depending on the current physical position of the test device.

throws
RemoteException

        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_90);
    
public voidsetRotationNatural()
Rotates up and also freezes rotation in that position by disabling the sensors. If you want to un-freeze the rotation and re-enable the sensors see {@link #unfreezeRotation()}. Note that doing so may cause the screen contents to rotate depending on the current physical position of the test device.

throws
RemoteException

        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_0);
    
public voidsetRotationRight()
Rotates right and also freezes rotation in that position by disabling the sensors. If you want to un-freeze the rotation and re-enable the sensors see {@link #unfreezeRotation()}. Note that doing so may cause the screen contents to rotate depending on the current physical position of the test device.

throws
RemoteException

        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_270);
    
public booleansleepDevice()
This method simply presses the power button if the screen is ON else it does nothing if the screen is already OFF.

return
true if the device was awake else false
throws
RemoteException

        if(isScreenOn()) {
            this.sendKey(KeyEvent.KEYCODE_POWER, 0);
            return true;
        }
        return false;
    
public booleanswipe(int downX, int downY, int upX, int upY, int steps)
Handle swipes in any direction.

param
downX
param
downY
param
upX
param
upY
param
steps
return
true if the swipe executed successfully

        return swipe(downX, downY, upX, upY, steps, false /*drag*/);
    
public booleanswipe(int downX, int downY, int upX, int upY, int steps, boolean drag)
Handle swipes/drags in any direction.

param
downX
param
downY
param
upX
param
upY
param
steps
param
drag when true, the swipe becomes a drag swipe
return
true if the swipe executed successfully

        boolean ret = false;
        int swipeSteps = steps;
        double xStep = 0;
        double yStep = 0;

        // avoid a divide by zero
        if(swipeSteps == 0)
            swipeSteps = 1;

        xStep = ((double)(upX - downX)) / swipeSteps;
        yStep = ((double)(upY - downY)) / swipeSteps;

        // first touch starts exactly at the point requested
        ret = touchDown(downX, downY);
        if (drag)
            SystemClock.sleep(mUiAutomatorBridge.getSystemLongPressTime());
        for(int i = 1; i < swipeSteps; i++) {
            ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
            if(ret == false)
                break;
            // set some known constant delay between steps as without it this
            // become completely dependent on the speed of the system and results
            // may vary on different devices. This guarantees at minimum we have
            // a preset delay.
            SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
        }
        if (drag)
            SystemClock.sleep(REGULAR_CLICK_LENGTH);
        ret &= touchUp(upX, upY);
        return(ret);
    
public booleanswipe(android.graphics.Point[] segments, int segmentSteps)
Performs a swipe between points in the Point array.

param
segments is Point array containing at least one Point object
param
segmentSteps steps to inject between two Points
return
true on success

        boolean ret = false;
        int swipeSteps = segmentSteps;
        double xStep = 0;
        double yStep = 0;

        // avoid a divide by zero
        if(segmentSteps == 0)
            segmentSteps = 1;

        // must have some points
        if(segments.length == 0)
            return false;

        // first touch starts exactly at the point requested
        ret = touchDown(segments[0].x, segments[0].y);
        for(int seg = 0; seg < segments.length; seg++) {
            if(seg + 1 < segments.length) {

                xStep = ((double)(segments[seg+1].x - segments[seg].x)) / segmentSteps;
                yStep = ((double)(segments[seg+1].y - segments[seg].y)) / segmentSteps;

                for(int i = 1; i < swipeSteps; i++) {
                    ret &= touchMove(segments[seg].x + (int)(xStep * i),
                            segments[seg].y + (int)(yStep * i));
                    if(ret == false)
                        break;
                    // set some known constant delay between steps as without it this
                    // become completely dependent on the speed of the system and results
                    // may vary on different devices. This guarantees at minimum we have
                    // a preset delay.
                    SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
                }
            }
        }
        ret &= touchUp(segments[segments.length - 1].x, segments[segments.length -1].y);
        return(ret);
    
public booleantoggleRecentApps()
Simulates a short press on the Recent Apps button.

return
true if successful, else return false
since
API Level 18

        return mUiAutomatorBridge.performGlobalAction(
                AccessibilityService.GLOBAL_ACTION_RECENTS);
    
private booleantouchDown(int x, int y)

        if (DEBUG) {
            Log.d(LOG_TAG, "touchDown (" + x + ", " + y + ")");
        }
        mDownTime = SystemClock.uptimeMillis();
        MotionEvent event = MotionEvent.obtain(
                mDownTime, mDownTime, MotionEvent.ACTION_DOWN, x, y, 1);
        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
        return injectEventSync(event);
    
private booleantouchMove(int x, int y)

        if (DEBUG) {
            Log.d(LOG_TAG, "touchMove (" + x + ", " + y + ")");
        }
        final long eventTime = SystemClock.uptimeMillis();
        MotionEvent event = MotionEvent.obtain(
                mDownTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 1);
        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
        return injectEventSync(event);
    
private booleantouchUp(int x, int y)

        if (DEBUG) {
            Log.d(LOG_TAG, "touchUp (" + x + ", " + y + ")");
        }
        final long eventTime = SystemClock.uptimeMillis();
        MotionEvent event = MotionEvent.obtain(
                mDownTime, eventTime, MotionEvent.ACTION_UP, x, y, 1);
        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
        mDownTime = 0;
        return injectEventSync(event);
    
public voidunfreezeRotation()
Re-enables the sensors and un-freezes the device rotation allowing its contents to rotate with the device physical rotation.

throws
RemoteException

        mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_UNFREEZE);
    
public booleanwakeDevice()
This method simply presses the power button if the screen is OFF else it does nothing if the screen is already ON.

return
true if the device was asleep else false
throws
RemoteException

        if(!isScreenOn()) {
            sendKey(KeyEvent.KEYCODE_POWER, 0);
            return true;
        }
        return false;