1 /* 2 * Copyright (C) 2020 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.interaction; 17 18 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; 19 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_GESTURE_COMPLETED; 20 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_NOT_STARTED_TOO_FAR_FROM_EDGE; 21 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_CANCELLED; 22 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION; 23 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED; 24 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE; 25 26 import android.content.Context; 27 import android.content.res.Resources; 28 import android.graphics.Point; 29 import android.graphics.PointF; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.View.OnTouchListener; 33 34 import androidx.annotation.Nullable; 35 36 import com.android.launcher3.testing.shared.ResourceUtils; 37 import com.android.launcher3.util.DisplayController; 38 import com.android.launcher3.util.NavigationMode; 39 import com.android.launcher3.util.VibratorWrapper; 40 import com.android.quickstep.util.MotionPauseDetector; 41 import com.android.quickstep.util.NavBarPosition; 42 import com.android.quickstep.util.TriggerSwipeUpTouchTracker; 43 44 /** Utility class to handle Home gesture. */ 45 public class NavBarGestureHandler implements OnTouchListener, 46 TriggerSwipeUpTouchTracker.OnSwipeUpListener, MotionPauseDetector.OnMotionPauseListener { 47 48 private static final String LOG_TAG = "NavBarGestureHandler"; 49 private final Context mContext; 50 private final Point mDisplaySize = new Point(); 51 private final TriggerSwipeUpTouchTracker mSwipeUpTouchTracker; 52 private final int mBottomGestureHeight; 53 private final PointF mDownPos = new PointF(); 54 private final PointF mLastPos = new PointF(); 55 private final MotionPauseDetector mMotionPauseDetector; 56 private boolean mTouchCameFromNavBar; 57 @Nullable 58 private NavBarGestureAttemptCallback mGestureCallback; 59 NavBarGestureHandler(Context context)60 NavBarGestureHandler(Context context) { 61 mContext = context; 62 DisplayController.Info displayInfo = DisplayController.INSTANCE.get(mContext).getInfo(); 63 Point currentSize = displayInfo.currentSize; 64 mDisplaySize.set(currentSize.x, currentSize.y); 65 mSwipeUpTouchTracker = 66 new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/, 67 new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo), 68 this); 69 mMotionPauseDetector = new MotionPauseDetector(context); 70 71 final Resources resources = context.getResources(); 72 mBottomGestureHeight = 73 ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, resources); 74 } 75 registerNavBarGestureAttemptCallback(NavBarGestureAttemptCallback callback)76 void registerNavBarGestureAttemptCallback(NavBarGestureAttemptCallback callback) { 77 mGestureCallback = callback; 78 } 79 unregisterNavBarGestureAttemptCallback()80 void unregisterNavBarGestureAttemptCallback() { 81 mGestureCallback = null; 82 } 83 84 @Override onSwipeUp(boolean wasFling, PointF finalVelocity)85 public void onSwipeUp(boolean wasFling, PointF finalVelocity) { 86 if (mGestureCallback == null) { 87 return; 88 } 89 if (mTouchCameFromNavBar) { 90 mGestureCallback.onNavBarGestureAttempted(wasFling 91 ? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED, finalVelocity); 92 } else { 93 mGestureCallback.onNavBarGestureAttempted(wasFling 94 ? HOME_NOT_STARTED_TOO_FAR_FROM_EDGE : OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE, 95 finalVelocity); 96 } 97 } 98 99 @Override onSwipeUpCancelled()100 public void onSwipeUpCancelled() { 101 if (mGestureCallback != null) { 102 mGestureCallback.onNavBarGestureAttempted(HOME_OR_OVERVIEW_CANCELLED, new PointF()); 103 } 104 } 105 106 @Override onTouch(View view, MotionEvent event)107 public boolean onTouch(View view, MotionEvent event) { 108 int action = event.getAction(); 109 boolean intercepted = mSwipeUpTouchTracker.interceptedTouch(); 110 switch (action) { 111 case MotionEvent.ACTION_DOWN: 112 mDownPos.set(event.getX(), event.getY()); 113 mLastPos.set(mDownPos); 114 mTouchCameFromNavBar = mDownPos.y >= mDisplaySize.y - mBottomGestureHeight; 115 if (!mTouchCameFromNavBar && mGestureCallback != null) { 116 mGestureCallback.setNavBarGestureProgress(null); 117 } 118 mSwipeUpTouchTracker.init(); 119 mMotionPauseDetector.clear(); 120 mMotionPauseDetector.setOnMotionPauseListener(this); 121 break; 122 case MotionEvent.ACTION_MOVE: 123 mLastPos.set(event.getX(), event.getY()); 124 break; 125 case MotionEvent.ACTION_UP: 126 case MotionEvent.ACTION_CANCEL: 127 mMotionPauseDetector.clear(); 128 if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) { 129 mGestureCallback.onNavBarGestureAttempted( 130 HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF()); 131 intercepted = true; 132 break; 133 } 134 break; 135 } 136 if (mTouchCameFromNavBar && mGestureCallback != null) { 137 mGestureCallback.setNavBarGestureProgress(event.getY() - mDownPos.y); 138 } 139 mSwipeUpTouchTracker.onMotionEvent(event); 140 mMotionPauseDetector.addPosition(event); 141 mMotionPauseDetector.setDisallowPause(mLastPos.y >= mDisplaySize.y - mBottomGestureHeight); 142 return intercepted; 143 } 144 onInterceptTouch(MotionEvent event)145 boolean onInterceptTouch(MotionEvent event) { 146 return event.getY() >= mDisplaySize.y - mBottomGestureHeight; 147 } 148 149 @Override onMotionPauseChanged(boolean isPaused)150 public void onMotionPauseChanged(boolean isPaused) { 151 mGestureCallback.onMotionPaused(isPaused); 152 } 153 154 @Override onMotionPauseDetected()155 public void onMotionPauseDetected() { 156 VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC); 157 } 158 159 enum NavBarGestureResult { 160 UNKNOWN, 161 HOME_GESTURE_COMPLETED, 162 OVERVIEW_GESTURE_COMPLETED, 163 HOME_NOT_STARTED_TOO_FAR_FROM_EDGE, 164 OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE, 165 HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, // Side swipe on nav bar. 166 HOME_OR_OVERVIEW_CANCELLED, 167 } 168 169 /** Callback to let the UI react to attempted nav bar gestures. */ 170 interface NavBarGestureAttemptCallback { 171 /** Called whenever any touch is completed. */ onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity)172 void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity); 173 174 /** Called when a motion stops or resumes */ onMotionPaused(boolean isPaused)175 default void onMotionPaused(boolean isPaused) {} 176 177 /** Indicates how far a touch originating in the nav bar has moved from the nav bar. */ setNavBarGestureProgress(@ullable Float displacement)178 default void setNavBarGestureProgress(@Nullable Float displacement) {} 179 } 180 } 181