1d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska/* 2d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * Copyright (C) 2017 The Android Open Source Project 3d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * 4d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * Licensed under the Apache License, Version 2.0 (the "License"); 5d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * you may not use this file except in compliance with the License. 6d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * You may obtain a copy of the License at 7d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * 8d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * http://www.apache.org/licenses/LICENSE-2.0 9d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * 10d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * Unless required by applicable law or agreed to in writing, software 11d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * distributed under the License is distributed on an "AS IS" BASIS, 12d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * See the License for the specific language governing permissions and 14d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * limitations under the License. 15d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska */ 16d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 179b028c55b13889bf75b5dd43b5b0f4051834ae1dAga Madurskapackage android.support.wear.widget.util; 18d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 19d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.graphics.Path; 20d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.graphics.PathMeasure; 21d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.graphics.RectF; 22d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.os.SystemClock; 23d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.support.annotation.VisibleForTesting; 24d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.support.test.espresso.UiController; 25d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.support.test.espresso.action.MotionEvents; 26d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.support.test.espresso.action.Swiper; 27d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.support.v4.util.Preconditions; 28d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.util.Log; 29d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskaimport android.view.MotionEvent; 30d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 31d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska/** 32d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * Swiper for gestures meant to be performed on an arc - part of a circle - not a straight line. 33d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * This class assumes a square bounding box with the radius of the circle being half the height of 34d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska * the box. 35d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska */ 36d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurskapublic class ArcSwipe implements Swiper { 37d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 38d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska /** Enum describing the exact gesture which will perform the curved swipe. */ 39d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska public enum Gesture { 40d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska /** Swipes quickly between the co-ordinates, clockwise. */ 41d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska FAST_CLOCKWISE(SWIPE_FAST_DURATION_MS, true), 42d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska /** Swipes deliberately slowly between the co-ordinates, clockwise. */ 43d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska SLOW_CLOCKWISE(SWIPE_SLOW_DURATION_MS, true), 44d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska /** Swipes quickly between the co-ordinates, anticlockwise. */ 45d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska FAST_ANTICLOCKWISE(SWIPE_FAST_DURATION_MS, false), 46d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska /** Swipes deliberately slowly between the co-ordinates, anticlockwise. */ 47d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska SLOW_ANTICLOCKWISE(SWIPE_SLOW_DURATION_MS, false); 48d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 49d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private final int mDuration; 50d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private final boolean mClockwise; 51d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 52d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska Gesture(int duration, boolean clockwise) { 53d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska mDuration = duration; 54d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska mClockwise = clockwise; 55d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 56d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 57d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 58d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska /** The number of motion events to send for each swipe. */ 59d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private static final int SWIPE_EVENT_COUNT = 10; 60d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 61d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska /** Length of time a "fast" swipe should last for, in milliseconds. */ 62d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private static final int SWIPE_FAST_DURATION_MS = 100; 63d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 64d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska /** Length of time a "slow" swipe should last for, in milliseconds. */ 65d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private static final int SWIPE_SLOW_DURATION_MS = 1500; 66d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 67d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private static final String TAG = ArcSwipe.class.getSimpleName(); 68d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private final RectF mBounds; 69d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private final Gesture mGesture; 70d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 71d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska public ArcSwipe(Gesture gesture, RectF bounds) { 72d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska Preconditions.checkArgument(bounds.height() == bounds.width()); 73d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska mGesture = gesture; 74d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska mBounds = bounds; 75d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 76d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 77d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska @Override 78d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska public Swiper.Status sendSwipe( 79d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska UiController uiController, 80d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[] startCoordinates, 81d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[] endCoordinates, 82d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[] precision) { 83d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska return sendArcSwipe( 84d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska uiController, 85d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska startCoordinates, 86d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska endCoordinates, 87d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska precision, 88d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska mGesture.mDuration, 89d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska mGesture.mClockwise); 90d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 91d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 92d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private float[][] interpolate(float[] start, float[] end, int steps, boolean isClockwise) { 93d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float startAngle = getAngle(start[0], start[1]); 94d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float endAngle = getAngle(end[0], end[1]); 95d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 96d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska Path path = new Path(); 97d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska PathMeasure pathMeasure = new PathMeasure(); 98d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska path.moveTo(start[0], start[1]); 99d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska path.arcTo(mBounds, startAngle, getSweepAngle(startAngle, endAngle, isClockwise)); 100d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska pathMeasure.setPath(path, false); 101d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float pathLength = pathMeasure.getLength(); 102d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 103d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[][] res = new float[steps][2]; 104d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[] mPathTangent = new float[2]; 105d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 106d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska for (int i = 1; i < steps + 1; i++) { 107d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska pathMeasure.getPosTan((pathLength * i) / (steps + 2f), res[i - 1], mPathTangent); 108d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 109d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 110d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska return res; 111d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 112d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 113d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska private Swiper.Status sendArcSwipe( 114d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska UiController uiController, 115d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[] startCoordinates, 116d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[] endCoordinates, 117d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[] precision, 118d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska int duration, 119d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska boolean isClockwise) { 120d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 121d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float[][] steps = interpolate(startCoordinates, endCoordinates, SWIPE_EVENT_COUNT, 122d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska isClockwise); 123d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska final int delayBetweenMovements = duration / steps.length; 124d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 125d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska MotionEvent downEvent = MotionEvents.sendDown(uiController, startCoordinates, 126d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska precision).down; 127d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska try { 128d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska for (int i = 0; i < steps.length; i++) { 129d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska if (!MotionEvents.sendMovement(uiController, downEvent, steps[i])) { 130d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska Log.e(TAG, 131d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska "Injection of move event as part of the swipe failed. Sending cancel " 132d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska + "event."); 133d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska MotionEvents.sendCancel(uiController, downEvent); 134d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska return Swiper.Status.FAILURE; 135d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 136d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 137d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska long desiredTime = downEvent.getDownTime() + delayBetweenMovements * i; 138d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska long timeUntilDesired = desiredTime - SystemClock.uptimeMillis(); 139d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska if (timeUntilDesired > 10) { 140d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska uiController.loopMainThreadForAtLeast(timeUntilDesired); 141d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 142d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 143d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 144d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska if (!MotionEvents.sendUp(uiController, downEvent, endCoordinates)) { 145d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska Log.e(TAG, 146d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska "Injection of up event as part of the swipe failed. Sending cancel event."); 147d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska MotionEvents.sendCancel(uiController, downEvent); 148d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska return Swiper.Status.FAILURE; 149d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 150d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } finally { 151d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska downEvent.recycle(); 152d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 153d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska return Swiper.Status.SUCCESS; 154d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 155d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 156d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska @VisibleForTesting 157d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float getAngle(double x, double y) { 158d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska double relativeX = x - (mBounds.width() / 2); 159d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska double relativeY = y - (mBounds.height() / 2); 160d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska double rowAngle = Math.atan2(relativeX, relativeY); 161d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska double angle = -Math.toDegrees(rowAngle) - 180; 162d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska if (angle < 0) { 163d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska angle += 360; 164d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 165d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska return (float) angle; 166d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 167d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska 168d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska @VisibleForTesting 169d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float getSweepAngle(float startAngle, float endAngle, boolean isClockwise) { 170d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska float sweepAngle = endAngle - startAngle; 171d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska if (sweepAngle < 0) { 172d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska sweepAngle += 360; 173d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 174d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska return isClockwise ? sweepAngle : (360 - sweepAngle); 175d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska } 176d5fc393a5aa24693d35b5ebd6d588613103aafdcAga Madurska} 177