11273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin/*
21273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * Copyright (C) 2016 The Android Open Source Project
31273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin *
41273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * Licensed under the Apache License, Version 2.0 (the "License");
51273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * you may not use this file except in compliance with the License.
61273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * You may obtain a copy of the License at
71273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin *
81273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin *      http://www.apache.org/licenses/LICENSE-2.0
91273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin *
101273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * Unless required by applicable law or agreed to in writing, software
111273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * distributed under the License is distributed on an "AS IS" BASIS,
121273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * See the License for the specific language governing permissions and
141273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * limitations under the License.
151273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin */
161273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
171273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linpackage com.android.documentsui.bots;
181273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
196526b38f2871c8d59eb80f4855138de1313de485Ben Linimport android.app.UiAutomation;
201273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.content.Context;
211273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.graphics.Point;
221273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.graphics.Rect;
236526b38f2871c8d59eb80f4855138de1313de485Ben Linimport android.os.SystemClock;
241273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.support.test.uiautomator.Configurator;
251273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.support.test.uiautomator.UiDevice;
261273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.support.test.uiautomator.UiObject;
271273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.support.test.uiautomator.UiObjectNotFoundException;
281273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.support.test.uiautomator.UiSelector;
296526b38f2871c8d59eb80f4855138de1313de485Ben Linimport android.view.InputDevice;
301273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linimport android.view.MotionEvent;
316526b38f2871c8d59eb80f4855138de1313de485Ben Linimport android.view.MotionEvent.PointerCoords;
326526b38f2871c8d59eb80f4855138de1313de485Ben Linimport android.view.MotionEvent.PointerProperties;
331273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
341273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin/**
351273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * A test helper class that provides support for controlling directory list
361273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin * and making assertions against the state of it.
371273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin */
381273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Linpublic class GestureBot extends Bots.BaseBot {
391273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    private static final String DIR_CONTAINER_ID = "com.android.documentsui:id/container_directory";
401273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    private static final String DIR_LIST_ID = "com.android.documentsui:id/dir_list";
411273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    private static final int LONGPRESS_STEPS = 60;
421273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    private static final int TRAVELING_STEPS = 20;
431273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    private static final int BAND_SELECTION_DEFAULT_STEPS = 100;
441273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    private static final int STEPS_INBETWEEN_POINTS = 2;
456526b38f2871c8d59eb80f4855138de1313de485Ben Lin    // Inserted after each motion event injection.
466526b38f2871c8d59eb80f4855138de1313de485Ben Lin    private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
476526b38f2871c8d59eb80f4855138de1313de485Ben Lin    private final UiAutomation mAutomation;
486526b38f2871c8d59eb80f4855138de1313de485Ben Lin    private long mDownTime = 0;
491273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
506526b38f2871c8d59eb80f4855138de1313de485Ben Lin    public GestureBot(UiDevice device, UiAutomation automation, Context context, int timeout) {
511273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        super(device, context, timeout);
526526b38f2871c8d59eb80f4855138de1313de485Ben Lin        mAutomation = automation;
531273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    }
541273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
551273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    public void gestureSelectFiles(String startLabel, String endLabel) throws Exception {
561273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        int toolType = Configurator.getInstance().getToolType();
571273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
581273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        Rect startCoord = findDocument(startLabel).getBounds();
591273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        Rect endCoord = findDocument(endLabel).getBounds();
601273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        double diffX = endCoord.centerX() - startCoord.centerX();
611273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        double diffY = endCoord.centerY() - startCoord.centerY();
621273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        Point[] points = new Point[LONGPRESS_STEPS + TRAVELING_STEPS];
631273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
641273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        // First simulate long-press by having a bunch of MOVE events in the same coordinate
651273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        for (int i = 0; i < LONGPRESS_STEPS; i++) {
661273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin            points[i] = new Point(startCoord.centerX(), startCoord.centerY());
671273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        }
681273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
691273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        // Next put the actual drag/move events
701273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        for (int i = 0; i < TRAVELING_STEPS; i++) {
711273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin            int newX = startCoord.centerX() + (int) (diffX / TRAVELING_STEPS * i);
721273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin            int newY = startCoord.centerY() + (int) (diffY / TRAVELING_STEPS * i);
731273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin            points[i + LONGPRESS_STEPS] = new Point(newX, newY);
741273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        }
751273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        mDevice.swipe(points, STEPS_INBETWEEN_POINTS);
761273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        Configurator.getInstance().setToolType(toolType);
771273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    }
781273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
791273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    public void bandSelection(Point start, Point end) throws Exception {
801273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        bandSelection(start, end, BAND_SELECTION_DEFAULT_STEPS);
811273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    }
821273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
831273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    public void bandSelection(Point start, Point end, int steps) throws Exception {
841273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        int toolType = Configurator.getInstance().getToolType();
851273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
866526b38f2871c8d59eb80f4855138de1313de485Ben Lin        swipe(start.x, start.y, end.x, end.y, steps, MotionEvent.BUTTON_PRIMARY);
871273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        Configurator.getInstance().setToolType(toolType);
881273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    }
891273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
901273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    public UiObject findDocument(String label) throws UiObjectNotFoundException {
911273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        final UiSelector docList = new UiSelector().resourceId(
921273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin                DIR_CONTAINER_ID).childSelector(
931273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin                        new UiSelector().resourceId(DIR_LIST_ID));
941273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
951273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        // Wait for the first list item to appear
961273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
971273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin
981273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin        return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
991273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin    }
1006526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1016526b38f2871c8d59eb80f4855138de1313de485Ben Lin    private void swipe(int downX, int downY, int upX, int upY, int steps, int button) {
1026526b38f2871c8d59eb80f4855138de1313de485Ben Lin        int swipeSteps = steps;
1036526b38f2871c8d59eb80f4855138de1313de485Ben Lin        double xStep = 0;
1046526b38f2871c8d59eb80f4855138de1313de485Ben Lin        double yStep = 0;
1056526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1066526b38f2871c8d59eb80f4855138de1313de485Ben Lin        // avoid a divide by zero
1076526b38f2871c8d59eb80f4855138de1313de485Ben Lin        if(swipeSteps == 0)
1086526b38f2871c8d59eb80f4855138de1313de485Ben Lin            swipeSteps = 1;
1096526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1106526b38f2871c8d59eb80f4855138de1313de485Ben Lin        xStep = ((double)(upX - downX)) / swipeSteps;
1116526b38f2871c8d59eb80f4855138de1313de485Ben Lin        yStep = ((double)(upY - downY)) / swipeSteps;
1126526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1136526b38f2871c8d59eb80f4855138de1313de485Ben Lin        // first touch starts exactly at the point requested
1146526b38f2871c8d59eb80f4855138de1313de485Ben Lin        touchDown(downX, downY, button);
1156526b38f2871c8d59eb80f4855138de1313de485Ben Lin        for(int i = 1; i < swipeSteps; i++) {
1166526b38f2871c8d59eb80f4855138de1313de485Ben Lin            touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i), button);
1176526b38f2871c8d59eb80f4855138de1313de485Ben Lin            // set some known constant delay between steps as without it this
1186526b38f2871c8d59eb80f4855138de1313de485Ben Lin            // become completely dependent on the speed of the system and results
1196526b38f2871c8d59eb80f4855138de1313de485Ben Lin            // may vary on different devices. This guarantees at minimum we have
1206526b38f2871c8d59eb80f4855138de1313de485Ben Lin            // a preset delay.
1216526b38f2871c8d59eb80f4855138de1313de485Ben Lin            SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
1226526b38f2871c8d59eb80f4855138de1313de485Ben Lin        }
1236526b38f2871c8d59eb80f4855138de1313de485Ben Lin        touchUp(upX, upY);
1246526b38f2871c8d59eb80f4855138de1313de485Ben Lin    }
1256526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1266526b38f2871c8d59eb80f4855138de1313de485Ben Lin    private boolean touchDown(int x, int y, int button) {
1276526b38f2871c8d59eb80f4855138de1313de485Ben Lin        long mDownTime = SystemClock.uptimeMillis();
1286526b38f2871c8d59eb80f4855138de1313de485Ben Lin        MotionEvent event = getMotionEvent(mDownTime, mDownTime, MotionEvent.ACTION_DOWN, button, x,
1296526b38f2871c8d59eb80f4855138de1313de485Ben Lin                y);
1306526b38f2871c8d59eb80f4855138de1313de485Ben Lin        return mAutomation.injectInputEvent(event, true);
1316526b38f2871c8d59eb80f4855138de1313de485Ben Lin    }
1326526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1336526b38f2871c8d59eb80f4855138de1313de485Ben Lin    private boolean touchUp(int x, int y) {
1346526b38f2871c8d59eb80f4855138de1313de485Ben Lin        final long eventTime = SystemClock.uptimeMillis();
1356526b38f2871c8d59eb80f4855138de1313de485Ben Lin        MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_UP, 0, x, y);
1366526b38f2871c8d59eb80f4855138de1313de485Ben Lin        mDownTime = 0;
1376526b38f2871c8d59eb80f4855138de1313de485Ben Lin        return mAutomation.injectInputEvent(event, true);
1386526b38f2871c8d59eb80f4855138de1313de485Ben Lin    }
1396526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1406526b38f2871c8d59eb80f4855138de1313de485Ben Lin    private boolean touchMove(int x, int y, int button) {
1416526b38f2871c8d59eb80f4855138de1313de485Ben Lin        final long eventTime = SystemClock.uptimeMillis();
1426526b38f2871c8d59eb80f4855138de1313de485Ben Lin        MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_MOVE, button, x,
1436526b38f2871c8d59eb80f4855138de1313de485Ben Lin                y);
1446526b38f2871c8d59eb80f4855138de1313de485Ben Lin        return mAutomation.injectInputEvent(event, true);
1456526b38f2871c8d59eb80f4855138de1313de485Ben Lin    }
1466526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1476526b38f2871c8d59eb80f4855138de1313de485Ben Lin    /** Helper function to obtain a MotionEvent. */
1486526b38f2871c8d59eb80f4855138de1313de485Ben Lin    private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, int button,
1496526b38f2871c8d59eb80f4855138de1313de485Ben Lin            float x, float y) {
1506526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1516526b38f2871c8d59eb80f4855138de1313de485Ben Lin        PointerProperties properties = new PointerProperties();
1526526b38f2871c8d59eb80f4855138de1313de485Ben Lin        properties.id = 0;
1536526b38f2871c8d59eb80f4855138de1313de485Ben Lin        properties.toolType = Configurator.getInstance().getToolType();
1546526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1556526b38f2871c8d59eb80f4855138de1313de485Ben Lin        PointerCoords coords = new PointerCoords();
1566526b38f2871c8d59eb80f4855138de1313de485Ben Lin        coords.pressure = 1;
1576526b38f2871c8d59eb80f4855138de1313de485Ben Lin        coords.size = 1;
1586526b38f2871c8d59eb80f4855138de1313de485Ben Lin        coords.x = x;
1596526b38f2871c8d59eb80f4855138de1313de485Ben Lin        coords.y = y;
1606526b38f2871c8d59eb80f4855138de1313de485Ben Lin
1616526b38f2871c8d59eb80f4855138de1313de485Ben Lin        return MotionEvent.obtain(downTime, eventTime, action, 1,
1626526b38f2871c8d59eb80f4855138de1313de485Ben Lin                new PointerProperties[] { properties }, new PointerCoords[] { coords },
1636526b38f2871c8d59eb80f4855138de1313de485Ben Lin                0, button, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
1646526b38f2871c8d59eb80f4855138de1313de485Ben Lin    }
1651273913f4ac0a272150be6c6bd7b0c6760e531b8Ben Lin}
166