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