/* * Copyright (C) 2016 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 com.android.documentsui.bots; import android.app.UiAutomation; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.os.SystemClock; import android.support.test.uiautomator.Configurator; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObjectNotFoundException; import android.support.test.uiautomator.UiSelector; import android.view.InputDevice; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; /** * A test helper class that provides support for controlling directory list * and making assertions against the state of it. */ public class GestureBot extends Bots.BaseBot { private static final int LONGPRESS_STEPS = 60; private static final int TRAVELING_STEPS = 20; private static final int BAND_SELECTION_DEFAULT_STEPS = 100; private static final int STEPS_INBETWEEN_POINTS = 2; // Inserted after each motion event injection. private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5; private static final int LONG_PRESS_EVENT_INJECTION_DELAY_MILIS = 1000; private final String mDirContainerId; private final String mDirListId; private final UiAutomation mAutomation; private long mDownTime = 0; public GestureBot(UiDevice device, UiAutomation automation, Context context, int timeout) { super(device, context, timeout); mDirContainerId = mTargetPackage + ":id/container_directory"; mDirListId = mTargetPackage + ":id/dir_list"; mAutomation = automation; } public void gestureSelectFiles(String startLabel, String endLabel) throws Exception { int toolType = Configurator.getInstance().getToolType(); Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER); Rect startCoord = findDocument(startLabel).getBounds(); Rect endCoord = findDocument(endLabel).getBounds(); double diffX = endCoord.centerX() - startCoord.centerX(); double diffY = endCoord.centerY() - startCoord.centerY(); Point[] points = new Point[LONGPRESS_STEPS + TRAVELING_STEPS]; // First simulate long-press by having a bunch of MOVE events in the same coordinate for (int i = 0; i < LONGPRESS_STEPS; i++) { points[i] = new Point(startCoord.centerX(), startCoord.centerY()); } // Next put the actual drag/move events for (int i = 0; i < TRAVELING_STEPS; i++) { int newX = startCoord.centerX() + (int) (diffX / TRAVELING_STEPS * i); int newY = startCoord.centerY() + (int) (diffY / TRAVELING_STEPS * i); points[i + LONGPRESS_STEPS] = new Point(newX, newY); } mDevice.swipe(points, STEPS_INBETWEEN_POINTS); Configurator.getInstance().setToolType(toolType); } public void bandSelection(Point start, Point end) throws Exception { bandSelection(start, end, BAND_SELECTION_DEFAULT_STEPS); } public void fingerSelection(Point start, Point end) throws Exception { fingerSelection(start, end, BAND_SELECTION_DEFAULT_STEPS); } public void bandSelection(Point start, Point end, int steps) throws Exception { int toolType = Configurator.getInstance().getToolType(); Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE); swipe(start.x, start.y, end.x, end.y, steps, MotionEvent.BUTTON_PRIMARY, false); Configurator.getInstance().setToolType(toolType); } private void fingerSelection(Point start, Point end, int steps) throws Exception { int toolType = Configurator.getInstance().getToolType(); Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER); swipe(start.x, start.y, end.x, end.y, steps, MotionEvent.BUTTON_PRIMARY, true); Configurator.getInstance().setToolType(toolType); } public UiObject findDocument(String label) throws UiObjectNotFoundException { final UiSelector docList = new UiSelector().resourceId( mDirContainerId).childSelector( new UiSelector().resourceId(mDirListId)); // Wait for the first list item to appear new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout); return mDevice.findObject(docList.childSelector(new UiSelector().text(label))); } private void swipe(int downX, int downY, int upX, int upY, int steps, int button, boolean fingerSelection) { 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 touchDown(downX, downY, button); if (fingerSelection) { SystemClock.sleep(LONG_PRESS_EVENT_INJECTION_DELAY_MILIS); } for (int i = 1; i < swipeSteps; i++) { touchMove(downX + (int) (xStep * i), downY + (int) (yStep * i), button); // 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); } touchUp(upX, upY); } private boolean touchDown(int x, int y, int button) { long mDownTime = SystemClock.uptimeMillis(); MotionEvent event = getMotionEvent(mDownTime, mDownTime, MotionEvent.ACTION_DOWN, button, x, y); return mAutomation.injectInputEvent(event, true); } private boolean touchUp(int x, int y) { final long eventTime = SystemClock.uptimeMillis(); MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_UP, 0, x, y); mDownTime = 0; return mAutomation.injectInputEvent(event, true); } private boolean touchMove(int x, int y, int button) { final long eventTime = SystemClock.uptimeMillis(); MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_MOVE, button, x, y); return mAutomation.injectInputEvent(event, true); } /** Helper function to obtain a MotionEvent. */ private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, int button, float x, float y) { PointerProperties properties = new PointerProperties(); properties.id = 0; properties.toolType = Configurator.getInstance().getToolType(); PointerCoords coords = new PointerCoords(); coords.pressure = 1; coords.size = 1; coords.x = x; coords.y = y; return MotionEvent.obtain(downTime, eventTime, action, 1, new PointerProperties[]{properties}, new PointerCoords[]{coords}, 0, button, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); } }