Blame client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/DoubleGestureDetector.java

Packit 1fb8d4
/*
Packit 1fb8d4
   2 finger gesture detector
Packit 1fb8d4
Packit 1fb8d4
   Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
Packit 1fb8d4
Packit Service 5a9772
   This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
Packit Service 5a9772
   If a copy of the MPL was not distributed with this file, You can obtain one at
Packit Service 5a9772
   http://mozilla.org/MPL/2.0/.
Packit 1fb8d4
*/
Packit 1fb8d4
Packit 1fb8d4
package com.freerdp.freerdpcore.utils;
Packit 1fb8d4
Packit 1fb8d4
import android.content.Context;
Packit 1fb8d4
import android.os.Handler;
Packit 1fb8d4
import android.view.MotionEvent;
Packit 1fb8d4
import android.view.ScaleGestureDetector;
Packit 1fb8d4
Packit 1fb8d4
import com.freerdp.freerdpcore.utils.GestureDetector.OnGestureListener;
Packit 1fb8d4
Packit Service 5a9772
public class DoubleGestureDetector
Packit Service 5a9772
{
Packit Service 5a9772
	// timeout during that the second finger has to touch the screen before the double finger
Packit Service 5a9772
	// detection is cancelled
Packit Service 5a9772
	private static final long DOUBLE_TOUCH_TIMEOUT = 100;
Packit Service 5a9772
	// timeout during that an UP event will trigger a single double touch event
Packit Service 5a9772
	private static final long SINGLE_DOUBLE_TOUCH_TIMEOUT = 1000;
Packit Service 5a9772
	// constants for Message.what used by GestureHandler below
Packit Service 5a9772
	private static final int TAP = 1;
Packit Service 5a9772
	// different detection modes
Packit Service 5a9772
	private static final int MODE_UNKNOWN = 0;
Packit Service 5a9772
	private static final int MODE_PINCH_ZOOM = 1;
Packit Service 5a9772
	private static final int MODE_SCROLL = 2;
Packit Service 5a9772
	private static final int SCROLL_SCORE_TO_REACH = 20;
Packit Service 5a9772
	private final OnDoubleGestureListener mListener;
Packit Service 5a9772
	private int mPointerDistanceSquare;
Packit Service 5a9772
	private int mCurrentMode;
Packit Service 5a9772
	private int mScrollDetectionScore;
Packit Service 5a9772
	private ScaleGestureDetector scaleGestureDetector;
Packit Service 5a9772
	private boolean mCancelDetection;
Packit Service 5a9772
	private boolean mDoubleInProgress;
Packit Service 5a9772
	private GestureHandler mHandler;
Packit Service 5a9772
	private MotionEvent mCurrentDownEvent;
Packit Service 5a9772
	private MotionEvent mCurrentDoubleDownEvent;
Packit Service 5a9772
	private MotionEvent mPreviousUpEvent;
Packit Service 5a9772
	private MotionEvent mPreviousPointerUpEvent;
Packit Service 5a9772
	/**
Packit Service 5a9772
	 * Creates a GestureDetector with the supplied listener.
Packit Service 5a9772
	 * You may only use this constructor from a UI thread (this is the usual situation).
Packit Service 5a9772
	 *
Packit Service 5a9772
	 * @param context  the application's context
Packit Service 5a9772
	 * @param listener the listener invoked for all the callbacks, this must
Packit Service 5a9772
	 *                 not be null.
Packit Service 5a9772
	 * @throws NullPointerException if {@code listener} is null.
Packit Service 5a9772
	 * @see android.os.Handler#Handler()
Packit Service 5a9772
	 */
Packit Service 5a9772
	public DoubleGestureDetector(Context context, Handler handler, OnDoubleGestureListener listener)
Packit Service 5a9772
	{
Packit Service 5a9772
		mListener = listener;
Packit Service 5a9772
		init(context, handler);
Packit Service 5a9772
	}
Packit Service 5a9772
Packit Service 5a9772
	private void init(Context context, Handler handler)
Packit Service 5a9772
	{
Packit Service 5a9772
		if (mListener == null)
Packit Service 5a9772
		{
Packit Service 5a9772
			throw new NullPointerException("OnGestureListener must not be null");
Packit Service 5a9772
		}
Packit Service 5a9772
Packit Service 5a9772
		if (handler != null)
Packit Service 5a9772
			mHandler = new GestureHandler(handler);
Packit Service 5a9772
		else
Packit Service 5a9772
			mHandler = new GestureHandler();
Packit Service 5a9772
Packit Service 5a9772
		// we use 1cm distance to decide between scroll and pinch zoom
Packit Service 5a9772
		//  - first convert cm to inches
Packit Service 5a9772
		//  - then multiply inches by dots per inch
Packit Service 5a9772
		float distInches = 0.5f / 2.54f;
Packit Service 5a9772
		float distPixelsX = distInches * context.getResources().getDisplayMetrics().xdpi;
Packit Service 5a9772
		float distPixelsY = distInches * context.getResources().getDisplayMetrics().ydpi;
Packit Service 5a9772
Packit Service 5a9772
		mPointerDistanceSquare = (int)(distPixelsX * distPixelsX + distPixelsY * distPixelsY);
Packit Service 5a9772
	}
Packit Service 5a9772
Packit Service 5a9772
	/**
Packit Service 5a9772
	 * Set scale gesture detector
Packit Service 5a9772
	 *
Packit Service 5a9772
	 * @param scaleGestureDetector
Packit Service 5a9772
	 */
Packit Service 5a9772
	public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector)
