1/*
2 * Copyright (C) 2016 Google Inc.
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
17package com.android.setupwizardlib.gesture;
18
19import android.graphics.Rect;
20import android.view.MotionEvent;
21import android.view.View;
22import android.view.ViewConfiguration;
23
24/**
25 * Helper class to detect the consective-tap gestures on a view.
26 *
27 * <p/>This class is instantiated and used similar to a GestureDetector, where onTouchEvent should
28 * be called when there are MotionEvents this detector should know about.
29 */
30public final class ConsecutiveTapsGestureDetector {
31
32    public interface OnConsecutiveTapsListener {
33        /**
34         * Callback method when the user tapped on the target view X number of times.
35         */
36        void onConsecutiveTaps(int numOfConsecutiveTaps);
37    }
38
39    private final View mView;
40    private final OnConsecutiveTapsListener mListener;
41    private final int mConsecutiveTapTouchSlopSquare;
42
43    private int mConsecutiveTapsCounter = 0;
44    private MotionEvent mPreviousTapEvent;
45
46    /**
47     * @param listener The listener that responds to the gesture.
48     * @param view  The target view that associated with consecutive-tap gesture.
49     */
50    public ConsecutiveTapsGestureDetector(
51            OnConsecutiveTapsListener listener,
52            View view) {
53        mListener = listener;
54        mView = view;
55        int doubleTapSlop = ViewConfiguration.get(mView.getContext()).getScaledDoubleTapSlop();
56        mConsecutiveTapTouchSlopSquare = doubleTapSlop * doubleTapSlop;
57    }
58
59    /**
60     * This method should be called from the relevant activity or view, typically in
61     * onTouchEvent, onInterceptTouchEvent or dispatchTouchEvent.
62     *
63     * @param ev The motion event
64     */
65    public void onTouchEvent(MotionEvent ev) {
66        if (ev.getAction() == MotionEvent.ACTION_UP) {
67            Rect viewRect = new Rect();
68            int[] leftTop = new int[2];
69            mView.getLocationOnScreen(leftTop);
70            viewRect.set(
71                    leftTop[0],
72                    leftTop[1],
73                    leftTop[0] + mView.getWidth(),
74                    leftTop[1] + mView.getHeight());
75            if (viewRect.contains((int) ev.getX(), (int) ev.getY())) {
76                if (isConsecutiveTap(ev)) {
77                    mConsecutiveTapsCounter++;
78                } else {
79                    mConsecutiveTapsCounter = 1;
80                }
81                mListener.onConsecutiveTaps(mConsecutiveTapsCounter);
82            } else {
83                // Touch outside the target view. Reset counter.
84                mConsecutiveTapsCounter = 0;
85            }
86
87            if (mPreviousTapEvent != null) {
88                mPreviousTapEvent.recycle();
89            }
90            mPreviousTapEvent = MotionEvent.obtain(ev);
91        }
92    }
93
94    /**
95     * Resets the consecutive-tap counter to zero.
96     */
97    public void resetCounter() {
98        mConsecutiveTapsCounter = 0;
99    }
100
101    /**
102     * Returns true if the distance between consecutive tap is within
103     * {@link #mConsecutiveTapTouchSlopSquare}. False, otherwise.
104     */
105    private boolean isConsecutiveTap(MotionEvent currentTapEvent) {
106        if (mPreviousTapEvent == null) {
107            return false;
108        }
109
110        double deltaX = mPreviousTapEvent.getX() - currentTapEvent.getX();
111        double deltaY = mPreviousTapEvent.getY() - currentTapEvent.getY();
112        return (deltaX * deltaX + deltaY * deltaY <= mConsecutiveTapTouchSlopSquare);
113    }
114}
115