Upstream version 5.34.98.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / LongPressDetector.java
1 // Copyright 2012 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.
4
5 package org.chromium.content.browser;
6
7 import android.content.Context;
8 import android.os.Handler;
9 import android.os.Message;
10 import android.view.MotionEvent;
11 import android.view.ViewConfiguration;
12
13 import java.util.Iterator;
14
15 /**
16  * This class controls long press timers and is owned by a ContentViewGestureHandler.
17  *
18  * For instance, we may receive a DOWN then UP, so we may need to cancel the
19  * timer before the UP completes its roundtrip from WebKit.
20  */
21 class LongPressDetector {
22     private MotionEvent mCurrentDownEvent;
23     private final LongPressDelegate mLongPressDelegate;
24     private final Handler mLongPressHandler;
25     private final int mTouchSlopSquare;
26     private boolean mInLongPress;
27
28     private static final int LONG_PRESS = 2;
29
30     private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
31     private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
32
33     LongPressDetector(Context context, LongPressDelegate delegate) {
34         mLongPressDelegate = delegate;
35         mLongPressHandler = new LongPressHandler();
36         final ViewConfiguration configuration = ViewConfiguration.get(context);
37         int touchSlop = configuration.getScaledTouchSlop();
38         mTouchSlopSquare = touchSlop * touchSlop;
39     }
40
41     private class LongPressHandler extends Handler {
42         LongPressHandler() {
43             super();
44         }
45
46         @Override
47         public void handleMessage(Message msg) {
48             switch (msg.what) {
49                 case LONG_PRESS:
50                     dispatchLongPress();
51                     break;
52                 default:
53                     throw new RuntimeException("Unknown message " + msg); // never
54             }
55         }
56     }
57
58     /**
59      * This is an interface to execute the LongPress when it receives the onLongPress message.
60      */
61     interface LongPressDelegate {
62         /**
63          * @param event The event will be recycled after this call has returned.
64          */
65         public void onLongPress(MotionEvent event);
66     }
67
68     private static long calculateLongPressTimeoutTime(MotionEvent ev) {
69         // Using getEventTime instead of getDownTime since for Android WebView,
70         // event stream can be arbitrarily delayed.
71         return ev.getEventTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT;
72     }
73
74     /**
75      * Initiates a LONG_PRESS gesture timer if needed.
76      */
77     void startLongPressTimerIfNeeded(MotionEvent ev) {
78         if (ev.getAction() != MotionEvent.ACTION_DOWN) return;
79
80         // If there is a current down, we do not expect another down event before
81         // receiving an up event
82         if (mCurrentDownEvent != null) return;
83
84         mCurrentDownEvent = MotionEvent.obtain(ev);
85         mLongPressHandler.sendEmptyMessageAtTime(LONG_PRESS,
86                 calculateLongPressTimeoutTime(mCurrentDownEvent));
87         mInLongPress = false;
88     }
89
90     // Cancel LONG_PRESS timers.
91     void cancelLongPressIfNeeded(MotionEvent ev) {
92         if (!hasPendingMessage() ||
93             mCurrentDownEvent == null || ev.getDownTime() != mCurrentDownEvent.getDownTime()) {
94             return;
95         }
96         final int action = ev.getAction();
97         final float y = ev.getY();
98         final float x = ev.getX();
99         switch (action & MotionEvent.ACTION_MASK) {
100             case MotionEvent.ACTION_MOVE:
101                 final int deltaX = (int) (x - mCurrentDownEvent.getX());
102                 final int deltaY = (int) (y - mCurrentDownEvent.getY());
103                 int distance = (deltaX * deltaX) + (deltaY * deltaY);
104                 if (distance > mTouchSlopSquare) {
105                     cancelLongPress();
106                 }
107                 break;
108             case MotionEvent.ACTION_UP:
109             case MotionEvent.ACTION_CANCEL:
110                 if (calculateLongPressTimeoutTime(mCurrentDownEvent) > ev.getEventTime()) {
111                     cancelLongPress();
112                 }
113                 break;
114             default:
115                 break;
116         }
117     }
118
119     // Given a stream of pending events, cancel the LONG_PRESS timer if appropriate.
120     void cancelLongPressIfNeeded(Iterator<MotionEvent> pendingEvents) {
121         if (mCurrentDownEvent == null)
122             return;
123         long currentDownTime = mCurrentDownEvent.getDownTime();
124         while (pendingEvents.hasNext()) {
125             MotionEvent pending = pendingEvents.next();
126             if (pending.getDownTime() != currentDownTime) {
127                 break;
128             }
129             cancelLongPressIfNeeded(pending);
130         }
131     }
132
133     void cancelLongPress() {
134         mInLongPress = false;
135         if (hasPendingMessage()) {
136             mLongPressHandler.removeMessages(LONG_PRESS);
137             mCurrentDownEvent.recycle();
138             mCurrentDownEvent = null;
139         }
140     }
141
142     // Used this to check if a onSingleTapUp is part of a long press event.
143     boolean isInLongPress() {
144         return mInLongPress;
145     }
146
147     private void dispatchLongPress() {
148         mInLongPress = true;
149         mLongPressDelegate.onLongPress(mCurrentDownEvent);
150         mCurrentDownEvent.recycle();
151         mCurrentDownEvent = null;
152     }
153
154     boolean hasPendingMessage() {
155         return mCurrentDownEvent != null;
156     }
157
158     /**
159      * This is for testing only.
160      * Sends a LongPress gesture. This should always be called after a down event.
161      */
162     void sendLongPressGestureForTest() {
163         if (mCurrentDownEvent == null) return;
164         dispatchLongPress();
165     }
166 }