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