1/*
2 ** Copyright 2015, 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
17package com.android.server.accessibility;
18
19import android.content.Context;
20import android.gesture.Gesture;
21import android.gesture.GestureLibraries;
22import android.gesture.GestureLibrary;
23import android.gesture.GesturePoint;
24import android.gesture.GestureStore;
25import android.gesture.GestureStroke;
26import android.gesture.Prediction;
27import android.util.Slog;
28import android.util.TypedValue;
29import android.view.GestureDetector;
30import android.view.MotionEvent;
31import android.view.VelocityTracker;
32import android.view.ViewConfiguration;
33
34import com.android.internal.R;
35
36import java.util.ArrayList;
37
38/**
39 * This class handles gesture detection for the Touch Explorer.  It collects
40 * touch events and determines when they match a gesture, as well as when they
41 * won't match a gesture.  These state changes are then surfaced to mListener.
42 */
43class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListener {
44
45    private static final boolean DEBUG = false;
46
47    // Tag for logging received events.
48    private static final String LOG_TAG = "AccessibilityGestureDetector";
49
50    /**
51     * Listener functions are called as a result of onMoveEvent().  The current
52     * MotionEvent in the context of these functions is the event passed into
53     * onMotionEvent.
54     */
55    public interface Listener {
56        /**
57         * Called when the user has performed a double tap and then held down
58         * the second tap.
59         *
60         * @param event The most recent MotionEvent received.
61         * @param policyFlags The policy flags of the most recent event.
62         */
63        void onDoubleTapAndHold(MotionEvent event, int policyFlags);
64
65        /**
66         * Called when the user lifts their finger on the second tap of a double
67         * tap.
68         *
69         * @param event The most recent MotionEvent received.
70         * @param policyFlags The policy flags of the most recent event.
71         *
72         * @return true if the event is consumed, else false
73         */
74        boolean onDoubleTap(MotionEvent event, int policyFlags);
75
76        /**
77         * Called when the system has decided the event stream is a gesture.
78         *
79         * @return true if the event is consumed, else false
80         */
81        boolean onGestureStarted();
82
83        /**
84         * Called when an event stream is recognized as a gesture.
85         *
86         * @param gestureId ID of the gesture that was recognized.
87         *
88         * @return true if the event is consumed, else false
89         */
90        boolean onGestureCompleted(int gestureId);
91
92        /**
93         * Called when the system has decided an event stream doesn't match any
94         * known gesture.
95         *
96         * @param event The most recent MotionEvent received.
97         * @param policyFlags The policy flags of the most recent event.
98         *
99         * @return true if the event is consumed, else false
100         */
101        public boolean onGestureCancelled(MotionEvent event, int policyFlags);
102    }
103
104    private final Listener mListener;
105    private final GestureDetector mGestureDetector;
106
107    // The library for gesture detection.
108    private final GestureLibrary mGestureLibrary;
109
110    // Indicates that a single tap has occurred.
111    private boolean mFirstTapDetected;
112
113    // Indicates that the down event of a double tap has occured.
114    private boolean mDoubleTapDetected;
115
116    // Indicates that motion events are being collected to match a gesture.
117    private boolean mRecognizingGesture;
118
119    // Indicates that we've collected enough data to be sure it could be a
120    // gesture.
121    private boolean mGestureStarted;
122
123    // Indicates that motion events from the second pointer are being checked
124    // for a double tap.
125    private boolean mSecondFingerDoubleTap;
126
127    // Tracks the most recent time where ACTION_POINTER_DOWN was sent for the
128    // second pointer.
129    private long mSecondPointerDownTime;
130
131    // Policy flags of the previous event.
132    private int mPolicyFlags;
133
134    // These values track the previous point that was saved to use for gesture
135    // detection.  They are only updated when the user moves more than the
136    // recognition threshold.
137    private float mPreviousGestureX;
138    private float mPreviousGestureY;
139
140    // These values track the previous point that was used to determine if there
141    // was a transition into or out of gesture detection.  They are updated when
142    // the user moves more than the detection threshold.
143    private float mBaseX;
144    private float mBaseY;
145    private long mBaseTime;
146
147    // This is the calculated movement threshold used track if the user is still
148    // moving their finger.
149    private final float mGestureDetectionThreshold;
150
151    // Buffer for storing points for gesture detection.
152    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
153
154    // The minimal delta between moves to add a gesture point.
155    private static final int TOUCH_TOLERANCE = 3;
156
157    // The minimal score for accepting a predicted gesture.
158    private static final float MIN_PREDICTION_SCORE = 2.0f;
159
160    // Distance a finger must travel before we decide if it is a gesture or not.
161    private static final int GESTURE_CONFIRM_MM = 10;
162
163    // Time threshold used to determine if an interaction is a gesture or not.
164    // If the first movement of 1cm takes longer than this value, we assume it's
165    // a slow movement, and therefore not a gesture.
166    //
167    // This value was determined by measuring the time for the first 1cm
168    // movement when gesturing, and touch exploring.  Based on user testing,
169    // all gestures started with the initial movement taking less than 100ms.
170    // When touch exploring, the first movement almost always takes longer than
171    // 200ms.  From this data, 200ms seems the best value to decide what
172    // kind of interaction it is.
173    private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 200;
174
175    // Time threshold used to determine if a gesture should be cancelled.  If
176    // the finger pauses for longer than this delay, the ongoing gesture is
177    // cancelled.
178    private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 500;
179
180    AccessibilityGestureDetector(Context context, Listener listener) {
181        mListener = listener;
182
183        mGestureDetector = new GestureDetector(context, this);
184        mGestureDetector.setOnDoubleTapListener(this);
185
186        mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
187        mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */);
188        mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
189        mGestureLibrary.load();
190
191        mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
192                context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
193    }
194
195    /**
196     * Handle a motion event.  If an action is completed, the appropriate
197     * callback on mListener is called, and the return value of the callback is
198     * passed to the caller.
199     *
200     * @param event The raw motion event.  It's important that this be the raw
201     * event, before any transformations have been applied, so that measurements
202     * can be made in physical units.
203     * @param policyFlags Policy flags for the event.
204     *
205     * @return true if the event is consumed, else false
206     */
207    public boolean onMotionEvent(MotionEvent event, int policyFlags) {
208        final float x = event.getX();
209        final float y = event.getY();
210        final long time = event.getEventTime();
211
212        mPolicyFlags = policyFlags;
213        switch (event.getActionMasked()) {
214            case MotionEvent.ACTION_DOWN:
215                mDoubleTapDetected = false;
216                mSecondFingerDoubleTap = false;
217                mRecognizingGesture = true;
218                mGestureStarted = false;
219                mPreviousGestureX = x;
220                mPreviousGestureY = y;
221                mStrokeBuffer.clear();
222                mStrokeBuffer.add(new GesturePoint(x, y, time));
223
224                mBaseX = x;
225                mBaseY = y;
226                mBaseTime = time;
227                break;
228
229            case MotionEvent.ACTION_MOVE:
230                if (mRecognizingGesture) {
231                    final float deltaX = mBaseX - x;
232                    final float deltaY = mBaseY - y;
233                    final double moveDelta = Math.hypot(deltaX, deltaY);
234                    if (moveDelta > mGestureDetectionThreshold) {
235                        // If the pointer has moved more than the threshold,
236                        // update the stored values.
237                        mBaseX = x;
238                        mBaseY = y;
239                        mBaseTime = time;
240
241                        // Since the pointer has moved, this is not a double
242                        // tap.
243                        mFirstTapDetected = false;
244                        mDoubleTapDetected = false;
245
246                        // If this hasn't been confirmed as a gesture yet, send
247                        // the event.
248                        if (!mGestureStarted) {
249                            mGestureStarted = true;
250                            return mListener.onGestureStarted();
251                        }
252                    } else if (!mFirstTapDetected) {
253                        // The finger may not move if they are double tapping.
254                        // In that case, we shouldn't cancel the gesture.
255                        final long timeDelta = time - mBaseTime;
256                        final long threshold = mGestureStarted ?
257                            CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
258                            CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
259
260                        // If the pointer hasn't moved for longer than the
261                        // timeout, cancel gesture detection.
262                        if (timeDelta > threshold) {
263                            cancelGesture();
264                            return mListener.onGestureCancelled(event, policyFlags);
265                        }
266                    }
267
268                    final float dX = Math.abs(x - mPreviousGestureX);
269                    final float dY = Math.abs(y - mPreviousGestureY);
270                    if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
271                        mPreviousGestureX = x;
272                        mPreviousGestureY = y;
273                        mStrokeBuffer.add(new GesturePoint(x, y, time));
274                    }
275                }
276                break;
277
278            case MotionEvent.ACTION_UP:
279                if (mDoubleTapDetected) {
280                    return finishDoubleTap(event, policyFlags);
281                }
282                if (mGestureStarted) {
283                    mStrokeBuffer.add(new GesturePoint(x, y, time));
284
285                    return recognizeGesture(event, policyFlags);
286                }
287                break;
288
289            case MotionEvent.ACTION_POINTER_DOWN:
290                // Once a second finger is used, we're definitely not
291                // recognizing a gesture.
292                cancelGesture();
293
294                if (event.getPointerCount() == 2) {
295                    // If this was the second finger, attempt to recognize double
296                    // taps on it.
297                    mSecondFingerDoubleTap = true;
298                    mSecondPointerDownTime = time;
299                } else {
300                    // If there are more than two fingers down, stop watching
301                    // for a double tap.
302                    mSecondFingerDoubleTap = false;
303                }
304                break;
305
306            case MotionEvent.ACTION_POINTER_UP:
307                // If we're detecting taps on the second finger, see if we
308                // should finish the double tap.
309                if (mSecondFingerDoubleTap && mDoubleTapDetected) {
310                    return finishDoubleTap(event, policyFlags);
311                }
312                break;
313
314            case MotionEvent.ACTION_CANCEL:
315                clear();
316                break;
317        }
318
319        // If we're detecting taps on the second finger, map events from the
320        // finger to the first finger.
321        if (mSecondFingerDoubleTap) {
322            MotionEvent newEvent = mapSecondPointerToFirstPointer(event);
323            if (newEvent == null) {
324                return false;
325            }
326            boolean handled = mGestureDetector.onTouchEvent(newEvent);
327            newEvent.recycle();
328            return handled;
329        }
330
331        if (!mRecognizingGesture) {
332            return false;
333        }
334
335        // Pass the event on to the standard gesture detector.
336        return mGestureDetector.onTouchEvent(event);
337    }
338
339    public void clear() {
340        mFirstTapDetected = false;
341        mDoubleTapDetected = false;
342        mSecondFingerDoubleTap = false;
343        mGestureStarted = false;
344        cancelGesture();
345    }
346
347    public boolean firstTapDetected() {
348        return mFirstTapDetected;
349    }
350
351    @Override
352    public void onLongPress(MotionEvent e) {
353        maybeSendLongPress(e, mPolicyFlags);
354    }
355
356    @Override
357    public boolean onSingleTapUp(MotionEvent event) {
358        mFirstTapDetected = true;
359        return false;
360    }
361
362    @Override
363    public boolean onSingleTapConfirmed(MotionEvent event) {
364        clear();
365        return false;
366    }
367
368    @Override
369    public boolean onDoubleTap(MotionEvent event) {
370        // The processing of the double tap is deferred until the finger is
371        // lifted, so that we can detect a long press on the second tap.
372        mDoubleTapDetected = true;
373        return false;
374    }
375
376    private void maybeSendLongPress(MotionEvent event, int policyFlags) {
377        if (!mDoubleTapDetected) {
378            return;
379        }
380
381        clear();
382
383        mListener.onDoubleTapAndHold(event, policyFlags);
384    }
385
386    private boolean finishDoubleTap(MotionEvent event, int policyFlags) {
387        clear();
388
389        return mListener.onDoubleTap(event, policyFlags);
390    }
391
392    private void cancelGesture() {
393        mRecognizingGesture = false;
394        mGestureStarted = false;
395        mStrokeBuffer.clear();
396    }
397
398    private boolean recognizeGesture(MotionEvent event, int policyFlags) {
399        Gesture gesture = new Gesture();
400        gesture.addStroke(new GestureStroke(mStrokeBuffer));
401
402        ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
403        if (!predictions.isEmpty()) {
404            Prediction bestPrediction = predictions.get(0);
405            if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
406                if (DEBUG) {
407                    Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
408                            + bestPrediction.score);
409                }
410                try {
411                    final int gestureId = Integer.parseInt(bestPrediction.name);
412                    return mListener.onGestureCompleted(gestureId);
413                } catch (NumberFormatException nfe) {
414                    Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
415                }
416            }
417        }
418
419        return mListener.onGestureCancelled(event, policyFlags);
420    }
421
422    private MotionEvent mapSecondPointerToFirstPointer(MotionEvent event) {
423        // Only map basic events when two fingers are down.
424        if (event.getPointerCount() != 2 ||
425                (event.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN &&
426                 event.getActionMasked() != MotionEvent.ACTION_POINTER_UP &&
427                 event.getActionMasked() != MotionEvent.ACTION_MOVE)) {
428            return null;
429        }
430
431        int action = event.getActionMasked();
432
433        if (action == MotionEvent.ACTION_POINTER_DOWN) {
434            action = MotionEvent.ACTION_DOWN;
435        } else if (action == MotionEvent.ACTION_POINTER_UP) {
436            action = MotionEvent.ACTION_UP;
437        }
438
439        // Map the information from the second pointer to the first.
440        return MotionEvent.obtain(mSecondPointerDownTime, event.getEventTime(), action,
441                event.getX(1), event.getY(1), event.getPressure(1), event.getSize(1),
442                event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
443                event.getDeviceId(), event.getEdgeFlags());
444    }
445}
446