FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
DoubleGestureDetector.java
1/*
2 2 finger gesture detector
3
4 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5
6 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7 If a copy of the MPL was not distributed with this file, You can obtain one at
8 http://mozilla.org/MPL/2.0/.
9*/
10
11package com.freerdp.freerdpcore.utils;
12
13import android.content.Context;
14import android.os.Handler;
15import android.view.MotionEvent;
16import android.view.ScaleGestureDetector;
17
18import com.freerdp.freerdpcore.utils.GestureDetector.OnGestureListener;
19
21{
22 // timeout during that the second finger has to touch the screen before the double finger
23 // detection is cancelled
24 private static final long DOUBLE_TOUCH_TIMEOUT = 100;
25 // timeout during that an UP event will trigger a single double touch event
26 private static final long SINGLE_DOUBLE_TOUCH_TIMEOUT = 1000;
27 // constants for Message.what used by GestureHandler below
28 private static final int TAP = 1;
29 // different detection modes
30 private static final int MODE_UNKNOWN = 0;
31 private static final int MODE_PINCH_ZOOM = 1;
32 private static final int MODE_SCROLL = 2;
33 private static final int SCROLL_SCORE_TO_REACH = 20;
34 private final OnDoubleGestureListener mListener;
35 private int mPointerDistanceSquare;
36 private int mCurrentMode;
37 private int mScrollDetectionScore;
38 private ScaleGestureDetector scaleGestureDetector;
39 private boolean mCancelDetection;
40 private boolean mDoubleInProgress;
41 private GestureHandler mHandler;
42 private MotionEvent mCurrentDownEvent;
43 private MotionEvent mCurrentDoubleDownEvent;
44 private MotionEvent mPreviousUpEvent;
45 private MotionEvent mPreviousPointerUpEvent;
56 public DoubleGestureDetector(Context context, Handler handler, OnDoubleGestureListener listener)
57 {
58 mListener = listener;
59 init(context, handler);
60 }
61
62 private void init(Context context, Handler handler)
63 {
64 if (mListener == null)
65 {
66 throw new NullPointerException("OnGestureListener must not be null");
67 }
68
69 if (handler != null)
70 mHandler = new GestureHandler(handler);
71 else
72 mHandler = new GestureHandler();
73
74 // we use 1cm distance to decide between scroll and pinch zoom
75 // - first convert cm to inches
76 // - then multiply inches by dots per inch
77 float distInches = 0.5f / 2.54f;
78 float distPixelsX = distInches * context.getResources().getDisplayMetrics().xdpi;
79 float distPixelsY = distInches * context.getResources().getDisplayMetrics().ydpi;
80
81 mPointerDistanceSquare = (int)(distPixelsX * distPixelsX + distPixelsY * distPixelsY);
82 }
83
89 public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector)
90 {
91 this.scaleGestureDetector = scaleGestureDetector;
92 }
93
102 public boolean onTouchEvent(MotionEvent ev)
103 {
104 boolean handled = false;
105 final int action = ev.getAction();
106 // dumpEvent(ev);
107
108 switch (action & MotionEvent.ACTION_MASK)
109 {
110 case MotionEvent.ACTION_DOWN:
111 if (mCurrentDownEvent != null)
112 mCurrentDownEvent.recycle();
113
114 mCurrentMode = MODE_UNKNOWN;
115 mCurrentDownEvent = MotionEvent.obtain(ev);
116 mCancelDetection = false;
117 mDoubleInProgress = false;
118 mScrollDetectionScore = 0;
119 handled = true;
120 break;
121
122 case MotionEvent.ACTION_POINTER_UP:
123 if (mPreviousPointerUpEvent != null)
124 mPreviousPointerUpEvent.recycle();
125 mPreviousPointerUpEvent = MotionEvent.obtain(ev);
126 break;
127
128 case MotionEvent.ACTION_POINTER_DOWN:
129 // more than 2 fingers down? cancel
130 // 2nd finger touched too late? cancel
131 if (ev.getPointerCount() > 2 ||
132 (ev.getEventTime() - mCurrentDownEvent.getEventTime()) > DOUBLE_TOUCH_TIMEOUT)
133 {
134 cancel();
135 break;
136 }
137
138 // detection cancelled?
139 if (mCancelDetection)
140 break;
141
142 // double touch gesture in progress
143 mDoubleInProgress = true;
144 if (mCurrentDoubleDownEvent != null)
145 mCurrentDoubleDownEvent.recycle();
146 mCurrentDoubleDownEvent = MotionEvent.obtain(ev);
147
148 // set detection mode to unknown and send a TOUCH timeout event to detect single
149 // taps
150 mCurrentMode = MODE_UNKNOWN;
151 mHandler.sendEmptyMessageDelayed(TAP, SINGLE_DOUBLE_TOUCH_TIMEOUT);
152
153 handled |= mListener.onDoubleTouchDown(ev);
154 break;
155
156 case MotionEvent.ACTION_MOVE:
157
158 // detection cancelled or not active?
159 if (mCancelDetection || !mDoubleInProgress || ev.getPointerCount() != 2)
160 break;
161
162 // determine mode
163 if (mCurrentMode == MODE_UNKNOWN)
164 {
165 // did the pointer distance change?
166 if (pointerDistanceChanged(mCurrentDoubleDownEvent, ev))
167 {
168 handled |= scaleGestureDetector.onTouchEvent(mCurrentDownEvent);
169 MotionEvent e = MotionEvent.obtain(ev);
170 e.setAction(mCurrentDoubleDownEvent.getAction());
171 handled |= scaleGestureDetector.onTouchEvent(e);
172 mCurrentMode = MODE_PINCH_ZOOM;
173 break;
174 }
175 else
176 {
177 mScrollDetectionScore++;
178 if (mScrollDetectionScore >= SCROLL_SCORE_TO_REACH)
179 mCurrentMode = MODE_SCROLL;
180 }
181 }
182
183 switch (mCurrentMode)
184 {
185 case MODE_PINCH_ZOOM:
186 if (scaleGestureDetector != null)
187 handled |= scaleGestureDetector.onTouchEvent(ev);
188 break;
189
190 case MODE_SCROLL:
191 handled = mListener.onDoubleTouchScroll(mCurrentDownEvent, ev);
192 break;
193
194 default:
195 handled = true;
196 break;
197 }
198
199 break;
200
201 case MotionEvent.ACTION_UP:
202 // fingers were not removed equally? cancel
203 if (mPreviousPointerUpEvent != null &&
204 (ev.getEventTime() - mPreviousPointerUpEvent.getEventTime()) >
205 DOUBLE_TOUCH_TIMEOUT)
206 {
207 mPreviousPointerUpEvent.recycle();
208 mPreviousPointerUpEvent = null;
209 cancel();
210 break;
211 }
212
213 // detection cancelled or not active?
214 if (mCancelDetection || !mDoubleInProgress)
215 break;
216
217 boolean hasTapEvent = mHandler.hasMessages(TAP);
218 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
219 if (mCurrentMode == MODE_UNKNOWN && hasTapEvent)
220 handled = mListener.onDoubleTouchSingleTap(mCurrentDoubleDownEvent);
221 else if (mCurrentMode == MODE_PINCH_ZOOM)
222 handled = scaleGestureDetector.onTouchEvent(ev);
223
224 if (mPreviousUpEvent != null)
225 mPreviousUpEvent.recycle();
226
227 // Hold the event we obtained above - listeners may have changed the original.
228 mPreviousUpEvent = currentUpEvent;
229 handled |= mListener.onDoubleTouchUp(ev);
230 break;
231
232 case MotionEvent.ACTION_CANCEL:
233 cancel();
234 break;
235 }
236
237 if ((action == MotionEvent.ACTION_MOVE) && !handled)
238 handled = true;
239
240 return handled;
241 }
242
243 private void cancel()
244 {
245 mHandler.removeMessages(TAP);
246 mCurrentMode = MODE_UNKNOWN;
247 mCancelDetection = true;
248 mDoubleInProgress = false;
249 }
250
251 // returns true of the distance between the two pointers changed
252 private boolean pointerDistanceChanged(MotionEvent oldEvent, MotionEvent newEvent)
253 {
254 int deltaX1 = Math.abs((int)oldEvent.getX(0) - (int)oldEvent.getX(1));
255 int deltaX2 = Math.abs((int)newEvent.getX(0) - (int)newEvent.getX(1));
256 int distXSquare = (deltaX2 - deltaX1) * (deltaX2 - deltaX1);
257
258 int deltaY1 = Math.abs((int)oldEvent.getY(0) - (int)oldEvent.getY(1));
259 int deltaY2 = Math.abs((int)newEvent.getY(0) - (int)newEvent.getY(1));
260 int distYSquare = (deltaY2 - deltaY1) * (deltaY2 - deltaY1);
261
262 return (distXSquare + distYSquare) > mPointerDistanceSquare;
263 }
264
271 public interface OnDoubleGestureListener {
272
276 boolean onDoubleTouchDown(MotionEvent e);
277
281 boolean onDoubleTouchUp(MotionEvent e);
282
290 boolean onDoubleTouchSingleTap(MotionEvent e);
291
307 boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2);
308 }
309
310 /*
311 private void dumpEvent(MotionEvent event) {
312 String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" ,
313 "POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" };
314 StringBuilder sb = new StringBuilder();
315 int action = event.getAction();
316 int actionCode = action & MotionEvent.ACTION_MASK;
317 sb.append("event ACTION_" ).append(names[actionCode]);
318 if (actionCode == MotionEvent.ACTION_POINTER_DOWN
319 || actionCode == MotionEvent.ACTION_POINTER_UP) {
320 sb.append("(pid " ).append(
321 action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
322 sb.append(")" );
323 }
324 sb.append("[" );
325 for (int i = 0; i < event.getPointerCount(); i++) {
326 sb.append("#" ).append(i);
327 sb.append("(pid " ).append(event.getPointerId(i));
328 sb.append(")=" ).append((int) event.getX(i));
329 sb.append("," ).append((int) event.getY(i));
330 if (i + 1 < event.getPointerCount())
331 sb.append(";" );
332 }
333 sb.append("]" );
334 Log.d("DoubleDetector", sb.toString());
335 }
336 */
337
338 private class GestureHandler extends Handler
339 {
340 GestureHandler()
341 {
342 super();
343 }
344
345 GestureHandler(Handler handler)
346 {
347 super(handler.getLooper());
348 }
349 }
350}
void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector)
DoubleGestureDetector(Context context, Handler handler, OnDoubleGestureListener listener)