/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.phone; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import com.android.systemui.R; /** * Detects a double tap. */ public class DoubleTapHelper { private static final long DOUBLETAP_TIMEOUT_MS = 1200; private final View mView; private final ActivationListener mActivationListener; private final DoubleTapListener mDoubleTapListener; private final SlideBackListener mSlideBackListener; private final DoubleTapLogListener mDoubleTapLogListener; private float mTouchSlop; private float mDoubleTapSlop; private boolean mActivated; private float mDownX; private float mDownY; private boolean mTrackTouch; private float mActivationX; private float mActivationY; private Runnable mTapTimeoutRunnable = this::makeInactive; public DoubleTapHelper(View view, ActivationListener activationListener, DoubleTapListener doubleTapListener, SlideBackListener slideBackListener, DoubleTapLogListener doubleTapLogListener) { mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop(); mDoubleTapSlop = view.getResources().getDimension(R.dimen.double_tap_slop); mView = view; mActivationListener = activationListener; mDoubleTapListener = doubleTapListener; mSlideBackListener = slideBackListener; mDoubleTapLogListener = doubleTapLogListener; } public boolean onTouchEvent(MotionEvent event) { return onTouchEvent(event, Integer.MAX_VALUE); } public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) { int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: mDownX = event.getX(); mDownY = event.getY(); mTrackTouch = true; if (mDownY > maxTouchableHeight) { mTrackTouch = false; } break; case MotionEvent.ACTION_MOVE: if (!isWithinTouchSlop(event)) { makeInactive(); mTrackTouch = false; } break; case MotionEvent.ACTION_UP: if (isWithinTouchSlop(event)) { if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) { return true; } if (!mActivated) { makeActive(); mView.postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS); mActivationX = event.getX(); mActivationY = event.getY(); } else { boolean withinDoubleTapSlop = isWithinDoubleTapSlop(event); if (mDoubleTapLogListener != null) { mDoubleTapLogListener.onDoubleTapLog(withinDoubleTapSlop, event.getX() - mActivationX, event.getY() - mActivationY); } if (withinDoubleTapSlop) { if (!mDoubleTapListener.onDoubleTap()) { return false; } } else { makeInactive(); mTrackTouch = false; } } } else { makeInactive(); mTrackTouch = false; } break; case MotionEvent.ACTION_CANCEL: makeInactive(); mTrackTouch = false; break; default: break; } return mTrackTouch; } private void makeActive() { if (!mActivated) { mActivated = true; mActivationListener.onActiveChanged(true); } } private void makeInactive() { if (mActivated) { mActivated = false; mActivationListener.onActiveChanged(false); } } private boolean isWithinTouchSlop(MotionEvent event) { return Math.abs(event.getX() - mDownX) < mTouchSlop && Math.abs(event.getY() - mDownY) < mTouchSlop; } private boolean isWithinDoubleTapSlop(MotionEvent event) { if (!mActivated) { // If we're not activated there's no double tap slop to satisfy. return true; } return Math.abs(event.getX() - mActivationX) < mDoubleTapSlop && Math.abs(event.getY() - mActivationY) < mDoubleTapSlop; } @FunctionalInterface public interface ActivationListener { void onActiveChanged(boolean active); } @FunctionalInterface public interface DoubleTapListener { boolean onDoubleTap(); } @FunctionalInterface public interface SlideBackListener { boolean onSlideBack(); } @FunctionalInterface public interface DoubleTapLogListener { void onDoubleTapLog(boolean accepted, float dx, float dy); } }