| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.accessibility.gestures; |
| |
| import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG; |
| |
| import android.annotation.IntDef; |
| import android.os.Handler; |
| import android.util.Slog; |
| import android.view.MotionEvent; |
| import android.view.ViewConfiguration; |
| |
| /** |
| * This class describes a common base for gesture matchers. A gesture matcher checks a series of |
| * motion events against a single gesture. Coordinating the individual gesture matchers is done by |
| * the GestureManifold. To create a new Gesture, extend this class and override the onDown, onMove, |
| * onUp, etc methods as necessary. If you don't override a method your matcher will do nothing in |
| * response to that type of event. Finally, be sure to give your gesture a name by overriding |
| * getGestureName(). |
| */ |
| abstract class GestureMatcher { |
| // Potential states for this individual gesture matcher. |
| // In STATE_CLEAR, this matcher is accepting new motion events but has not formally signaled |
| // that there is enough data to judge that a gesture has started. |
| static final int STATE_CLEAR = 0; |
| // In STATE_GESTURE_STARTED, this matcher continues to accept motion events and it has signaled |
| // to the gesture manifold that what looks like the specified gesture has started. |
| static final int STATE_GESTURE_STARTED = 1; |
| // In STATE_GESTURE_COMPLETED, this matcher has successfully matched the specified gesture. and |
| // will not accept motion events until it is cleared. |
| static final int STATE_GESTURE_COMPLETED = 2; |
| // In STATE_GESTURE_CANCELED, this matcher will not accept new motion events because it is |
| // impossible that this set of motion events will match the specified gesture. |
| static final int STATE_GESTURE_CANCELED = 3; |
| |
| @IntDef({STATE_CLEAR, STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELED}) |
| public @interface State {} |
| |
| @State private int mState = STATE_CLEAR; |
| // The id number of the gesture that gets passed to accessibility services. |
| private final int mGestureId; |
| // handler for asynchronous operations like timeouts |
| private final Handler mHandler; |
| |
| private final StateChangeListener mListener; |
| |
| // Use this to transition to new states after a delay. |
| // e.g. cancel or complete after some timeout. |
| // Convenience functions for tapTimeout and doubleTapTimeout are already defined here. |
| protected final DelayedTransition mDelayedTransition; |
| |
| GestureMatcher(int gestureId, Handler handler, StateChangeListener listener) { |
| mGestureId = gestureId; |
| mHandler = handler; |
| mDelayedTransition = new DelayedTransition(); |
| mListener = listener; |
| } |
| |
| /** |
| * Resets all state information for this matcher. Subclasses that include their own state |
| * information should override this method to reset their own state information and call |
| * super.clear(). |
| */ |
| protected void clear() { |
| mState = STATE_CLEAR; |
| cancelPendingTransitions(); |
| } |
| |
| public int getState() { |
| return mState; |
| } |
| |
| /** |
| * Transitions to a new state and notifies any listeners. Note that any pending transitions are |
| * canceled. |
| */ |
| private void setState( |
| @State int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| mState = state; |
| cancelPendingTransitions(); |
| mListener.onStateChanged(mGestureId, mState, event, rawEvent, policyFlags); |
| } |
| |
| /** Indicates that there is evidence to suggest that this gesture has started. */ |
| protected final void startGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| setState(STATE_GESTURE_STARTED, event, rawEvent, policyFlags); |
| } |
| |
| /** Indicates this stream of motion events can no longer match this gesture. */ |
| protected final void cancelGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags); |
| } |
| |
| /** Indicates this gesture is completed. */ |
| protected final void completeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| setState(STATE_GESTURE_COMPLETED, event, rawEvent, policyFlags); |
| } |
| |
| public int getGestureId() { |
| return mGestureId; |
| } |
| |
| /** |
| * Process a motion event and attempt to match it to this gesture. |
| * |
| * @param event the event as passed in from the event stream. |
| * @param rawEvent the original un-modified event. Useful for calculating movements in physical |
| * space. |
| * @param policyFlags the policy flags as passed in from the event stream. |
| * @return the state of this matcher. |
| */ |
| public final int onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| if (mState == STATE_GESTURE_CANCELED || mState == STATE_GESTURE_COMPLETED) { |
| return mState; |
| } |
| switch (event.getActionMasked()) { |
| case MotionEvent.ACTION_DOWN: |
| onDown(event, rawEvent, policyFlags); |
| break; |
| case MotionEvent.ACTION_POINTER_DOWN: |
| onPointerDown(event, rawEvent, policyFlags); |
| break; |
| case MotionEvent.ACTION_MOVE: |
| onMove(event, rawEvent, policyFlags); |
| break; |
| case MotionEvent.ACTION_POINTER_UP: |
| onPointerUp(event, rawEvent, policyFlags); |
| break; |
| case MotionEvent.ACTION_UP: |
| onUp(event, rawEvent, policyFlags); |
| break; |
| default: |
| // Cancel because of invalid event. |
| setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags); |
| break; |
| } |
| return mState; |
| } |
| |
| /** |
| * Matchers override this method to respond to ACTION_DOWN events. ACTION_DOWN events indicate |
| * the first finger has touched the screen. If not overridden the default response is to do |
| * nothing. |
| */ |
| protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {} |
| |
| /** |
| * Matchers override this method to respond to ACTION_POINTER_DOWN events. ACTION_POINTER_DOWN |
| * indicates that more than one finger has touched the screen. If not overridden the default |
| * response is to do nothing. |
| * |
| * @param event the event as passed in from the event stream. |
| * @param rawEvent the original un-modified event. Useful for calculating movements in physical |
| * space. |
| * @param policyFlags the policy flags as passed in from the event stream. |
| */ |
| protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {} |
| |
| /** |
| * Matchers override this method to respond to ACTION_MOVE events. ACTION_MOVE indicates that |
| * one or fingers has moved. If not overridden the default response is to do nothing. |
| * |
| * @param event the event as passed in from the event stream. |
| * @param rawEvent the original un-modified event. Useful for calculating movements in physical |
| * space. |
| * @param policyFlags the policy flags as passed in from the event stream. |
| */ |
| protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {} |
| |
| /** |
| * Matchers override this method to respond to ACTION_POINTER_UP events. ACTION_POINTER_UP |
| * indicates that a finger has lifted from the screen but at least one finger continues to touch |
| * the screen. If not overridden the default response is to do nothing. |
| * |
| * @param event the event as passed in from the event stream. |
| * @param rawEvent the original un-modified event. Useful for calculating movements in physical |
| * space. |
| * @param policyFlags the policy flags as passed in from the event stream. |
| */ |
| protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {} |
| |
| /** |
| * Matchers override this method to respond to ACTION_UP events. ACTION_UP indicates that there |
| * are no more fingers touching the screen. If not overridden the default response is to do |
| * nothing. |
| * |
| * @param event the event as passed in from the event stream. |
| * @param rawEvent the original un-modified event. Useful for calculating movements in physical |
| * space. |
| * @param policyFlags the policy flags as passed in from the event stream. |
| */ |
| protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {} |
| |
| /** Cancels this matcher after the tap timeout. Any pending state transitions are removed. */ |
| protected void cancelAfterTapTimeout(MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| cancelAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags); |
| } |
| |
| /** Cancels this matcher after the double tap timeout. Any pending cancelations are removed. */ |
| protected final void cancelAfterDoubleTapTimeout( |
| MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| cancelAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags); |
| } |
| |
| /** |
| * Cancels this matcher after the specified timeout. Any pending cancelations are removed. Used |
| * to prevent this matcher from accepting motion events until it is cleared. |
| */ |
| protected final void cancelAfter( |
| long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| mDelayedTransition.cancel(); |
| mDelayedTransition.post(STATE_GESTURE_CANCELED, timeout, event, rawEvent, policyFlags); |
| } |
| |
| /** Cancels any delayed transitions between states scheduled for this matcher. */ |
| protected final void cancelPendingTransitions() { |
| mDelayedTransition.cancel(); |
| } |
| |
| /** |
| * Signals that this gesture has been completed after the tap timeout has expired. Used to |
| * ensure that there is no conflict with another gesture or for gestures that explicitly require |
| * a hold. |
| */ |
| protected final void completeAfterLongPressTimeout( |
| MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| completeAfter(ViewConfiguration.getLongPressTimeout(), event, rawEvent, policyFlags); |
| } |
| |
| /** |
| * Signals that this gesture has been completed after the tap timeout has expired. Used to |
| * ensure that there is no conflict with another gesture or for gestures that explicitly require |
| * a hold. |
| */ |
| protected final void completeAfterTapTimeout( |
| MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| completeAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags); |
| } |
| |
| /** |
| * Signals that this gesture has been completed after the specified timeout has expired. Used to |
| * ensure that there is no conflict with another gesture or for gestures that explicitly require |
| * a hold. |
| */ |
| protected final void completeAfter( |
| long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| mDelayedTransition.cancel(); |
| mDelayedTransition.post(STATE_GESTURE_COMPLETED, timeout, event, rawEvent, policyFlags); |
| } |
| |
| /** |
| * Signals that this gesture has been completed after the double-tap timeout has expired. Used |
| * to ensure that there is no conflict with another gesture or for gestures that explicitly |
| * require a hold. |
| */ |
| protected final void completeAfterDoubleTapTimeout( |
| MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| completeAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags); |
| } |
| |
| public static String getStateSymbolicName(@State int state) { |
| switch (state) { |
| case STATE_CLEAR: |
| return "STATE_CLEAR"; |
| case STATE_GESTURE_STARTED: |
| return "STATE_GESTURE_STARTED"; |
| case STATE_GESTURE_COMPLETED: |
| return "STATE_GESTURE_COMPLETED"; |
| case STATE_GESTURE_CANCELED: |
| return "STATE_GESTURE_CANCELED"; |
| default: |
| return "Unknown state: " + state; |
| } |
| } |
| |
| /** |
| * Returns a readable name for this matcher that can be displayed to the user and in system |
| * logs. |
| */ |
| abstract String getGestureName(); |
| |
| /** |
| * Returns a String representation of this matcher. Each matcher can override this method to add |
| * extra state information to the string representation. |
| */ |
| public String toString() { |
| return getGestureName() + ":" + getStateSymbolicName(mState); |
| } |
| |
| /** This class allows matchers to transition between states on a delay. */ |
| protected final class DelayedTransition implements Runnable { |
| |
| private static final String LOG_TAG = "GestureMatcher.DelayedTransition"; |
| int mTargetState; |
| MotionEvent mEvent; |
| MotionEvent mRawEvent; |
| int mPolicyFlags; |
| |
| public void cancel() { |
| // Avoid meaningless debug messages. |
| if (DEBUG && isPending()) { |
| Slog.d( |
| LOG_TAG, |
| getGestureName() |
| + ": canceling delayed transition to " |
| + getStateSymbolicName(mTargetState)); |
| } |
| mHandler.removeCallbacks(this); |
| } |
| |
| public void post( |
| int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags) { |
| mTargetState = state; |
| mEvent = event; |
| mRawEvent = rawEvent; |
| mPolicyFlags = policyFlags; |
| mHandler.postDelayed(this, delay); |
| if (DEBUG) { |
| Slog.d( |
| LOG_TAG, |
| getGestureName() |
| + ": posting delayed transition to " |
| + getStateSymbolicName(mTargetState)); |
| } |
| } |
| |
| public boolean isPending() { |
| return mHandler.hasCallbacks(this); |
| } |
| |
| public void forceSendAndRemove() { |
| if (isPending()) { |
| run(); |
| cancel(); |
| } |
| } |
| |
| @Override |
| public void run() { |
| if (DEBUG) { |
| Slog.d( |
| LOG_TAG, |
| getGestureName() |
| + ": executing delayed transition to " |
| + getStateSymbolicName(mTargetState)); |
| } |
| setState(mTargetState, mEvent, mRawEvent, mPolicyFlags); |
| } |
| } |
| |
| /** Interface to allow a class to listen for state changes in a specific gesture matcher */ |
| interface StateChangeListener { |
| |
| void onStateChanged( |
| int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags); |
| } |
| } |