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