Packit Service 5a9772
	{
Packit Service 5a9772
		this.scaleGestureDetector = scaleGestureDetector;
Packit Service 5a9772
	}
Packit Service 5a9772
Packit Service 5a9772
	/**
Packit Service 5a9772
	 * Analyzes the given motion event and if applicable triggers the
Packit Service 5a9772
	 * appropriate callbacks on the {@link OnGestureListener} supplied.
Packit Service 5a9772
	 *
Packit Service 5a9772
	 * @param ev The current motion event.
Packit Service 5a9772
	 * @return true if the {@link OnGestureListener} consumed the event,
Packit Service 5a9772
	 * else false.
Packit Service 5a9772
	 */
Packit Service 5a9772
	public boolean onTouchEvent(MotionEvent ev)
Packit Service 5a9772
	{
Packit Service 5a9772
		boolean handled = false;
Packit Service 5a9772
		final int action = ev.getAction();
Packit Service 5a9772
		// dumpEvent(ev);
Packit Service 5a9772
Packit Service 5a9772
		switch (action & MotionEvent.ACTION_MASK)
Packit Service 5a9772
		{
Packit Service 5a9772
			case MotionEvent.ACTION_DOWN:
Packit Service 5a9772
				if (mCurrentDownEvent != null)
Packit Service 5a9772
					mCurrentDownEvent.recycle();
Packit Service 5a9772
Packit Service 5a9772
				mCurrentMode = MODE_UNKNOWN;
Packit Service 5a9772
				mCurrentDownEvent = MotionEvent.obtain(ev);
Packit Service 5a9772
				mCancelDetection = false;
Packit Service 5a9772
				mDoubleInProgress = false;
Packit Service 5a9772
				mScrollDetectionScore = 0;
Packit Service 5a9772
				handled = true;
Packit Service 5a9772
				break;
Packit Service 5a9772
Packit Service 5a9772
			case MotionEvent.ACTION_POINTER_UP:
Packit Service 5a9772
				if (mPreviousPointerUpEvent != null)
Packit Service 5a9772
					mPreviousPointerUpEvent.recycle();
Packit Service 5a9772
				mPreviousPointerUpEvent = MotionEvent.obtain(ev);
Packit Service 5a9772
				break;
Packit Service 5a9772
Packit Service 5a9772
			case MotionEvent.ACTION_POINTER_DOWN:
Packit Service 5a9772
				// more than 2 fingers down? cancel
Packit Service 5a9772
				// 2nd finger touched too late? cancel
Packit Service 5a9772
				if (ev.getPointerCount() > 2 ||
Packit Service 5a9772
				    (ev.getEventTime() - mCurrentDownEvent.getEventTime()) > DOUBLE_TOUCH_TIMEOUT)
Packit Service 5a9772
				{
Packit Service 5a9772
					cancel();
Packit Service 5a9772
					break;
Packit Service 5a9772
				}
Packit Service 5a9772
Packit Service 5a9772
				// detection cancelled?
Packit Service 5a9772
				if (mCancelDetection)
Packit Service 5a9772
					break;
Packit Service 5a9772
Packit Service 5a9772
				// double touch gesture in progress
Packit Service 5a9772
				mDoubleInProgress = true;
Packit Service 5a9772
				if (mCurrentDoubleDownEvent != null)
Packit Service 5a9772
					mCurrentDoubleDownEvent.recycle();
Packit Service 5a9772
				mCurrentDoubleDownEvent = MotionEvent.obtain(ev);
Packit Service 5a9772
Packit Service 5a9772
				// set detection mode to unkown and send a TOUCH timeout event to detect single taps
Packit Service 5a9772
				mCurrentMode = MODE_UNKNOWN;
Packit Service 5a9772
				mHandler.sendEmptyMessageDelayed(TAP, SINGLE_DOUBLE_TOUCH_TIMEOUT);
Packit Service 5a9772
Packit Service 5a9772
				handled |= mListener.onDoubleTouchDown(ev);
Packit Service 5a9772
				break;
Packit Service 5a9772
Packit Service 5a9772
			case MotionEvent.ACTION_MOVE:
Packit Service 5a9772
Packit Service 5a9772
				// detection cancelled or not active?
Packit Service 5a9772
				if (mCancelDetection || !mDoubleInProgress || ev.getPointerCount() != 2)
Packit Service 5a9772
					break;
Packit Service 5a9772
Packit Service 5a9772
				// determine mode
Packit Service 5a9772
				if (mCurrentMode == MODE_UNKNOWN)
Packit Service 5a9772
				{
Packit Service 5a9772
					// did the pointer distance change?
Packit Service 5a9772
					if (pointerDistanceChanged(mCurrentDoubleDownEvent, ev))
Packit Service 5a9772
					{
Packit Service 5a9772
						handled |= scaleGestureDetector.onTouchEvent(mCurrentDownEvent);
Packit Service 5a9772
						MotionEvent e = MotionEvent.obtain(ev);
Packit Service 5a9772
						e.setAction(mCurrentDoubleDownEvent.getAction());
Packit Service 5a9772
						handled |= scaleGestureDetector.onTouchEvent(e);
Packit Service 5a9772
						mCurrentMode = MODE_PINCH_ZOOM;
Packit Service 5a9772
						break;
Packit Service 5a9772
					}
Packit Service 5a9772
					else
Packit Service 5a9772
					{
Packit Service 5a9772
						mScrollDetectionScore++;
Packit Service 5a9772
						if (mScrollDetectionScore >= SCROLL_SCORE_TO_REACH)
Packit Service 5a9772
							mCurrentMode = MODE_SCROLL;
Packit Service 5a9772
					}
Packit Service 5a9772
				}
Packit Service 5a9772
Packit Service 5a9772
				switch (mCurrentMode)
Packit Service 5a9772
				{
Packit Service 5a9772
					case MODE_PINCH_ZOOM:
Packit Service 5a9772
						if (scaleGestureDetector != null)
Packit Service 5a9772
							handled |= scaleGestureDetector.onTouchEvent(ev);
Packit Service 5a9772
						break;
Packit Service 5a9772
Packit Service 5a9772
					case MODE_SCROLL:
Packit Service 5a9772
						handled = mListener.onDoubleTouchScroll(mCurrentDownEvent, ev);
Packit Service 5a9772
						break;
Packit Service 5a9772
Packit Service 5a9772
					default:
Packit Service 5a9772
						handled = true;
Packit Service 5a9772
						break;
Packit Service 5a9772
				}
Packit Service 5a9772
Packit Service 5a9772
				break;
Packit Service 5a9772
Packit Service 5a9772
			case MotionEvent.ACTION_UP:
Packit Service 5a9772
				// fingers were not removed equally? cancel
Packit Service 5a9772
				if (mPreviousPointerUpEvent != null &&
Packit Service 5a9772
				    (ev.getEventTime() - mPreviousPointerUpEvent.getEventTime()) >
Packit Service 5a9772
				        DOUBLE_TOUCH_TIMEOUT)
Packit Service 5a9772
				{
Packit Service 5a9772
					mPreviousPointerUpEvent.recycle();
Packit Service 5a9772
					mPreviousPointerUpEvent = null;
Packit Service 5a9772
					cancel();
Packit Service 5a9772
					break;
Packit Service 5a9772
				}
Packit Service 5a9772
Packit Service 5a9772
				// detection cancelled or not active?
Packit Service 5a9772
				if (mCancelDetection || !mDoubleInProgress)
Packit Service 5a9772
					break;
Packit Service 5a9772
Packit Service 5a9772
				boolean hasTapEvent = mHandler.hasMessages(TAP);
Packit Service 5a9772
				MotionEvent currentUpEvent = MotionEvent.obtain(ev);
Packit Service 5a9772
				if (mCurrentMode == MODE_UNKNOWN && hasTapEvent)
Packit Service 5a9772
					handled = mListener.onDoubleTouchSingleTap(mCurrentDoubleDownEvent);
Packit Service 5a9772
				else if (mCurrentMode == MODE_PINCH_ZOOM)
Packit Service 5a9772
					handled = scaleGestureDetector.onTouchEvent(ev);
Packit Service 5a9772
Packit Service 5a9772
				if (mPreviousUpEvent != null)
Packit Service 5a9772
					mPreviousUpEvent.recycle();
Packit Service 5a9772
Packit Service 5a9772
				// Hold the event we obtained above - listeners may have changed the original.
Packit Service 5a9772
				mPreviousUpEvent = currentUpEvent;
Packit Service 5a9772
				handled |= mListener.onDoubleTouchUp(ev);
Packit Service 5a9772
				break;
Packit Service 5a9772
Packit Service 5a9772
			case MotionEvent.ACTION_CANCEL:
Packit Service 5a9772
				cancel();
Packit Service 5a9772
				break;
Packit Service 5a9772
		}
Packit Service 5a9772
Packit Service 5a9772
		if ((action == MotionEvent.ACTION_MOVE) && handled == false)
Packit Service 5a9772
			handled = true;
Packit Service 5a9772
Packit Service 5a9772
		return handled;
Packit Service 5a9772
	}
