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