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