Packit Service 5a9772
Packit Service 5a9772
	private void cancel()
Packit Service 5a9772
	{
Packit Service 5a9772
		mHandler.removeMessages(TAP);
Packit Service 5a9772
		mCurrentMode = MODE_UNKNOWN;
Packit Service 5a9772
		mCancelDetection = true;
Packit Service 5a9772
		mDoubleInProgress = false;
Packit Service 5a9772
	}
Packit Service 5a9772
Packit Service 5a9772
	// returns true of the distance between the two pointers changed
Packit Service 5a9772
	private boolean pointerDistanceChanged(MotionEvent oldEvent, MotionEvent newEvent)
Packit Service 5a9772
	{
Packit Service 5a9772
		int deltaX1 = Math.abs((int)oldEvent.getX(0) - (int)oldEvent.getX(1));
Packit Service 5a9772
		int deltaX2 = Math.abs((int)newEvent.getX(0) - (int)newEvent.getX(1));
Packit Service 5a9772
		int distXSquare = (deltaX2 - deltaX1) * (deltaX2 - deltaX1);
Packit Service 5a9772
Packit Service 5a9772
		int deltaY1 = Math.abs((int)oldEvent.getY(0) - (int)oldEvent.getY(1));
Packit Service 5a9772
		int deltaY2 = Math.abs((int)newEvent.getY(0) - (int)newEvent.getY(1));
Packit Service 5a9772
		int distYSquare = (deltaY2 - deltaY1) * (deltaY2 - deltaY1);
Packit Service 5a9772
Packit Service 5a9772
		return (distXSquare + distYSquare) > mPointerDistanceSquare;
Packit Service 5a9772
	}
