FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
GestureDetector.java
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * Modified for aFreeRDP by Martin Fleisz (martin.fleisz@thincast.com)
17 */
18
19package com.freerdp.freerdpcore.utils;
20
21import android.content.Context;
22import android.os.Build;
23import android.os.Handler;
24import android.os.Message;
25import android.util.DisplayMetrics;
26import android.view.MotionEvent;
27import android.view.ViewConfiguration;
28
29public class GestureDetector
30{
31
32 private static final int TAP_TIMEOUT = 100;
33 private static final int DOUBLE_TAP_TIMEOUT = 200;
34 // Distance a touch can wander before we think the user is the first touch in a sequence of
35 // double tap
36 private static final int LARGE_TOUCH_SLOP = 18;
37 // Distance between the first touch and second touch to still be considered a double tap
38 private static final int DOUBLE_TAP_SLOP = 100;
39 // constants for Message.what used by GestureHandler below
40 private static final int SHOW_PRESS = 1;
41 private static final int LONG_PRESS = 2;
42 private static final int TAP = 3;
43 private final Handler mHandler;
44 private final OnGestureListener mListener;
45 private int mTouchSlopSquare;
46 private int mLargeTouchSlopSquare;
47 private int mDoubleTapSlopSquare;
48 private int mLongpressTimeout = 100;
49 private OnDoubleTapListener mDoubleTapListener;
50 private boolean mStillDown;
51 private boolean mInLongPress;
52 private boolean mAlwaysInTapRegion;
53 private boolean mAlwaysInBiggerTapRegion;
54 private MotionEvent mCurrentDownEvent;
55 private MotionEvent mPreviousUpEvent;
60 private boolean mIsDoubleTapping;
61 private float mLastMotionY;
62 private float mLastMotionX;
63 private boolean mIsLongpressEnabled;
69 private boolean mIgnoreMultitouch;
80 public GestureDetector(Context context, OnGestureListener listener)
81 {
82 this(context, listener, null);
83 }
84
96 public GestureDetector(Context context, OnGestureListener listener, Handler handler)
97 {
98 this(context, listener, handler,
99 context != null &&
100 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO);
101 }
102
116 public GestureDetector(Context context, OnGestureListener listener, Handler handler,
117 boolean ignoreMultitouch)
118 {
119 if (handler != null)
120 {
121 mHandler = new GestureHandler(handler);
122 }
123 else
124 {
125 mHandler = new GestureHandler();
126 }
127 mListener = listener;
128 if (listener instanceof OnDoubleTapListener)
129 {
131 }
132 init(context, ignoreMultitouch);
133 }
134
135 private void init(Context context, boolean ignoreMultitouch)
136 {
137 if (mListener == null)
138 {
139 throw new NullPointerException("OnGestureListener must not be null");
140 }
141 mIsLongpressEnabled = true;
142 mIgnoreMultitouch = ignoreMultitouch;
143
144 // Fallback to support pre-donuts releases
145 int touchSlop, largeTouchSlop, doubleTapSlop;
146 if (context == null)
147 {
148 // noinspection deprecation
149 touchSlop = ViewConfiguration.getTouchSlop();
150 largeTouchSlop = touchSlop + 2;
151 doubleTapSlop = DOUBLE_TAP_SLOP;
152 }
153 else
154 {
155 final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
156 final float density = metrics.density;
157 final ViewConfiguration configuration = ViewConfiguration.get(context);
158 touchSlop = configuration.getScaledTouchSlop();
159 largeTouchSlop = (int)(density * LARGE_TOUCH_SLOP + 0.5f);
160 doubleTapSlop = configuration.getScaledDoubleTapSlop();
161 }
162 mTouchSlopSquare = touchSlop * touchSlop;
163 mLargeTouchSlopSquare = largeTouchSlop * largeTouchSlop;
164 mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
165 }
166
174 public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)
175 {
176 mDoubleTapListener = onDoubleTapListener;
177 }
178
189 {
190 mIsLongpressEnabled = isLongpressEnabled;
191 }
192
196 public boolean isLongpressEnabled()
197 {
198 return mIsLongpressEnabled;
199 }
200
201 public void setLongPressTimeout(int timeout)
202 {
203 mLongpressTimeout = timeout;
204 }
205
214 public boolean onTouchEvent(MotionEvent ev)
215 {
216 final int action = ev.getAction();
217 final float y = ev.getY();
218 final float x = ev.getX();
219
220 boolean handled = false;
221
222 switch (action & MotionEvent.ACTION_MASK)
223 {
224 case MotionEvent.ACTION_POINTER_DOWN:
225 if (mIgnoreMultitouch)
226 {
227 // Multitouch event - abort.
228 cancel();
229 }
230 break;
231
232 case MotionEvent.ACTION_POINTER_UP:
233 // Ending a multitouch gesture and going back to 1 finger
234 if (mIgnoreMultitouch && ev.getPointerCount() == 2)
235 {
236 int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
237 MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0)
238 ? 1
239 : 0;
240 mLastMotionX = ev.getX(index);
241 mLastMotionY = ev.getY(index);
242 }
243 break;
244
245 case MotionEvent.ACTION_DOWN:
246 if (mDoubleTapListener != null)
247 {
248 boolean hadTapMessage = mHandler.hasMessages(TAP);
249 if (hadTapMessage)
250 mHandler.removeMessages(TAP);
251 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) &&
252 hadTapMessage &&
253 isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev))
254 {
255 // This is a second tap
256 mIsDoubleTapping = true;
257 // Give a callback with the first tap of the double-tap
258 handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
259 // Give a callback with down event of the double-tap
260 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
261 }
262 else
263 {
264 // This is a first tap
265 mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
266 }
267 }
268
269 mLastMotionX = x;
270 mLastMotionY = y;
271 if (mCurrentDownEvent != null)
272 {
273 mCurrentDownEvent.recycle();
274 }
275 mCurrentDownEvent = MotionEvent.obtain(ev);
276 mAlwaysInTapRegion = true;
277 mAlwaysInBiggerTapRegion = true;
278 mStillDown = true;
279 mInLongPress = false;
280
281 if (mIsLongpressEnabled)
282 {
283 mHandler.removeMessages(LONG_PRESS);
284 mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() +
285 TAP_TIMEOUT +
286 mLongpressTimeout);
287 }
288 mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
289 mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
290 handled |= mListener.onDown(ev);
291 break;
292
293 case MotionEvent.ACTION_MOVE:
294 if (mIgnoreMultitouch && ev.getPointerCount() > 1)
295 {
296 break;
297 }
298 final float scrollX = mLastMotionX - x;
299 final float scrollY = mLastMotionY - y;
300 if (mIsDoubleTapping)
301 {
302 // Give the move events of the double-tap
303 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
304 }
305 else if (mAlwaysInTapRegion)
306 {
307 final int deltaX = (int)(x - mCurrentDownEvent.getX());
308 final int deltaY = (int)(y - mCurrentDownEvent.getY());
309 int distance = (deltaX * deltaX) + (deltaY * deltaY);
310 if (distance > mTouchSlopSquare)
311 {
312 mLastMotionX = x;
313 mLastMotionY = y;
314 mAlwaysInTapRegion = false;
315 mHandler.removeMessages(TAP);
316 mHandler.removeMessages(SHOW_PRESS);
317 mHandler.removeMessages(LONG_PRESS);
318 }
319 if (distance > mLargeTouchSlopSquare)
320 {
321 mAlwaysInBiggerTapRegion = false;
322 }
323 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
324 }
325 else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1))
326 {
327 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
328 mLastMotionX = x;
329 mLastMotionY = y;
330 }
331 break;
332
333 case MotionEvent.ACTION_UP:
334 mStillDown = false;
335 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
336 if (mIsDoubleTapping)
337 {
338 // Finally, give the up event of the double-tap
339 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
340 }
341 else if (mInLongPress)
342 {
343 mHandler.removeMessages(TAP);
344 mListener.onLongPressUp(ev);
345 mInLongPress = false;
346 }
347 else if (mAlwaysInTapRegion)
348 {
349 handled = mListener.onSingleTapUp(mCurrentDownEvent);
350 }
351 else
352 {
353 // A fling must travel the minimum tap distance
354 }
355 if (mPreviousUpEvent != null)
356 {
357 mPreviousUpEvent.recycle();
358 }
359 // Hold the event we obtained above - listeners may have changed the original.
360 mPreviousUpEvent = currentUpEvent;
361 mIsDoubleTapping = false;
362 mHandler.removeMessages(SHOW_PRESS);
363 mHandler.removeMessages(LONG_PRESS);
364 handled |= mListener.onUp(ev);
365 break;
366 case MotionEvent.ACTION_CANCEL:
367 cancel();
368 break;
369 }
370 return handled;
371 }
372
373 private void cancel()
374 {
375 mHandler.removeMessages(SHOW_PRESS);
376 mHandler.removeMessages(LONG_PRESS);
377 mHandler.removeMessages(TAP);
378 mAlwaysInTapRegion = false; // ensures that we won't receive an OnSingleTap notification
379 // when a 2-Finger tap is performed
380 mIsDoubleTapping = false;
381 mStillDown = false;
382 if (mInLongPress)
383 {
384 mInLongPress = false;
385 }
386 }
387
388 private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
389 MotionEvent secondDown)
390 {
391 if (!mAlwaysInBiggerTapRegion)
392 {
393 return false;
394 }
395
396 if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT)
397 {
398 return false;
399 }
400
401 int deltaX = (int)firstDown.getX() - (int)secondDown.getX();
402 int deltaY = (int)firstDown.getY() - (int)secondDown.getY();
403 return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
404 }
405
406 private void dispatchLongPress()
407 {
408 mHandler.removeMessages(TAP);
409 mInLongPress = true;
410 mListener.onLongPress(mCurrentDownEvent);
411 }
412
419 public interface OnGestureListener {
420
428 boolean onDown(MotionEvent e);
429
437 boolean onUp(MotionEvent e);
438
447 void onShowPress(MotionEvent e);
448
456 boolean onSingleTapUp(MotionEvent e);
457
473 boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
474
481 void onLongPress(MotionEvent e);
482
488 void onLongPressUp(MotionEvent e);
489 }
490
495 public interface OnDoubleTapListener {
507 boolean onSingleTapConfirmed(MotionEvent e);
508
515 boolean onDoubleTap(MotionEvent e);
516
524 boolean onDoubleTapEvent(MotionEvent e);
525 }
526
534 {
535 public boolean onSingleTapUp(MotionEvent e)
536 {
537 return false;
538 }
539
540 public void onLongPress(MotionEvent e)
541 {
542 }
543
544 public void onLongPressUp(MotionEvent e)
545 {
546 }
547
548 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
549 {
550 return false;
551 }
552
553 public void onShowPress(MotionEvent e)
554 {
555 }
556
557 public boolean onDown(MotionEvent e)
558 {
559 return false;
560 }
561
562 public boolean onUp(MotionEvent e)
563 {
564 return false;
565 }
566
567 public boolean onDoubleTap(MotionEvent e)
568 {
569 return false;
570 }
571
572 public boolean onDoubleTapEvent(MotionEvent e)
573 {
574 return false;
575 }
576
577 public boolean onSingleTapConfirmed(MotionEvent e)
578 {
579 return false;
580 }
581 }
582
583 private class GestureHandler extends Handler
584 {
585 GestureHandler()
586 {
587 super();
588 }
589
590 GestureHandler(Handler handler)
591 {
592 super(handler.getLooper());
593 }
594
595 @Override public void handleMessage(Message msg)
596 {
597 switch (msg.what)
598 {
599 case SHOW_PRESS:
600 mListener.onShowPress(mCurrentDownEvent);
601 break;
602
603 case LONG_PRESS:
604 dispatchLongPress();
605 break;
606
607 case TAP:
608 // If the user's finger is still down, do not count it as a tap
609 if (mDoubleTapListener != null && !mStillDown)
610 {
611 mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
612 }
613 break;
614
615 default:
616 throw new RuntimeException("Unknown message " + msg); // never
617 }
618 }
619 }
620}
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)
void setIsLongpressEnabled(boolean isLongpressEnabled)
GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean ignoreMultitouch)
GestureDetector(Context context, OnGestureListener listener)
GestureDetector(Context context, OnGestureListener listener, Handler handler)
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)