blob: 847ab1a0182fb6d49401e57841d0fc7a366204f3 [file] [log] [blame]
// 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.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;
private Rect mPanGestureBounds;
InputMonitor(DesktopView view, RenderStub renderStub, Context context) {
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);
renderStub.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);
}
});
}
// -------------- 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.shouldSuppressFling()) {
return false;
}
if (mInputState.isScrollFling()) {
mInputState.setDetectedAction(DetectedAction.AFTER_SCROLL_FLING);
mOnScrollFling.raise(new TwoPointsEventParameter(e1, e2, velocityX, velocityY));
return true;
}
if (mInputState.shouldSuppressCursorMovement()) {
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 (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.shouldSuppressCursorMovement()) {
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;
}
}