Home | History | Annotate | Download | only in bots
      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 
     50     public GestureBot(UiDevice device, UiAutomation automation, Context context, int timeout) {
     51         super(device, context, timeout);
     52         mAutomation = automation;
     53     }
     54 
     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 
     79     public void bandSelection(Point start, Point end) throws Exception {
     80         bandSelection(start, end, BAND_SELECTION_DEFAULT_STEPS);
     81     }
     82 
     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 
     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 
    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 
    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 
    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 
    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. */
    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