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.launcher3.uioverrides.touchcontrollers; 17 18 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU; 19 import static com.android.launcher3.AbstractFloatingView.getOpenView; 20 import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON; 21 import static com.android.launcher3.LauncherState.NORMAL; 22 import static com.android.launcher3.LauncherState.OVERVIEW; 23 import static com.android.launcher3.Utilities.EDGE_NAV_BAR; 24 25 import android.animation.ValueAnimator; 26 import android.os.SystemClock; 27 import android.view.MotionEvent; 28 29 import com.android.launcher3.AbstractFloatingView; 30 import com.android.launcher3.LauncherState; 31 import com.android.launcher3.touch.AbstractStateChangeTouchController; 32 import com.android.launcher3.touch.SingleAxisSwipeDetector; 33 import com.android.launcher3.uioverrides.QuickstepLauncher; 34 import com.android.quickstep.SystemUiProxy; 35 import com.android.quickstep.util.LayoutUtils; 36 import com.android.quickstep.views.AllAppsEduView; 37 38 /** 39 * Touch controller for handling edge swipes in 2-button mode 40 */ 41 public class TwoButtonNavbarTouchController extends AbstractStateChangeTouchController { 42 43 private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3; 44 45 private static final String TAG = "2BtnNavbarTouchCtrl"; 46 47 private final boolean mIsTransposed; 48 49 // If true, we will finish the current animation instantly on second touch. 50 private boolean mFinishFastOnSecondTouch; 51 52 private int mContinuousTouchCount = 0; 53 TwoButtonNavbarTouchController(QuickstepLauncher l)54 public TwoButtonNavbarTouchController(QuickstepLauncher l) { 55 super(l, l.getDeviceProfile().isVerticalBarLayout() 56 ? SingleAxisSwipeDetector.HORIZONTAL : SingleAxisSwipeDetector.VERTICAL); 57 mIsTransposed = l.getDeviceProfile().isVerticalBarLayout(); 58 } 59 60 @Override canInterceptTouch(MotionEvent ev)61 protected boolean canInterceptTouch(MotionEvent ev) { 62 boolean canIntercept = canInterceptTouchInternal(ev); 63 if (!canIntercept) { 64 mContinuousTouchCount = 0; 65 } 66 return canIntercept; 67 } 68 canInterceptTouchInternal(MotionEvent ev)69 private boolean canInterceptTouchInternal(MotionEvent ev) { 70 if (mCurrentAnimation != null) { 71 if (mFinishFastOnSecondTouch) { 72 mCurrentAnimation.getAnimationPlayer().end(); 73 } 74 75 // If we are already animating from a previous state, we can intercept. 76 return true; 77 } 78 if (AbstractFloatingView.getTopOpenView(mLauncher) != null) { 79 return false; 80 } 81 if ((ev.getEdgeFlags() & EDGE_NAV_BAR) == 0) { 82 return false; 83 } 84 if (!mIsTransposed && mLauncher.isInState(OVERVIEW)) { 85 return true; 86 } 87 return mLauncher.isInState(NORMAL); 88 } 89 90 @Override onControllerInterceptTouchEvent(MotionEvent ev)91 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 92 boolean intercept = super.onControllerInterceptTouchEvent(ev); 93 return intercept; 94 } 95 96 @Override getTargetState(LauncherState fromState, boolean isDragTowardPositive)97 protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) { 98 if (mIsTransposed) { 99 boolean draggingFromNav = 100 mLauncher.getDeviceProfile().isSeascape() == isDragTowardPositive; 101 return draggingFromNav ? HINT_STATE_TWO_BUTTON : NORMAL; 102 } else { 103 LauncherState startState = mStartState != null ? mStartState : fromState; 104 return isDragTowardPositive ^ (startState == OVERVIEW) ? HINT_STATE_TWO_BUTTON : NORMAL; 105 } 106 } 107 108 @Override onReinitToState(LauncherState newToState)109 protected void onReinitToState(LauncherState newToState) { 110 super.onReinitToState(newToState); 111 } 112 113 @Override updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, LauncherState targetState, float velocity, boolean isFling)114 protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, 115 LauncherState targetState, float velocity, boolean isFling) { 116 super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, 117 velocity, isFling); 118 mFinishFastOnSecondTouch = !mIsTransposed && mFromState == NORMAL; 119 120 if (targetState == HINT_STATE_TWO_BUTTON) { 121 // We were going to HINT_STATE_TWO_BUTTON, but end that animation immediately so we go 122 // to OVERVIEW instead. 123 animator.setDuration(0); 124 } 125 } 126 127 @Override getShiftRange()128 protected float getShiftRange() { 129 // Should be in sync with TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT 130 return LayoutUtils.getDefaultSwipeHeight(mLauncher, mLauncher.getDeviceProfile()); 131 } 132 133 @Override initCurrentAnimation()134 protected float initCurrentAnimation() { 135 float range = getShiftRange(); 136 long maxAccuracy = (long) (2 * range); 137 mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState, 138 maxAccuracy); 139 return (mLauncher.getDeviceProfile().isSeascape() ? 1 : -1) / range; 140 } 141 142 @Override updateProgress(float fraction)143 protected void updateProgress(float fraction) { 144 super.updateProgress(fraction); 145 146 // We have reached HINT_STATE, end the gesture now to go to OVERVIEW. 147 if (fraction >= 1 && mToState == HINT_STATE_TWO_BUTTON) { 148 final long now = SystemClock.uptimeMillis(); 149 MotionEvent event = MotionEvent.obtain(now, now, 150 MotionEvent.ACTION_UP, 0.0f, 0.0f, 0); 151 mDetector.onTouchEvent(event); 152 event.recycle(); 153 } 154 } 155 156 @Override onSwipeInteractionCompleted(LauncherState targetState)157 protected void onSwipeInteractionCompleted(LauncherState targetState) { 158 super.onSwipeInteractionCompleted(targetState); 159 if (!mIsTransposed) { 160 mContinuousTouchCount++; 161 } 162 if (mStartState == NORMAL && targetState == HINT_STATE_TWO_BUTTON) { 163 SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG); 164 } else if (targetState == NORMAL 165 && mContinuousTouchCount >= MAX_NUM_SWIPES_TO_TRIGGER_EDU) { 166 mContinuousTouchCount = 0; 167 if (getOpenView(mLauncher, TYPE_ALL_APPS_EDU) == null) { 168 AllAppsEduView.show(mLauncher); 169 } 170 } 171 mStartState = null; 172 } 173 } 174