Packit Service 5a9772
Packit Service 5a9772
	/**
Packit Service 5a9772
	 * The listener that is used to notify when gestures occur.
Packit Service 5a9772
	 * If you want to listen for all the different gestures then implement
Packit Service 5a9772
	 * this interface. If you only want to listen for a subset it might
Packit Service 5a9772
	 * be easier to extend {@link SimpleOnGestureListener}.
Packit Service 5a9772
	 */
Packit Service 5a9772
	public interface OnDoubleGestureListener {
Packit Service 5a9772
Packit Service 5a9772
		/**
Packit Service 5a9772
		 * Notified when a multi tap event starts
Packit Service 5a9772
		 */
Packit Service 5a9772
		boolean onDoubleTouchDown(MotionEvent e);
Packit Service 5a9772
Packit Service 5a9772
		/**
Packit Service 5a9772
		 * Notified when a multi tap event ends
Packit Service 5a9772
		 */
Packit Service 5a9772
		boolean onDoubleTouchUp(MotionEvent e);
Packit Service 5a9772
Packit Service 5a9772
		/**
Packit Service 5a9772
		 * Notified when a tap occurs with the up {@link MotionEvent}
Packit Service 5a9772
		 * that triggered it.
Packit Service 5a9772
		 *
Packit Service 5a9772
		 * @param e The up motion event that completed the first tap
Packit Service 5a9772
		 * @return true if the event is consumed, else false
Packit Service 5a9772
		 */
Packit Service 5a9772
		boolean onDoubleTouchSingleTap(MotionEvent e);
Packit Service 5a9772
Packit Service 5a9772
		/**
Packit Service 5a9772
		 * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
Packit Service 5a9772
		 * current move {@link MotionEvent}. The distance in x and y is also supplied for
Packit Service 5a9772
		 * convenience.
Packit Service 5a9772
		 *
Packit Service 5a9772
		 * @param e1        The first down motion event that started the scrolling.
Packit Service 5a9772
		 * @param e2        The move motion event that triggered the current onScroll.
Packit Service 5a9772
		 * @param distanceX The distance along the X axis that has been scrolled since the last
Packit Service 5a9772
		 *                  call to onScroll. This is NOT the distance between {@code e1}
Packit Service 5a9772
		 *                  and {@code e2}.
Packit Service 5a9772
		 * @param distanceY The distance along the Y axis that has been scrolled since the last
Packit Service 5a9772
		 *                  call to onScroll. This is NOT the distance between {@code e1}
Packit Service 5a9772
		 *                  and {@code e2}.
Packit Service 5a9772
		 * @return true if the event is consumed, else false
Packit Service 5a9772
		 */
Packit Service 5a9772
		boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2);
