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