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 21 import com.android.internal.annotations.VisibleForTesting; 22 import com.android.systemui.dagger.qualifiers.Main; 23 import com.android.systemui.plugins.FalsingManager; 24 import com.android.systemui.util.concurrency.DelayableExecutor; 25 26 import javax.inject.Inject; 27 28 /** 29 * Detects single and double taps on notifications. 30 */ 31 public class NotificationTapHelper { 32 33 public static final long DOUBLE_TAP_TIMEOUT_MS = 1200; 34 35 private final ActivationListener mActivationListener; 36 private final DoubleTapListener mDoubleTapListener; 37 private final FalsingManager mFalsingManager; 38 private final DelayableExecutor mExecutor; 39 private final SlideBackListener mSlideBackListener; 40 41 private boolean mTrackTouch; 42 private Runnable mTimeoutCancel; 43 NotificationTapHelper(FalsingManager falsingManager, DelayableExecutor executor, ActivationListener activationListener, DoubleTapListener doubleTapListener, SlideBackListener slideBackListener)44 private NotificationTapHelper(FalsingManager falsingManager, DelayableExecutor executor, 45 ActivationListener activationListener, DoubleTapListener doubleTapListener, 46 SlideBackListener slideBackListener) { 47 mFalsingManager = falsingManager; 48 mExecutor = executor; 49 mActivationListener = activationListener; 50 mDoubleTapListener = doubleTapListener; 51 mSlideBackListener = slideBackListener; 52 } 53 54 @VisibleForTesting onTouchEvent(MotionEvent event)55 boolean onTouchEvent(MotionEvent event) { 56 return onTouchEvent(event, Integer.MAX_VALUE); 57 } 58 59 /** Call to have the helper process a touch event. */ onTouchEvent(MotionEvent event, int maxTouchableHeight)60 public boolean onTouchEvent(MotionEvent event, int maxTouchableHeight) { 61 int action = event.getActionMasked(); 62 switch (action) { 63 case MotionEvent.ACTION_DOWN: 64 mTrackTouch = event.getY() <= maxTouchableHeight; 65 break; 66 case MotionEvent.ACTION_MOVE: 67 if (mTrackTouch && !mFalsingManager.isSimpleTap()) { 68 makeInactive(); 69 mTrackTouch = false; 70 } 71 break; 72 case MotionEvent.ACTION_CANCEL: 73 makeInactive(); 74 mTrackTouch = false; 75 break; 76 case MotionEvent.ACTION_UP: 77 mTrackTouch = false; 78 79 // 1) See if we have confidence that we can activate after a single tap. 80 // 2) Else, see if it looks like a tap at all and check for a double-tap. 81 if (!mFalsingManager.isFalseTap(FalsingManager.NO_PENALTY)) { 82 makeInactive(); 83 return mDoubleTapListener.onDoubleTap(); 84 } else if (mFalsingManager.isSimpleTap()) { 85 if (mSlideBackListener != null && mSlideBackListener.onSlideBack()) { 86 return true; 87 } 88 if (mTimeoutCancel == null) { 89 // first tap 90 makeActive(); 91 return true; 92 } else { 93 // second tap 94 makeInactive(); 95 if (!mFalsingManager.isFalseDoubleTap()) { 96 return mDoubleTapListener.onDoubleTap(); 97 } 98 } 99 } else { 100 makeInactive(); 101 } 102 break; 103 default: 104 break; 105 } 106 return mTrackTouch; 107 } 108 makeActive()109 private void makeActive() { 110 mTimeoutCancel = mExecutor.executeDelayed(this::makeInactive, DOUBLE_TAP_TIMEOUT_MS); 111 mActivationListener.onActiveChanged(true); 112 } 113 makeInactive()114 private void makeInactive() { 115 mActivationListener.onActiveChanged(false); 116 if (mTimeoutCancel != null) { 117 mTimeoutCancel.run(); 118 mTimeoutCancel = null; 119 } 120 } 121 122 /** */ 123 @FunctionalInterface 124 public interface ActivationListener { 125 /** */ onActiveChanged(boolean active)126 void onActiveChanged(boolean active); 127 } 128 129 /** */ 130 @FunctionalInterface 131 public interface DoubleTapListener { 132 /** */ onDoubleTap()133 boolean onDoubleTap(); 134 } 135 136 /** */ 137 @FunctionalInterface 138 public interface SlideBackListener { 139 /** */ onSlideBack()140 boolean onSlideBack(); 141 } 142 143 /** 144 * Injectable factory that creates a {@link NotificationTapHelper}. 145 */ 146 public static class Factory { 147 private final FalsingManager mFalsingManager; 148 private final DelayableExecutor mDelayableExecutor; 149 150 @Inject Factory(FalsingManager falsingManager, @Main DelayableExecutor delayableExecutor)151 public Factory(FalsingManager falsingManager, @Main DelayableExecutor delayableExecutor) { 152 mFalsingManager = falsingManager; 153 mDelayableExecutor = delayableExecutor; 154 } 155 156 /** Create a {@link NotificationTapHelper} */ create(ActivationListener activationListener, DoubleTapListener doubleTapListener, SlideBackListener slideBackListener)157 public NotificationTapHelper create(ActivationListener activationListener, 158 DoubleTapListener doubleTapListener, SlideBackListener slideBackListener) { 159 return new NotificationTapHelper(mFalsingManager, mDelayableExecutor, 160 activationListener, doubleTapListener, slideBackListener); 161 } 162 } 163 } 164