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