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