Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / android / java / src / org / chromium / chromoting / TapGestureDetector.java
blob14cf558fac1ba1593cd31d34bfe0b3e5886ee994
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.graphics.PointF;
9 import android.os.Handler;
10 import android.os.Message;
11 import android.util.SparseArray;
12 import android.view.MotionEvent;
13 import android.view.ViewConfiguration;
15 import java.lang.ref.WeakReference;
17 /**
18 * This class detects multi-finger tap and long-press events. This is provided since the stock
19 * Android gesture-detectors only detect taps/long-presses made with one finger.
21 public class TapGestureDetector {
22 /** The listener for receiving notifications of tap gestures. */
23 public interface OnTapListener {
24 /**
25 * Notified when a tap event occurs.
27 * @param pointerCount The number of fingers that were tapped.
28 * @return True if the event is consumed.
30 boolean onTap(int pointerCount);
32 /**
33 * Notified when a long-touch event occurs.
35 * @param pointerCount The number of fingers held down.
37 void onLongPress(int pointerCount);
40 /** The listener to which notifications are sent. */
41 private OnTapListener mListener;
43 /** Handler used for posting tasks to be executed in the future. */
44 private Handler mHandler;
46 /** The maximum number of fingers seen in the gesture. */
47 private int mPointerCount = 0;
49 /**
50 * Stores the location of each down MotionEvent (by pointer ID), for detecting motion of any
51 * pointer beyond the TouchSlop region.
53 private SparseArray<PointF> mInitialPositions = new SparseArray<PointF>();
55 /**
56 * Threshold squared-distance, in pixels, to use for motion-detection. If a finger moves less
57 * than this distance, the gesture is still eligible to be a tap event.
59 private int mTouchSlopSquare;
61 /** Set to true whenever motion is detected in the gesture, or a long-touch is triggered. */
62 private boolean mTapCancelled = false;
64 // This static inner class holds a WeakReference to the outer object, to avoid triggering the
65 // lint HandlerLeak warning.
66 private static class EventHandler extends Handler {
67 private final WeakReference<TapGestureDetector> mDetector;
69 public EventHandler(TapGestureDetector detector) {
70 mDetector = new WeakReference<TapGestureDetector>(detector);
73 @Override
74 public void handleMessage(Message message) {
75 TapGestureDetector detector = mDetector.get();
76 if (detector != null) {
77 detector.mListener.onLongPress(detector.mPointerCount);
78 detector.mTapCancelled = true;
83 public TapGestureDetector(Context context, OnTapListener listener) {
84 mListener = listener;
85 mHandler = new EventHandler(this);
86 ViewConfiguration config = ViewConfiguration.get(context);
87 int touchSlop = config.getScaledTouchSlop();
88 mTouchSlopSquare = touchSlop * touchSlop;
91 /** Analyzes the touch event to determine whether to notify the listener. */
92 public boolean onTouchEvent(MotionEvent event) {
93 boolean handled = false;
94 switch (event.getActionMasked()) {
95 case MotionEvent.ACTION_DOWN:
96 reset();
97 // Cause a long-press notification to be triggered after the timeout.
98 mHandler.sendEmptyMessageDelayed(0, ViewConfiguration.getLongPressTimeout());
99 trackDownEvent(event);
100 mPointerCount = 1;
101 break;
103 case MotionEvent.ACTION_POINTER_DOWN:
104 trackDownEvent(event);
105 mPointerCount = Math.max(mPointerCount, event.getPointerCount());
106 break;
108 case MotionEvent.ACTION_MOVE:
109 if (!mTapCancelled) {
110 if (trackMoveEvent(event)) {
111 cancelLongTouchNotification();
112 mTapCancelled = true;
115 break;
117 case MotionEvent.ACTION_UP:
118 cancelLongTouchNotification();
119 if (!mTapCancelled) {
120 handled = mListener.onTap(mPointerCount);
122 break;
124 case MotionEvent.ACTION_POINTER_UP:
125 cancelLongTouchNotification();
126 trackUpEvent(event);
127 break;
129 case MotionEvent.ACTION_CANCEL:
130 cancelLongTouchNotification();
131 break;
133 default:
134 break;
136 return handled;
139 /** Stores the location of the ACTION_DOWN or ACTION_POINTER_DOWN event. */
140 private void trackDownEvent(MotionEvent event) {
141 int pointerIndex = 0;
142 if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
143 pointerIndex = event.getActionIndex();
145 int pointerId = event.getPointerId(pointerIndex);
146 mInitialPositions.put(pointerId,
147 new PointF(event.getX(pointerIndex), event.getY(pointerIndex)));
150 /** Removes the ACTION_UP or ACTION_POINTER_UP event from the stored list. */
151 private void trackUpEvent(MotionEvent event) {
152 int pointerIndex = 0;
153 if (event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
154 pointerIndex = event.getActionIndex();
156 int pointerId = event.getPointerId(pointerIndex);
157 mInitialPositions.remove(pointerId);
161 * Processes an ACTION_MOVE event and returns whether a pointer moved beyond the TouchSlop
162 * threshold.
164 * @return True if motion was detected.
166 private boolean trackMoveEvent(MotionEvent event) {
167 int pointerCount = event.getPointerCount();
168 for (int i = 0; i < pointerCount; i++) {
169 int pointerId = event.getPointerId(i);
170 float currentX = event.getX(i);
171 float currentY = event.getY(i);
172 PointF downPoint = mInitialPositions.get(pointerId);
173 if (downPoint == null) {
174 // There was no corresponding DOWN event, so add it. This is an inconsistency
175 // which shouldn't normally occur.
176 mInitialPositions.put(pointerId, new PointF(currentX, currentY));
177 continue;
179 float deltaX = currentX - downPoint.x;
180 float deltaY = currentY - downPoint.y;
181 if (deltaX * deltaX + deltaY * deltaY > mTouchSlopSquare) {
182 return true;
185 return false;
188 /** Cleans up any stored data for the gesture. */
189 private void reset() {
190 cancelLongTouchNotification();
191 mPointerCount = 0;
192 mInitialPositions.clear();
193 mTapCancelled = false;
196 /** Cancels any pending long-touch notifications from the message-queue. */
197 private void cancelLongTouchNotification() {
198 mHandler.removeMessages(0);