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