Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / android / java / src / org / chromium / chromoting / SwipePinchDetector.java
blob8f3b17f517a7bb1b5298f366de76c4bf45f56e10
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.chromoting;
7 import android.content.Context;
8 import android.view.MotionEvent;
9 import android.view.ViewConfiguration;
11 /**
12 * Helper class for disambiguating whether to treat a two-finger gesture as a swipe or a pinch.
13 * Initially, the status will be unknown, until the fingers have moved sufficiently far to
14 * determine the intent.
16 public class SwipePinchDetector {
17 /** Current state of the gesture. */
18 private enum State {
19 UNKNOWN,
20 SWIPE,
21 PINCH
23 private State mState = State.UNKNOWN;
25 /** Initial coordinates of the two pointers in the current gesture. */
26 private float mFirstX0;
27 private float mFirstY0;
28 private float mFirstX1;
29 private float mFirstY1;
31 /**
32 * The initial coordinates above are valid when this flag is set. Used to determine whether a
33 * MotionEvent's pointer coordinates are the first ones of the gesture.
35 private boolean mInGesture = false;
37 /**
38 * Threshold squared-distance, in pixels, to use for motion-detection.
40 private int mTouchSlopSquare;
42 private void reset() {
43 mState = State.UNKNOWN;
44 mInGesture = false;
47 /** Construct a new detector, using the context to determine movement thresholds. */
48 public SwipePinchDetector(Context context) {
49 ViewConfiguration config = ViewConfiguration.get(context);
50 int touchSlop = config.getScaledTouchSlop();
51 mTouchSlopSquare = touchSlop * touchSlop;
54 /** Returns whether a swipe is in progress. */
55 public boolean isSwiping() {
56 return mState == State.SWIPE;
59 /** Returns whether a pinch is in progress. */
60 public boolean isPinching() {
61 return mState == State.PINCH;
64 /**
65 * Analyzes the touch event to determine whether the user is swiping or pinching. Only
66 * motion events with 2 pointers are considered here. Once the gesture is determined to be a
67 * swipe or a pinch, further 2-finger motion-events will be ignored. When a different event is
68 * passed in (motion event with != 2 pointers, or some other event type), this object will
69 * revert back to the original UNKNOWN state.
71 public void onTouchEvent(MotionEvent event) {
72 if (event.getPointerCount() != 2) {
73 reset();
74 return;
77 // Only MOVE or DOWN events are considered - all other events should finish any current
78 // gesture and reset the detector. In addition, a DOWN event should reset the detector,
79 // since it signals the start of the gesture. If the events are consistent, a DOWN event
80 // will occur at the start of the gesture, but this implementation tries to cope in case
81 // the first event is MOVE rather than DOWN.
82 int action = event.getActionMasked();
83 if (action != MotionEvent.ACTION_MOVE) {
84 reset();
85 if (action != MotionEvent.ACTION_POINTER_DOWN) {
86 return;
90 // If the gesture is known, there is no need for further processing - the state should
91 // remain the same until the gesture is complete, as tested above.
92 if (mState != State.UNKNOWN) {
93 return;
96 float currentX0 = event.getX(0);
97 float currentY0 = event.getY(0);
98 float currentX1 = event.getX(1);
99 float currentY1 = event.getY(1);
100 if (!mInGesture) {
101 // This is the first event of the gesture, so store the pointer coordinates.
102 mFirstX0 = currentX0;
103 mFirstY0 = currentY0;
104 mFirstX1 = currentX1;
105 mFirstY1 = currentY1;
106 mInGesture = true;
107 return;
110 float deltaX0 = currentX0 - mFirstX0;
111 float deltaY0 = currentY0 - mFirstY0;
112 float deltaX1 = currentX1 - mFirstX1;
113 float deltaY1 = currentY1 - mFirstY1;
115 float squaredDistance0 = deltaX0 * deltaX0 + deltaY0 * deltaY0;
116 float squaredDistance1 = deltaX1 * deltaX1 + deltaY1 * deltaY1;
119 // If both fingers have moved beyond the touch-slop, it is safe to recognize the gesture.
120 // However, one finger might be held stationary whilst the other finger is moved a long
121 // distance. In this case, it is preferable to trigger a PINCH. This should be detected
122 // soon enough to avoid triggering a sudden large change in the zoom level, but not so
123 // soon that SWIPE never gets triggered.
125 // Threshold level for triggering the PINCH gesture if one finger is stationary. This
126 // cannot be equal to the touch-slop, because in that case, SWIPE would rarely be detected.
127 // One finger would usually leave the touch-slop radius slightly before the other finger,
128 // triggering a PINCH as described above. A larger radius gives an opportunity for
129 // SWIPE to be detected. Doubling the radius is an arbitrary choice that works well.
130 int pinchThresholdSquare = 4 * mTouchSlopSquare;
132 boolean finger0Moved = squaredDistance0 > mTouchSlopSquare;
133 boolean finger1Moved = squaredDistance1 > mTouchSlopSquare;
135 if (!finger0Moved && !finger1Moved) {
136 return;
139 if (finger0Moved && !finger1Moved) {
140 if (squaredDistance0 > pinchThresholdSquare) {
141 mState = State.PINCH;
143 return;
146 if (!finger0Moved && finger1Moved) {
147 if (squaredDistance1 > pinchThresholdSquare) {
148 mState = State.PINCH;
150 return;
153 // Both fingers have moved, so determine SWIPE/PINCH status. If the fingers have moved in
154 // the same direction, this is a SWIPE, otherwise it's a PINCH. This can be measured by
155 // taking the scalar product of the direction vectors. This product is positive if the
156 // vectors are pointing in the same direction, and negative if they're in opposite
157 // directions.
158 float scalarProduct = deltaX0 * deltaX1 + deltaY0 * deltaY1;
159 mState = (scalarProduct > 0) ? State.SWIPE : State.PINCH;