1 /* 2 * Copyright (C) 2017 The Android Open Source Project 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 17 package com.android.systemui.statusbar.phone; 18 19 import android.view.MotionEvent; 20 import android.view.View; 21 import android.view.ViewConfiguration; 22 23 import com.android.systemui.R; 24 25 /** 26 * Detects a double tap. 27 */ 28 public class DoubleTapHelper { 29 30 private static final long DOUBLETAP_TIMEOUT_MS = 1200; 31 32 private final View mView; 33 private final ActivationListener mActivationListener; 34 private final DoubleTapListener mDoubleTapListener; 35 private final SlideBackListener mSlideBackListener; 36 private final DoubleTapLogListener mDoubleTapLogListener; 37 38 private float mTouchSlop; 39 private float mDoubleTapSlop; 40 41 private boolean mActivated; 42 43 private float mDownX; 44 private float mDownY; 45 private boolean mTrackTouch; 46 47 private float mActivationX; 48 private float mActivationY; 49 private Runnable mTapTimeoutRunnable = this::makeInactive; 50 DoubleTapHelper(View view, ActivationListener activationListener, DoubleTapListener doubleTapListener, SlideBackListener slideBackListener, DoubleTapLogListener doubleTapLogListener)51 public DoubleTapHelper(View view, ActivationListener activationListener, 52 DoubleTapListener doubleTapListener, SlideBackListener slideBackListener, 53 DoubleTapLogListener doubleTapLogListener) { 54 mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop(); 55 mDoubleTapSlop = view.getResources().getDimension(R.dimen.double_tap_slop); 56 mView = view; 57 58 mActivationListener = activationListener; 59 mDoubleTapListener = doubleTapListener; 60 mSlideBackListener = slideBackListener; 61 mDoubleTapLogListener = doubleTapLogListener; 62 } 63 onTouchEvent(MotionEvent event)64 public boolean onTouchEvent(MotionEvent event) { 65 return onTouchEvent(event, Integer.MAX_VALUE); 66 } 67 onTouchEvent(MotionEvent event, int maxTouchableHeight)68 public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) { 69 int action = event.getActionMasked(); 70 switch (action) { 71 case MotionEvent.ACTION_DOWN: 72 mDownX = event.getX(); 73 mDownY = event.getY(); 74 mTrackTouch = true; 75 if (mDownY > maxTouchableHeight) { 76 mTrackTouch = false; 77 } 78 break; 79 case MotionEvent.ACTION_MOVE: 80 if (!isWithinTouchSlop(event)) { 81 makeInactive(); 82 mTrackTouch = false; 83 } 84 break; 85 case MotionEvent.ACTION_UP: 86 if (isWithinTouchSlop(event)) { 87 if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) { 88 return true; 89 } 90 if (!mActivated) { 91 makeActive(); 92 mView.postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS); 93 mActivationX = event.getX(); 94 mActivationY = event.getY(); 95 } else { 96 boolean withinDoubleTapSlop = isWithinDoubleTapSlop(event); 97 if (mDoubleTapLogListener != null) { 98 mDoubleTapLogListener.onDoubleTapLog(withinDoubleTapSlop, 99 event.getX() - mActivationX, 100 event.getY() - mActivationY); 101 } 102 if (withinDoubleTapSlop) { 103 if (!mDoubleTapListener.onDoubleTap()) { 104 return false; 105 } 106 } else { 107 makeInactive(); 108 mTrackTouch = false; 109 } 110 } 111 } else { 112 makeInactive(); 113 mTrackTouch = false; 114 } 115 break; 116 case MotionEvent.ACTION_CANCEL: 117 makeInactive(); 118 mTrackTouch = false; 119 break; 120 default: 121 break; 122 } 123 return mTrackTouch; 124 } 125 makeActive()126 private void makeActive() { 127 if (!mActivated) { 128 mActivated = true; 129 mActivationListener.onActiveChanged(true); 130 } 131 } 132 makeInactive()133 private void makeInactive() { 134 if (mActivated) { 135 mActivated = false; 136 mActivationListener.onActiveChanged(false); 137 } 138 } 139 isWithinTouchSlop(MotionEvent event)140 private boolean isWithinTouchSlop(MotionEvent event) { 141 return Math.abs(event.getX() - mDownX) < mTouchSlop 142 && Math.abs(event.getY() - mDownY) < mTouchSlop; 143 } 144 isWithinDoubleTapSlop(MotionEvent event)145 public boolean isWithinDoubleTapSlop(MotionEvent event) { 146 if (!mActivated) { 147 // If we're not activated there's no double tap slop to satisfy. 148 return true; 149 } 150 151 return Math.abs(event.getX() - mActivationX) < mDoubleTapSlop 152 && Math.abs(event.getY() - mActivationY) < mDoubleTapSlop; 153 } 154 155 @FunctionalInterface 156 public interface ActivationListener { onActiveChanged(boolean active)157 void onActiveChanged(boolean active); 158 } 159 160 @FunctionalInterface 161 public interface DoubleTapListener { onDoubleTap()162 boolean onDoubleTap(); 163 } 164 165 @FunctionalInterface 166 public interface SlideBackListener { onSlideBack()167 boolean onSlideBack(); 168 } 169 170 @FunctionalInterface 171 public interface DoubleTapLogListener { onDoubleTapLog(boolean accepted, float dx, float dy)172 void onDoubleTapLog(boolean accepted, float dx, float dy); 173 } 174 } 175