diff options
author | 2011-06-22 17:11:07 -0700 | |
---|---|---|
committer | 2011-06-30 11:59:35 -0700 | |
commit | 87d7929d142f7c5f1937e12d6fd32a43ab00740e (patch) | |
tree | 0c70162130fef28c0e605653b0fe2372e5895761 /java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java | |
parent | 7e7244873a1317ba898e498526b963f7d41caa86 (diff) | |
download | latinime-87d7929d142f7c5f1937e12d6fd32a43ab00740e.tar.gz latinime-87d7929d142f7c5f1937e12d6fd32a43ab00740e.tar.xz latinime-87d7929d142f7c5f1937e12d6fd32a43ab00740e.zip |
Added text navigation gestures for keyboard touch exploration.
Bug: 4905427
Change-Id: I9b44d65e4503e46ce71322a3c325c55d188e34a0
Diffstat (limited to 'java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java')
-rw-r--r-- | java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java new file mode 100644 index 000000000..9d99e3131 --- /dev/null +++ b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2011 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.inputmethod.accessibility; + +import android.content.Context; +import android.os.Message; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.inputmethod.compat.MotionEventCompatUtils; +import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.latin.StaticInnerHandlerWrapper; + +/** + * Detects flick gestures within a stream of hover events. + * <p> + * A flick gesture is defined as a stream of hover events with the following + * properties: + * <ul> + * <li>Begins with a {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event + * <li>Contains any number of {@link MotionEventCompatUtils#ACTION_HOVER_MOVE} + * events + * <li>Ends with a {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event + * <li>Maximum duration of 250 milliseconds + * <li>Minimum distance between enter and exit points must be at least equal to + * scaled double tap slop (see + * {@link ViewConfiguration#getScaledDoubleTapSlop()}) + * </ul> + * <p> + * Initial enter events are intercepted and cached until the stream fails to + * satisfy the constraints defined above, at which point the cached enter event + * is sent to its source {@link AccessibleKeyboardViewProxy} and subsequent move + * and exit events are ignored. + */ +public abstract class FlickGestureDetector { + public static final int FLICK_UP = 0; + public static final int FLICK_RIGHT = 1; + public static final int FLICK_LEFT = 2; + public static final int FLICK_DOWN = 3; + + private final FlickHandler mFlickHandler; + private final int mFlickRadiusSquare; + + private AccessibleKeyboardViewProxy mCachedView; + private PointerTracker mCachedTracker; + private MotionEvent mCachedHoverEnter; + + private static class FlickHandler extends StaticInnerHandlerWrapper<FlickGestureDetector> { + private static final int MSG_FLICK_TIMEOUT = 1; + + /** The maximum duration of a flick gesture in milliseconds. */ + private static final int DELAY_FLICK_TIMEOUT = 250; + + public FlickHandler(FlickGestureDetector outerInstance) { + super(outerInstance); + } + + @Override + public void handleMessage(Message msg) { + final FlickGestureDetector gestureDetector = getOuterInstance(); + + switch (msg.what) { + case MSG_FLICK_TIMEOUT: + gestureDetector.clearFlick(true); + } + } + + public void startFlickTimeout() { + cancelFlickTimeout(); + sendEmptyMessageDelayed(MSG_FLICK_TIMEOUT, DELAY_FLICK_TIMEOUT); + } + + public void cancelFlickTimeout() { + removeMessages(MSG_FLICK_TIMEOUT); + } + } + + /** + * Creates a new flick gesture detector. + * + * @param context The parent context. + */ + public FlickGestureDetector(Context context) { + final int doubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); + + mFlickHandler = new FlickHandler(this); + mFlickRadiusSquare = doubleTapSlop * doubleTapSlop; + } + + /** + * Processes motion events to detect flick gestures. + * + * @param event The current event. + * @param view The source of the event. + * @param tracker A pointer tracker for the event. + * @return {@code true} if the event was handled. + */ + public boolean onHoverEvent(MotionEvent event, AccessibleKeyboardViewProxy view, + PointerTracker tracker) { + // Always cache and consume the first hover event. + if (event.getAction() == MotionEventCompatUtils.ACTION_HOVER_ENTER) { + mCachedView = view; + mCachedTracker = tracker; + mCachedHoverEnter = MotionEvent.obtain(event); + mFlickHandler.startFlickTimeout(); + return true; + } + + // Stop if the event has already been canceled. + if (mCachedHoverEnter == null) { + return false; + } + + final float distanceSquare = calculateDistanceSquare(mCachedHoverEnter, event); + final long timeout = event.getEventTime() - mCachedHoverEnter.getEventTime(); + + switch (event.getAction()) { + case MotionEventCompatUtils.ACTION_HOVER_MOVE: + // Consume all valid move events before timeout. + return true; + case MotionEventCompatUtils.ACTION_HOVER_EXIT: + // Ignore exit events outside the flick radius. + if (distanceSquare < mFlickRadiusSquare) { + clearFlick(true); + return false; + } else { + return dispatchFlick(mCachedHoverEnter, event); + } + default: + return false; + } + } + + /** + * Clears the cached flick information and optionally forwards the event to + * the source view's internal hover event handler. + * + * @param sendCachedEvent Set to {@code true} to forward the hover event to + * the source view. + */ + private void clearFlick(boolean sendCachedEvent) { + mFlickHandler.cancelFlickTimeout(); + + if (mCachedHoverEnter != null) { + if (sendCachedEvent) { + mCachedView.onHoverEventInternal(mCachedHoverEnter, mCachedTracker); + } + mCachedHoverEnter.recycle(); + mCachedHoverEnter = null; + } + + mCachedTracker = null; + mCachedView = null; + } + + /** + * Computes the direction of a flick gesture and forwards it to + * {@link #onFlick(MotionEvent, MotionEvent, int)} for handling. + * + * @param e1 The {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event + * where the flick started. + * @param e2 The {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event + * where the flick ended. + * @return {@code true} if the flick event was handled. + */ + private boolean dispatchFlick(MotionEvent e1, MotionEvent e2) { + clearFlick(false); + + final float dX = e2.getX() - e1.getX(); + final float dY = e2.getY() - e1.getY(); + final int direction; + + if (dY > dX) { + if (dY > -dX) { + direction = FLICK_DOWN; + } else { + direction = FLICK_LEFT; + } + } else { + if (dY > -dX) { + direction = FLICK_RIGHT; + } else { + direction = FLICK_UP; + } + } + + return onFlick(e1, e2, direction); + } + + private float calculateDistanceSquare(MotionEvent e1, MotionEvent e2) { + final float dX = e2.getX() - e1.getX(); + final float dY = e2.getY() - e1.getY(); + return (dX * dX) + (dY * dY); + } + + /** + * Handles a detected flick gesture. + * + * @param e1 The {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event + * where the flick started. + * @param e2 The {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event + * where the flick ended. + * @param direction The direction of the flick event, one of: + * <ul> + * <li>{@link #FLICK_UP} + * <li>{@link #FLICK_DOWN} + * <li>{@link #FLICK_LEFT} + * <li>{@link #FLICK_RIGHT} + * </ul> + * @return {@code true} if the flick event was handled. + */ + public abstract boolean onFlick(MotionEvent e1, MotionEvent e2, int direction); +} |