InteractionControllerpublic 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 boolean | clickAndSync(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.
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 boolean | clickAndWaitForNewWindow(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.
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 boolean | clickNoSync(int x, int y)Clicks at coordinates without waiting for device idle. This may be used for operations
that require stressing the target.
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.Runnable | clickRunnable(int x, int y)Returns a Runnable for use in {@link #runAndWaitForEvents(Runnable, Predicate, long) to
perform a click.
return new Runnable() {
@Override
public void run() {
if(touchDown(x, y)) {
SystemClock.sleep(REGULAR_CLICK_LENGTH);
touchUp(x, y);
}
}
};
| public void | freezeRotation()Disables the sensors and freezes the device rotation at its
current rotation state.
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_CURRENT);
| private android.view.accessibility.AccessibilityEvent | getLastMatchingEvent(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 int | getPointerAction(int motionEnvent, int index)
return motionEnvent + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
| private boolean | injectEventSync(android.view.InputEvent event)
return mUiAutomatorBridge.injectInputEvent(event, true);
| public boolean | isScreenOn()Checks the power manager if the screen is ON
return mUiAutomatorBridge.isScreenOn();
| public boolean | longTapNoSync(int x, int y)Touches down for a long press at the specified coordinates.
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 boolean | openNotification()Opens the notification shade
return mUiAutomatorBridge.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
| public boolean | openQuickSettings()Opens the quick settings shade
return mUiAutomatorBridge.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
| public boolean | performMultiPointerGesture(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;
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 void | recycleAccessibilityEvents(java.util.List events)
for (AccessibilityEvent event : events)
event.recycle();
events.clear();
| private android.view.accessibility.AccessibilityEvent | runAndWaitForEvents(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.
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 boolean | scrollSwipe(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.
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 boolean | sendKey(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 boolean | sendKeyAndWaitForEvent(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.
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 boolean | sendText(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 void | setRotationLeft()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.
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_90);
| public void | setRotationNatural()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.
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_0);
| public void | setRotationRight()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.
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_FREEZE_270);
| public boolean | sleepDevice()This method simply presses the power button if the screen is ON else
it does nothing if the screen is already OFF.
if(isScreenOn()) {
this.sendKey(KeyEvent.KEYCODE_POWER, 0);
return true;
}
return false;
| public boolean | swipe(int downX, int downY, int upX, int upY, int steps)Handle swipes in any direction.
return swipe(downX, downY, upX, upY, steps, false /*drag*/);
| public boolean | swipe(int downX, int downY, int upX, int upY, int steps, boolean drag)Handle swipes/drags in any direction.
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 boolean | swipe(android.graphics.Point[] segments, int segmentSteps)Performs a swipe between points in the Point array.
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 boolean | toggleRecentApps()Simulates a short press on the Recent Apps button.
return mUiAutomatorBridge.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_RECENTS);
| private boolean | touchDown(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 boolean | touchMove(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 boolean | touchUp(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 void | unfreezeRotation()Re-enables the sensors and un-freezes the device rotation
allowing its contents to rotate with the device physical rotation.
mUiAutomatorBridge.setRotation(UiAutomation.ROTATION_UNFREEZE);
| public boolean | wakeDevice()This method simply presses the power button if the screen is OFF else
it does nothing if the screen is already ON.
if(!isScreenOn()) {
sendKey(KeyEvent.KEYCODE_POWER, 0);
return true;
}
return false;
|
|