1 /* 2 * Copyright (C) 2016 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 17 package com.android.documentsui.bots; 18 19 import android.app.UiAutomation; 20 import android.content.Context; 21 import android.graphics.Point; 22 import android.graphics.Rect; 23 import android.os.SystemClock; 24 import android.support.test.uiautomator.Configurator; 25 import android.support.test.uiautomator.UiDevice; 26 import android.support.test.uiautomator.UiObject; 27 import android.support.test.uiautomator.UiObjectNotFoundException; 28 import android.support.test.uiautomator.UiSelector; 29 import android.view.InputDevice; 30 import android.view.MotionEvent; 31 import android.view.MotionEvent.PointerCoords; 32 import android.view.MotionEvent.PointerProperties; 33 34 /** 35 * A test helper class that provides support for controlling directory list 36 * and making assertions against the state of it. 37 */ 38 public class GestureBot extends Bots.BaseBot { 39 private static final int LONGPRESS_STEPS = 60; 40 private static final int TRAVELING_STEPS = 20; 41 private static final int BAND_SELECTION_DEFAULT_STEPS = 100; 42 private static final int STEPS_INBETWEEN_POINTS = 2; 43 // Inserted after each motion event injection. 44 private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5; 45 private static final int LONG_PRESS_EVENT_INJECTION_DELAY_MILIS = 1000; 46 private final String mDirContainerId; 47 private final String mDirListId; 48 private final UiAutomation mAutomation; 49 private long mDownTime = 0; 50 GestureBot(UiDevice device, UiAutomation automation, Context context, int timeout)51 public GestureBot(UiDevice device, UiAutomation automation, Context context, int timeout) { 52 super(device, context, timeout); 53 mDirContainerId = mTargetPackage + ":id/container_directory"; 54 mDirListId = mTargetPackage + ":id/dir_list"; 55 mAutomation = automation; 56 } 57 gestureSelectFiles(String startLabel, String endLabel)58 public void gestureSelectFiles(String startLabel, String endLabel) throws Exception { 59 int toolType = Configurator.getInstance().getToolType(); 60 Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER); 61 Rect startCoord = findDocument(startLabel).getBounds(); 62 Rect endCoord = findDocument(endLabel).getBounds(); 63 double diffX = endCoord.centerX() - startCoord.centerX(); 64 double diffY = endCoord.centerY() - startCoord.centerY(); 65 Point[] points = new Point[LONGPRESS_STEPS + TRAVELING_STEPS]; 66 67 // First simulate long-press by having a bunch of MOVE events in the same coordinate 68 for (int i = 0; i < LONGPRESS_STEPS; i++) { 69 points[i] = new Point(startCoord.centerX(), startCoord.centerY()); 70 } 71 72 // Next put the actual drag/move events 73 for (int i = 0; i < TRAVELING_STEPS; i++) { 74 int newX = startCoord.centerX() + (int) (diffX / TRAVELING_STEPS * i); 75 int newY = startCoord.centerY() + (int) (diffY / TRAVELING_STEPS * i); 76 points[i + LONGPRESS_STEPS] = new Point(newX, newY); 77 } 78 mDevice.swipe(points, STEPS_INBETWEEN_POINTS); 79 Configurator.getInstance().setToolType(toolType); 80 } 81 bandSelection(Point start, Point end)82 public void bandSelection(Point start, Point end) throws Exception { 83 bandSelection(start, end, BAND_SELECTION_DEFAULT_STEPS); 84 } 85 fingerSelection(Point start, Point end)86 public void fingerSelection(Point start, Point end) throws Exception { 87 fingerSelection(start, end, BAND_SELECTION_DEFAULT_STEPS); 88 } 89 bandSelection(Point start, Point end, int steps)90 public void bandSelection(Point start, Point end, int steps) throws Exception { 91 int toolType = Configurator.getInstance().getToolType(); 92 Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE); 93 swipe(start.x, start.y, end.x, end.y, steps, MotionEvent.BUTTON_PRIMARY, false); 94 Configurator.getInstance().setToolType(toolType); 95 } 96 fingerSelection(Point start, Point end, int steps)97 private void fingerSelection(Point start, Point end, int steps) throws Exception { 98 int toolType = Configurator.getInstance().getToolType(); 99 Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER); 100 swipe(start.x, start.y, end.x, end.y, steps, MotionEvent.BUTTON_PRIMARY, true); 101 Configurator.getInstance().setToolType(toolType); 102 } 103 findDocument(String label)104 public UiObject findDocument(String label) throws UiObjectNotFoundException { 105 final UiSelector docList = new UiSelector().resourceId( 106 mDirContainerId).childSelector( 107 new UiSelector().resourceId(mDirListId)); 108 109 // Wait for the first list item to appear 110 new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout); 111 112 return mDevice.findObject(docList.childSelector(new UiSelector().text(label))); 113 } 114 swipe(int downX, int downY, int upX, int upY, int steps, int button, boolean fingerSelection)115 private void swipe(int downX, int downY, int upX, int upY, int steps, int button, 116 boolean fingerSelection) { 117 int swipeSteps = steps; 118 double xStep = 0; 119 double yStep = 0; 120 121 // avoid a divide by zero 122 if (swipeSteps == 0) { 123 swipeSteps = 1; 124 } 125 126 xStep = ((double) (upX - downX)) / swipeSteps; 127 yStep = ((double) (upY - downY)) / swipeSteps; 128 129 // first touch starts exactly at the point requested 130 touchDown(downX, downY, button); 131 if (fingerSelection) { 132 SystemClock.sleep(LONG_PRESS_EVENT_INJECTION_DELAY_MILIS); 133 } 134 for (int i = 1; i < swipeSteps; i++) { 135 touchMove(downX + (int) (xStep * i), downY + (int) (yStep * i), button); 136 // set some known constant delay between steps as without it this 137 // become completely dependent on the speed of the system and results 138 // may vary on different devices. This guarantees at minimum we have 139 // a preset delay. 140 SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS); 141 } 142 touchUp(upX, upY); 143 } 144 touchDown(int x, int y, int button)145 private boolean touchDown(int x, int y, int button) { 146 long mDownTime = SystemClock.uptimeMillis(); 147 MotionEvent event = getMotionEvent(mDownTime, mDownTime, MotionEvent.ACTION_DOWN, button, x, 148 y); 149 return mAutomation.injectInputEvent(event, true); 150 } 151 touchUp(int x, int y)152 private boolean touchUp(int x, int y) { 153 final long eventTime = SystemClock.uptimeMillis(); 154 MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_UP, 0, x, y); 155 mDownTime = 0; 156 return mAutomation.injectInputEvent(event, true); 157 } 158 touchMove(int x, int y, int button)159 private boolean touchMove(int x, int y, int button) { 160 final long eventTime = SystemClock.uptimeMillis(); 161 MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_MOVE, button, x, 162 y); 163 return mAutomation.injectInputEvent(event, true); 164 } 165 166 /** Helper function to obtain a MotionEvent. */ getMotionEvent(long downTime, long eventTime, int action, int button, float x, float y)167 private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, int button, 168 float x, float y) { 169 170 PointerProperties properties = new PointerProperties(); 171 properties.id = 0; 172 properties.toolType = Configurator.getInstance().getToolType(); 173 174 PointerCoords coords = new PointerCoords(); 175 coords.pressure = 1; 176 coords.size = 1; 177 coords.x = x; 178 coords.y = y; 179 180 return MotionEvent.obtain(downTime, eventTime, action, 1, 181 new PointerProperties[]{properties}, new PointerCoords[]{coords}, 182 0, button, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); 183 } 184 } 185