Chromium Code Reviews| Index: remoting/android/java/src/org/chromium/chromoting/InputMonitor.java |
| diff --git a/remoting/android/java/src/org/chromium/chromoting/InputMonitor.java b/remoting/android/java/src/org/chromium/chromoting/InputMonitor.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..62003d33562f4c096d5df97cbe4140906b504b56 |
| --- /dev/null |
| +++ b/remoting/android/java/src/org/chromium/chromoting/InputMonitor.java |
| @@ -0,0 +1,306 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package org.chromium.chromoting; |
| + |
| +import android.content.Context; |
| +import android.graphics.Rect; |
| +import android.view.GestureDetector; |
| +import android.view.MotionEvent; |
| +import android.view.ScaleGestureDetector; |
| +import android.view.ViewConfiguration; |
| + |
| +import org.chromium.base.VisibleForTesting; |
| +import org.chromium.chromoting.InputState.DetectedAction; |
| +import org.chromium.chromoting.InputState.StartAction; |
| + |
| +/** |
| + * A combination of existing Android and chromium motion and touch detectors, and provide a set of |
| + * {@link Event} when each kind of touch behavior has been detected. |
| + */ |
| +public final class InputMonitor |
| + implements GestureDetector.OnGestureListener, |
| + ScaleGestureDetector.OnScaleGestureListener, |
| + TapGestureDetector.OnTapListener { |
| + /** Tap with one or more fingers. */ |
| + private final Event.Raisable<TapEventParameter> mOnTap; |
| + |
| + /** Long press and hold with one or more fingers. */ |
| + private final Event.Raisable<TapEventParameter> mOnPressAndHold; |
| + |
| + /** Any motion event received. */ |
| + private final Event.Raisable<MotionEvent> mOnTouchEvent; |
| + |
| + /** Scroll with two fingers. */ |
| + private final Event.Raisable<TwoPointsEventParameter> mOnScroll; |
| + |
| + /** Fling with two fingers. */ |
| + private final Event.Raisable<TwoPointsEventParameter> mOnScrollFling; |
| + |
| + /** Fling with one finger. */ |
| + private final Event.Raisable<TwoPointsEventParameter> mOnFling; |
| + |
| + /** Scale with two fingers. */ |
| + private final Event.Raisable<ScaleEventParameter> mOnScale; |
| + |
| + /** Swipe with three or more fingers. */ |
| + private final Event.Raisable<TwoPointsEventParameter> mOnSwipe; |
| + |
| + /** Move with one finger. */ |
| + private final Event.Raisable<TwoPointsEventParameter> mOnMove; |
| + |
| + private final InputState.Settable mInputState; |
| + private final int mEdgeSlopInPx; |
| + private final float mSwipeThreshold; |
| + private final GestureDetector mScroller; |
| + private final ScaleGestureDetector mZoomer; |
| + private final TapGestureDetector mTapDetector; |
| + private final SwipePinchDetector mSwipePinchDetector; |
| + |
| + // Controls whether InputMonitor treats a scrolling with four or more fingers as a swipe. |
| + // TouchInputHandler treats a gesture with three fingers as a swipe. To make sure we can easily |
|
Lambros
2016/07/14 18:58:07
I don't see the need for this flag and I don't thi
Hzj_jie
2016/07/14 23:18:06
Personally I think swiping with three or four fing
|
| + // test the differences between TouchInputHandler and InputMonitor, we can set this variable to |
|
Lambros
2016/07/14 18:58:07
Also, better to avoid flags that make code behave
Hzj_jie
2016/07/14 23:18:06
Done.
|
| + // false to force InputMonitor to behave consistently as TouchInputHandler. |
| + private final boolean mSwipeWithMoreFingers; |
| + |
| + private Rect mPanGestureBounds; |
| + |
| + @VisibleForTesting |
| + InputMonitor(DesktopViewInterface view, Context context, boolean swipeWithMoreFingers) { |
| + mOnTap = new Event.Raisable<>(); |
| + mOnPressAndHold = new Event.Raisable<>(); |
| + mOnTouchEvent = new Event.Raisable<>(); |
| + mOnScroll = new Event.Raisable<>(); |
| + mOnScrollFling = new Event.Raisable<>(); |
| + mOnFling = new Event.Raisable<>(); |
| + mOnScale = new Event.Raisable<>(); |
| + mOnSwipe = new Event.Raisable<>(); |
| + mOnMove = new Event.Raisable<>(); |
| + mInputState = new InputState.Settable(); |
| + mEdgeSlopInPx = ViewConfiguration.get(context).getScaledEdgeSlop(); |
| + mSwipeThreshold = 40 * context.getResources().getDisplayMetrics().density; |
| + mScroller = new GestureDetector(context, this, null, false); |
| + mScroller.setIsLongpressEnabled(false); |
| + mZoomer = new ScaleGestureDetector(context, this); |
| + mTapDetector = new TapGestureDetector(context, this); |
| + mSwipePinchDetector = new SwipePinchDetector(context); |
| + view.onClientSizeChanged().add( |
| + new Event.ParameterRunnable<SizeChangedEventParameter>() { |
| + @Override |
| + public void run(SizeChangedEventParameter param) { |
| + handleClientSizeChanged(param); |
| + } |
| + }); |
| + // Currently we support only touch events. |
| + view.onTouch().add( |
| + new Event.ParameterRunnable<TouchEventParameter>() { |
| + @Override |
| + public void run(TouchEventParameter param) { |
| + handleTouch(param); |
| + } |
| + }); |
| + mSwipeWithMoreFingers = swipeWithMoreFingers; |
| + } |
| + |
| + public InputMonitor(DesktopViewInterface view, Context context) { |
| + this(view, context, true); |
| + } |
| + |
| + // -------------- Getters ------------------------------------------------------------- |
| + public Event<TapEventParameter> onTap() { |
| + return mOnTap; |
| + } |
| + |
| + public Event<TapEventParameter> onPressAndHold() { |
| + return mOnPressAndHold; |
| + } |
| + |
| + public Event<MotionEvent> onTouchEvent() { |
| + return mOnTouchEvent; |
| + } |
| + |
| + public Event<TwoPointsEventParameter> onScroll() { |
| + return mOnScroll; |
| + } |
| + |
| + public Event<TwoPointsEventParameter> onScrollFling() { |
| + return mOnScrollFling; |
| + } |
| + |
| + public Event<TwoPointsEventParameter> onFling() { |
| + return mOnFling; |
| + } |
| + |
| + public Event<ScaleEventParameter> onScale() { |
| + return mOnScale; |
| + } |
| + |
| + public Event<TwoPointsEventParameter> onSwipe() { |
| + return mOnSwipe; |
| + } |
| + |
| + public Event<TwoPointsEventParameter> onMove() { |
| + return mOnMove; |
| + } |
| + |
| + public InputState inputState() { |
| + return mInputState; |
| + } |
| + |
| + // -------------- Implementations of GestureDetector.OnGestureListener ---------------- |
| + |
| + /** Called whenever a gesture starts. Always accepts the gesture so it isn't ignored. */ |
| + @Override |
| + public boolean onDown(MotionEvent e) { |
| + return true; |
| + } |
| + |
| + /** Called when a fling gesture is recognized. */ |
| + @Override |
| + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { |
| + if (mInputState.suppressFling()) { |
| + return false; |
| + } |
| + |
| + if (mInputState.scrollFling()) { |
| + mInputState.setDetectedAction(DetectedAction.AFTER_SCROLL_FLING); |
| + mOnScrollFling.raise(new TwoPointsEventParameter(e1, e2, velocityX, velocityY)); |
| + return true; |
| + } |
| + |
| + if (mInputState.suppressCursorMovement()) { |
| + return false; |
| + } |
| + |
| + mInputState.setDetectedAction(DetectedAction.FLING); |
| + mOnFling.raise(new TwoPointsEventParameter(e1, e2, velocityX, velocityY)); |
| + return true; |
| + } |
| + |
| + /** Called when a long-press is triggered for one or more fingers. */ |
| + @Override |
| + public void onLongPress(MotionEvent e) {} |
| + |
| + /** Called when the user drags one or more fingers across the touchscreen. */ |
| + @Override |
| + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { |
| + if (!isInPanGestureBounds(e1)) { |
| + // The gesture of scrolling from edge to the center should be handled by Android OS. |
| + mInputState.setDetectedAction(InputState.DetectedAction.SCROLL_EDGE); |
| + return false; |
| + } |
| + |
| + if (((mSwipeWithMoreFingers && e2.getPointerCount() >= 3) || e2.getPointerCount() == 3) |
| + && !mInputState.swipeCompleted()) { |
| + if (distanceY > mSwipeThreshold || distanceY < -mSwipeThreshold) { |
| + mInputState.setDetectedAction(DetectedAction.SWIPE); |
| + mOnSwipe.raise(new TwoPointsEventParameter(e1, e2, distanceX, distanceY)); |
| + return true; |
| + } |
| + return false; |
| + } |
| + |
| + if (e2.getPointerCount() == 2 && mSwipePinchDetector.isSwiping()) { |
| + mInputState.setDetectedAction(DetectedAction.SCROLL); |
| + mOnScroll.raise(new TwoPointsEventParameter(e1, e2, distanceX, distanceY)); |
| + return true; |
| + } |
| + |
| + if (e2.getPointerCount() != 1 || mInputState.suppressCursorMovement()) { |
| + return false; |
| + } |
| + |
| + mInputState.setDetectedAction(DetectedAction.MOVE); |
| + mOnMove.raise(new TwoPointsEventParameter(e1, e2, distanceX, distanceY)); |
| + return true; |
| + } |
| + |
| + /** Called by {@link GestureDetector}, does nothing. */ |
| + @Override |
| + public void onShowPress(MotionEvent e) {} |
| + |
| + /** Called by {@link GestureDetector}, returns false to continue following gesture detection. */ |
| + @Override |
| + public boolean onSingleTapUp(MotionEvent e) { |
| + return false; |
| + } |
| + |
| + // --------- Implementations of ScaleGestureDetector.OnScaleGestureListener ----------- |
| + /** Called when the user is in the process of pinch-zooming. */ |
| + @Override |
| + public boolean onScale(ScaleGestureDetector detector) { |
| + if (!mSwipePinchDetector.isPinching()) { |
| + return false; |
| + } |
| + |
| + mInputState.setDetectedAction(DetectedAction.SCALE); |
| + mOnScale.raise(new ScaleEventParameter(detector.getScaleFactor(), |
| + detector.getFocusX(), |
| + detector.getFocusY())); |
| + return true; |
| + } |
| + |
| + /** |
| + * Called when the user starts to zoom. Always accepts the zoom so that |
| + * onScale() can decide whether to respond to it. |
| + */ |
| + @Override |
| + public boolean onScaleBegin(ScaleGestureDetector detector) { |
| + return true; |
| + } |
| + |
| + /** Called when the user is done zooming. Defers to onScale()'s judgement. */ |
| + @Override |
| + public void onScaleEnd(ScaleGestureDetector detector) { |
| + onScale(detector); |
| + } |
| + |
| + // -------------- Implementations of TapGestureDetector.OnTapListener ---------------- |
| + /** Called when the user taps the screen with one or more fingers. */ |
| + @Override |
| + public boolean onTap(int pointerCount, float x, float y) { |
| + TapEventParameter para = new TapEventParameter(pointerCount, x, y); |
| + mOnTap.raise(para); |
| + return para.handled; |
| + } |
| + |
| + /** Called when a long-press is triggered for one or more fingers. */ |
| + @Override |
| + public void onLongPress(int pointerCount, float x, float y) { |
| + TapEventParameter para = new TapEventParameter(pointerCount, x, y); |
| + mOnPressAndHold.raise(para); |
| + if (para.handled) { |
| + mInputState.setStartAction(StartAction.LONG_PRESS); |
| + } |
| + } |
| + |
| + /** |
| + * Returns a boolean value to indicate whether the MotionEvent is in the range of |
| + * {@link mPanGestureBounds} |
| + */ |
| + private boolean isInPanGestureBounds(MotionEvent e) { |
| + Preconditions.notNull(e); |
| + return mPanGestureBounds == null |
| + || mPanGestureBounds.contains((int) e.getX(), (int) e.getY()); |
| + } |
| + |
| + // ---------------------------- Event handlers --------------------------------------- |
| + private void handleClientSizeChanged(SizeChangedEventParameter parameter) { |
| + mPanGestureBounds = new Rect(mEdgeSlopInPx, |
| + mEdgeSlopInPx, |
| + parameter.width - mEdgeSlopInPx, |
| + parameter.height - mEdgeSlopInPx); |
| + } |
| + |
| + private void handleTouch(TouchEventParameter parameter) { |
| + mOnTouchEvent.raise(parameter.event); |
| + |
| + boolean handled = mScroller.onTouchEvent(parameter.event); |
| + handled |= mZoomer.onTouchEvent(parameter.event); |
| + handled |= mTapDetector.onTouchEvent(parameter.event); |
| + mSwipePinchDetector.onTouchEvent(parameter.event); |
| + |
| + parameter.handled = handled; |
| + } |
| +} |