• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.server.accessibility.gestures;
18 
19 import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.os.Handler;
24 import android.util.Slog;
25 import android.view.MotionEvent;
26 import android.view.ViewConfiguration;
27 
28 /**
29  * This class describes a common base for gesture matchers. A gesture matcher checks a series of
30  * motion events against a single gesture. Coordinating the individual gesture matchers is done by
31  * the GestureManifold. To create a new Gesture, extend this class and override the onDown, onMove,
32  * onUp, etc methods as necessary. If you don't override a method your matcher will do nothing in
33  * response to that type of event. Finally, be sure to give your gesture a name by overriding
34  * getGestureName().
35  * @hide
36  */
37 public abstract class GestureMatcher {
38     // Potential states for this individual gesture matcher.
39     // In STATE_CLEAR, this matcher is accepting new motion events but has not formally signaled
40     // that there is enough data to judge that a gesture has started.
41     public static final int STATE_CLEAR = 0;
42     // In STATE_GESTURE_STARTED, this matcher continues to accept motion events and it has signaled
43     // to the gesture manifold that what looks like the specified gesture has started.
44     public static final int STATE_GESTURE_STARTED = 1;
45     // In STATE_GESTURE_COMPLETED, this matcher has successfully matched the specified gesture. and
46     // will not accept motion events until it is cleared.
47     public static final int STATE_GESTURE_COMPLETED = 2;
48     // In STATE_GESTURE_CANCELED, this matcher will not accept new motion events because it is
49     // impossible that this set of motion events will match the specified gesture.
50     public static final int STATE_GESTURE_CANCELED = 3;
51 
52     @IntDef({STATE_CLEAR, STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELED})
53     public @interface State {}
54 
55     @State private int mState = STATE_CLEAR;
56     // The id number of the gesture that gets passed to accessibility services.
57     private final int mGestureId;
58     // handler for asynchronous operations like timeouts
59     private final Handler mHandler;
60 
61     private StateChangeListener mListener;
62 
63     // Use this to transition to new states after a delay.
64     // e.g. cancel or complete after some timeout.
65     // Convenience functions for tapTimeout and doubleTapTimeout are already defined here.
66     protected final DelayedTransition mDelayedTransition;
67 
GestureMatcher(int gestureId, Handler handler, StateChangeListener listener)68     protected GestureMatcher(int gestureId, Handler handler, StateChangeListener listener) {
69         mGestureId = gestureId;
70         mHandler = handler;
71         mDelayedTransition = new DelayedTransition();
72         mListener = listener;
73     }
74 
75     /**
76      * Resets all state information for this matcher. Subclasses that include their own state
77      * information should override this method to reset their own state information and call
78      * super.clear().
79      */
clear()80     public void clear() {
81         mState = STATE_CLEAR;
82         cancelPendingTransitions();
83     }
84 
getState()85     public final int getState() {
86         return mState;
87     }
88 
89     /**
90      * Transitions to a new state and notifies any listeners. Note that any pending transitions are
91      * canceled.
92      */
setState( @tate int state, MotionEvent event, MotionEvent rawEvent, int policyFlags)93     private void setState(
94             @State int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
95         mState = state;
96         cancelPendingTransitions();
97         if (mListener != null) {
98             mListener.onStateChanged(mGestureId, mState, event, rawEvent, policyFlags);
99         }
100     }
101 
102     /** Indicates that there is evidence to suggest that this gesture has started. */
startGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags)103     protected final void startGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
104         setState(STATE_GESTURE_STARTED, event, rawEvent, policyFlags);
105     }
106 
107     /** Indicates this stream of motion events can no longer match this gesture. */
cancelGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags)108     protected final void cancelGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
109         setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
110     }
111 
112     /** Indicates this gesture is completed. */
completeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags)113     protected final void completeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
114         setState(STATE_GESTURE_COMPLETED, event, rawEvent, policyFlags);
115     }
116 
setListener(@onNull StateChangeListener listener)117     public final void setListener(@NonNull StateChangeListener listener) {
118         mListener = listener;
119     }
120 
getGestureId()121     public int getGestureId() {
122         return mGestureId;
123     }
124 
125     /**
126      * Process a motion event and attempt to match it to this gesture.
127      *
128      * @param event the event as passed in from the event stream.
129      * @param rawEvent the original un-modified event. Useful for calculating movements in physical
130      *     space.
131      * @param policyFlags the policy flags as passed in from the event stream.
132      * @return the state of this matcher.
133      */
onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)134     public final int onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
135         if (mState == STATE_GESTURE_CANCELED || mState == STATE_GESTURE_COMPLETED) {
136             return mState;
137         }
138         switch (event.getActionMasked()) {
139             case MotionEvent.ACTION_DOWN:
140                 onDown(event, rawEvent, policyFlags);
141                 break;
142             case MotionEvent.ACTION_POINTER_DOWN:
143                 onPointerDown(event, rawEvent, policyFlags);
144                 break;
145             case MotionEvent.ACTION_MOVE:
146                 onMove(event, rawEvent, policyFlags);
147                 break;
148             case MotionEvent.ACTION_POINTER_UP:
149                 onPointerUp(event, rawEvent, policyFlags);
150                 break;
151             case MotionEvent.ACTION_UP:
152                 onUp(event, rawEvent, policyFlags);
153                 break;
154             default:
155                 // Cancel because of invalid event.
156                 setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
157                 break;
158         }
159         return mState;
160     }
161 
162     /**
163      * Matchers override this method to respond to ACTION_DOWN events. ACTION_DOWN events indicate
164      * the first finger has touched the screen. If not overridden the default response is to do
165      * nothing.
166      */
onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags)167     protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
168 
169     /**
170      * Matchers override this method to respond to ACTION_POINTER_DOWN events. ACTION_POINTER_DOWN
171      * indicates that more than one finger has touched the screen. If not overridden the default
172      * response is to do nothing.
173      *
174      * @param event the event as passed in from the event stream.
175      * @param rawEvent the original un-modified event. Useful for calculating movements in physical
176      *     space.
177      * @param policyFlags the policy flags as passed in from the event stream.
178      */
onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags)179     protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
180 
181     /**
182      * Matchers override this method to respond to ACTION_MOVE events. ACTION_MOVE indicates that
183      * one or fingers has moved. If not overridden the default response is to do nothing.
184      *
185      * @param event the event as passed in from the event stream.
186      * @param rawEvent the original un-modified event. Useful for calculating movements in physical
187      *     space.
188      * @param policyFlags the policy flags as passed in from the event stream.
189      */
onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags)190     protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
191 
192     /**
193      * Matchers override this method to respond to ACTION_POINTER_UP events. ACTION_POINTER_UP
194      * indicates that a finger has lifted from the screen but at least one finger continues to touch
195      * the screen. If not overridden the default response is to do nothing.
196      *
197      * @param event the event as passed in from the event stream.
198      * @param rawEvent the original un-modified event. Useful for calculating movements in physical
199      *     space.
200      * @param policyFlags the policy flags as passed in from the event stream.
201      */
onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags)202     protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
203 
204     /**
205      * Matchers override this method to respond to ACTION_UP events. ACTION_UP indicates that there
206      * are no more fingers touching the screen. If not overridden the default response is to do
207      * nothing.
208      *
209      * @param event the event as passed in from the event stream.
210      * @param rawEvent the original un-modified event. Useful for calculating movements in physical
211      *     space.
212      * @param policyFlags the policy flags as passed in from the event stream.
213      */
onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags)214     protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
215 
216     /** Cancels this matcher after the tap timeout. Any pending state transitions are removed. */
cancelAfterTapTimeout(MotionEvent event, MotionEvent rawEvent, int policyFlags)217     protected void cancelAfterTapTimeout(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
218         cancelAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
219     }
220 
221     /** Cancels this matcher after the double tap timeout. Any pending cancelations are removed. */
cancelAfterDoubleTapTimeout( MotionEvent event, MotionEvent rawEvent, int policyFlags)222     protected final void cancelAfterDoubleTapTimeout(
223             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
224         cancelAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
225     }
226 
227     /**
228      * Cancels this matcher after the specified timeout. Any pending cancelations are removed. Used
229      * to prevent this matcher from accepting motion events until it is cleared.
230      */
cancelAfter( long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags)231     protected final void cancelAfter(
232             long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
233         mDelayedTransition.cancel();
234         mDelayedTransition.post(STATE_GESTURE_CANCELED, timeout, event, rawEvent, policyFlags);
235     }
236 
237     /** Cancels any delayed transitions between states scheduled for this matcher. */
cancelPendingTransitions()238     protected final void cancelPendingTransitions() {
239         mDelayedTransition.cancel();
240     }
241 
242     /**
243      * Signals that this gesture has been completed after the tap timeout has expired. Used to
244      * ensure that there is no conflict with another gesture or for gestures that explicitly require
245      * a hold.
246      */
completeAfterLongPressTimeout( MotionEvent event, MotionEvent rawEvent, int policyFlags)247     protected final void completeAfterLongPressTimeout(
248             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
249         completeAfter(ViewConfiguration.getLongPressTimeout(), event, rawEvent, policyFlags);
250     }
251 
252     /**
253      * Signals that this gesture has been completed after the tap timeout has expired. Used to
254      * ensure that there is no conflict with another gesture or for gestures that explicitly require
255      * a hold.
256      */
completeAfterTapTimeout( MotionEvent event, MotionEvent rawEvent, int policyFlags)257     protected final void completeAfterTapTimeout(
258             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
259         completeAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
260     }
261 
262     /**
263      * Signals that this gesture has been completed after the specified timeout has expired. Used to
264      * ensure that there is no conflict with another gesture or for gestures that explicitly require
265      * a hold.
266      */
completeAfter( long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags)267     protected final void completeAfter(
268             long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
269         mDelayedTransition.cancel();
270         mDelayedTransition.post(STATE_GESTURE_COMPLETED, timeout, event, rawEvent, policyFlags);
271     }
272 
273     /**
274      * Signals that this gesture has been completed after the double-tap timeout has expired. Used
275      * to ensure that there is no conflict with another gesture or for gestures that explicitly
276      * require a hold.
277      */
completeAfterDoubleTapTimeout( MotionEvent event, MotionEvent rawEvent, int policyFlags)278     protected final void completeAfterDoubleTapTimeout(
279             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
280         completeAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
281     }
282 
getStateSymbolicName(@tate int state)283     static String getStateSymbolicName(@State int state) {
284         switch (state) {
285             case STATE_CLEAR:
286                 return "STATE_CLEAR";
287             case STATE_GESTURE_STARTED:
288                 return "STATE_GESTURE_STARTED";
289             case STATE_GESTURE_COMPLETED:
290                 return "STATE_GESTURE_COMPLETED";
291             case STATE_GESTURE_CANCELED:
292                 return "STATE_GESTURE_CANCELED";
293             default:
294                 return "Unknown state: " + state;
295         }
296     }
297 
298     /**
299      * Returns a readable name for this matcher that can be displayed to the user and in system
300      * logs.
301      */
getGestureName()302     protected abstract String getGestureName();
303 
304     /**
305      * Returns a String representation of this matcher. Each matcher can override this method to add
306      * extra state information to the string representation.
307      */
toString()308     public String toString() {
309         return getGestureName() + ":" + getStateSymbolicName(mState);
310     }
311 
312     /** This class allows matchers to transition between states on a delay. */
313     protected final class DelayedTransition implements Runnable {
314 
315         private static final String LOG_TAG = "GestureMatcher.DelayedTransition";
316         int mTargetState;
317         MotionEvent mEvent;
318         MotionEvent mRawEvent;
319         int mPolicyFlags;
320 
cancel()321         public void cancel() {
322             // Avoid meaningless debug messages.
323             if (DEBUG && isPending()) {
324                 Slog.d(
325                         LOG_TAG,
326                         getGestureName()
327                                 + ": canceling delayed transition to "
328                                 + getStateSymbolicName(mTargetState));
329             }
330             mHandler.removeCallbacks(this);
331             recycleEvent();
332         }
333 
post( int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags)334         public void post(
335                 int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
336             // Recycle the old event first if necessary, to handle duplicate calls to post.
337             recycleEvent();
338             mTargetState = state;
339             if (android.view.accessibility.Flags.copyEventsForGestureDetection()) {
340                 mEvent = event.copy();
341                 mRawEvent = rawEvent.copy();
342             } else {
343                 mEvent = event;
344                 mRawEvent = rawEvent;
345             }
346             mPolicyFlags = policyFlags;
347             mHandler.postDelayed(this, delay);
348             if (DEBUG) {
349                 Slog.d(
350                         LOG_TAG,
351                         getGestureName()
352                                 + ": posting delayed transition to "
353                                 + getStateSymbolicName(mTargetState));
354             }
355         }
356 
isPending()357         public boolean isPending() {
358             return mHandler.hasCallbacks(this);
359         }
360 
forceSendAndRemove()361         public void forceSendAndRemove() {
362             if (isPending()) {
363                 run();
364                 cancel();
365             }
366         }
367 
368         @Override
run()369         public void run() {
370             if (DEBUG) {
371                 Slog.d(
372                         LOG_TAG,
373                         getGestureName()
374                                 + ": executing delayed transition to "
375                                 + getStateSymbolicName(mTargetState));
376             }
377             setState(mTargetState, mEvent, mRawEvent, mPolicyFlags);
378             recycleEvent();
379         }
380 
recycleEvent()381         private void recycleEvent() {
382             if (android.view.accessibility.Flags.copyEventsForGestureDetection()) {
383                 if (mEvent == null || mRawEvent == null) {
384                     return;
385                 }
386                 mEvent.recycle();
387                 mRawEvent.recycle();
388                 mEvent = null;
389                 mRawEvent = null;
390             }
391         }
392     }
393 
394     /** Interface to allow a class to listen for state changes in a specific gesture matcher */
395     public interface StateChangeListener {
396 
onStateChanged( int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags)397         void onStateChanged(
398                 int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags);
399     }
400 }
401