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