• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package com.android.quickstep.util;
17 
18 import static android.view.MotionEvent.ACTION_CANCEL;
19 import static android.view.MotionEvent.ACTION_DOWN;
20 import static android.view.MotionEvent.ACTION_MOVE;
21 import static android.view.MotionEvent.ACTION_UP;
22 
23 import static com.android.launcher3.Utilities.squaredHypot;
24 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
25 
26 import android.content.Context;
27 import android.graphics.PointF;
28 import android.view.MotionEvent;
29 import android.view.VelocityTracker;
30 
31 import com.android.launcher3.R;
32 import com.android.launcher3.Utilities;
33 
34 /**
35  * Tracks motion events to determine whether a gesture on the nav bar is a swipe up.
36  */
37 public class TriggerSwipeUpTouchTracker {
38 
39     private final PointF mDownPos = new PointF();
40     private final float mSquaredTouchSlop;
41     private final float mMinFlingVelocity;
42     private final boolean mDisableHorizontalSwipe;
43     private final NavBarPosition mNavBarPosition;
44     private final Runnable mOnInterceptTouch;
45     private final OnSwipeUpListener mOnSwipeUp;
46 
47     private boolean mInterceptedTouch;
48     private VelocityTracker mVelocityTracker;
49 
TriggerSwipeUpTouchTracker(Context context, boolean disableHorizontalSwipe, NavBarPosition navBarPosition, Runnable onInterceptTouch, OnSwipeUpListener onSwipeUp)50     public TriggerSwipeUpTouchTracker(Context context, boolean disableHorizontalSwipe,
51             NavBarPosition navBarPosition, Runnable onInterceptTouch,
52             OnSwipeUpListener onSwipeUp) {
53         mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
54         mMinFlingVelocity = context.getResources().getDimension(
55                 R.dimen.quickstep_fling_threshold_speed);
56         mNavBarPosition = navBarPosition;
57         mDisableHorizontalSwipe = disableHorizontalSwipe;
58         mOnInterceptTouch = onInterceptTouch;
59         mOnSwipeUp = onSwipeUp;
60 
61         init();
62     }
63 
64     /**
65      * Reset some initial values to prepare for the next gesture.
66      */
init()67     public void init() {
68         mInterceptedTouch = false;
69         mVelocityTracker = VelocityTracker.obtain();
70     }
71 
72     /**
73      * @return Whether we have passed the touch slop and are still tracking the gesture.
74      */
interceptedTouch()75     public boolean interceptedTouch() {
76         return mInterceptedTouch;
77     }
78 
79     /**
80      * Track motion events to determine whether an atomic swipe up has occurred.
81      */
onMotionEvent(MotionEvent ev)82     public void onMotionEvent(MotionEvent ev) {
83         if (mVelocityTracker == null) {
84             return;
85         }
86 
87         mVelocityTracker.addMovement(ev);
88         switch (ev.getActionMasked()) {
89             case ACTION_DOWN: {
90                 mDownPos.set(ev.getX(), ev.getY());
91                 break;
92             }
93             case ACTION_MOVE: {
94                 if (!mInterceptedTouch) {
95                     float displacementX = ev.getX() - mDownPos.x;
96                     float displacementY = ev.getY() - mDownPos.y;
97                     if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
98                         if (mDisableHorizontalSwipe
99                                 && Math.abs(displacementX) > Math.abs(displacementY)) {
100                             // Horizontal gesture is not allowed in this region
101                             endTouchTracking();
102                             break;
103                         }
104 
105                         mInterceptedTouch = true;
106 
107                         if (mOnInterceptTouch != null) {
108                             mOnInterceptTouch.run();
109                         }
110                     }
111                 }
112                 break;
113             }
114 
115             case ACTION_CANCEL:
116                 endTouchTracking();
117                 break;
118 
119             case ACTION_UP: {
120                 onGestureEnd(ev);
121                 endTouchTracking();
122                 break;
123             }
124         }
125     }
126 
endTouchTracking()127     private void endTouchTracking() {
128         if (mVelocityTracker != null) {
129             mVelocityTracker.recycle();
130             mVelocityTracker = null;
131         }
132     }
133 
onGestureEnd(MotionEvent ev)134     private void onGestureEnd(MotionEvent ev) {
135         mVelocityTracker.computeCurrentVelocity(PX_PER_MS);
136         float velocityX = mVelocityTracker.getXVelocity();
137         float velocityY = mVelocityTracker.getYVelocity();
138         float velocity = mNavBarPosition.isRightEdge()
139                 ? -velocityX
140                 : mNavBarPosition.isLeftEdge()
141                         ? velocityX
142                         : -velocityY;
143 
144         final boolean wasFling = Math.abs(velocity) >= mMinFlingVelocity;
145         final boolean isSwipeUp;
146         if (wasFling) {
147             isSwipeUp = velocity > 0;
148         } else {
149             float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
150             float displacementY = ev.getY() - mDownPos.y;
151             isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
152         }
153 
154         if (mOnSwipeUp != null) {
155             if (isSwipeUp) {
156                 mOnSwipeUp.onSwipeUp(wasFling, new PointF(velocityX, velocityY));
157             } else {
158                 mOnSwipeUp.onSwipeUpCancelled();
159             }
160         }
161     }
162 
163     /**
164      * Callback when the gesture ends and was determined to be a swipe from the nav bar.
165      */
166     public interface OnSwipeUpListener {
167         /**
168          * Called on touch up if a swipe up was detected.
169          * @param wasFling Whether the swipe was a fling, or just passed touch slop at low velocity.
170          * @param finalVelocity The final velocity of the swipe.
171          */
onSwipeUp(boolean wasFling, PointF finalVelocity)172         void onSwipeUp(boolean wasFling, PointF finalVelocity);
173 
174         /** Called on touch up if a swipe up was not detected. */
onSwipeUpCancelled()175         void onSwipeUpCancelled();
176     }
177 }
178