Packit Service 5a9772
	}
Packit Service 5a9772
Packit Service 5a9772
	/*
Packit Service 5a9772
	private void dumpEvent(MotionEvent event) {
Packit Service 5a9772
	       String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" ,
Packit Service 5a9772
	          "POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" };
Packit Service 5a9772
	       StringBuilder sb = new StringBuilder();
Packit Service 5a9772
	       int action = event.getAction();
Packit Service 5a9772
	       int actionCode = action & MotionEvent.ACTION_MASK;
Packit Service 5a9772
	       sb.append("event ACTION_" ).append(names[actionCode]);
Packit Service 5a9772
	       if (actionCode == MotionEvent.ACTION_POINTER_DOWN
Packit Service 5a9772
	             || actionCode == MotionEvent.ACTION_POINTER_UP) {
Packit Service 5a9772
	          sb.append("(pid " ).append(
Packit Service 5a9772
	          action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
Packit Service 5a9772
	          sb.append(")" );
Packit Service 5a9772
	       }
Packit Service 5a9772
	       sb.append("[" );
Packit Service 5a9772
	       for (int i = 0; i < event.getPointerCount(); i++) {
Packit Service 5a9772
	          sb.append("#" ).append(i);
Packit Service 5a9772
	          sb.append("(pid " ).append(event.getPointerId(i));
Packit Service 5a9772
	          sb.append(")=" ).append((int) event.getX(i));
Packit Service 5a9772
	          sb.append("," ).append((int) event.getY(i));
Packit Service 5a9772
	          if (i + 1 < event.getPointerCount())
Packit Service 5a9772
	             sb.append(";" );
Packit Service 5a9772
	       }
Packit Service 5a9772
	       sb.append("]" );
Packit Service 5a9772
	       Log.d("DoubleDetector", sb.toString());
Packit Service 5a9772
	    }
Packit Service 5a9772
	*/
Packit Service 5a9772
Packit Service 5a9772
	private class GestureHandler extends Handler
Packit Service 5a9772
	{
Packit Service 5a9772
		GestureHandler()
Packit Service 5a9772
		{
Packit Service 5a9772
			super();
Packit Service 5a9772
		}
Packit Service 5a9772
Packit Service 5a9772
		GestureHandler(Handler handler)
Packit Service 5a9772
		{
Packit Service 5a9772
			super(handler.getLooper());
Packit Service 5a9772
		}
Packit Service 5a9772
	}
Packit 1fb8d4
}