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.launcher3.uioverrides.touchcontrollers; 17 18 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; 19 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU; 20 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; 21 import static com.android.launcher3.LauncherAnimUtils.newCancelListener; 22 import static com.android.launcher3.LauncherState.ALL_APPS; 23 import static com.android.launcher3.LauncherState.NORMAL; 24 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; 25 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback; 26 import static com.android.launcher3.anim.Interpolators.DEACCEL_3; 27 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU; 28 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 29 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; 30 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; 31 32 import android.animation.ValueAnimator; 33 import android.view.MotionEvent; 34 import android.view.animation.Interpolator; 35 36 import com.android.launcher3.AbstractFloatingView; 37 import com.android.launcher3.Launcher; 38 import com.android.launcher3.LauncherState; 39 import com.android.launcher3.R; 40 import com.android.launcher3.Utilities; 41 import com.android.launcher3.allapps.AllAppsTransitionController; 42 import com.android.launcher3.anim.AnimatorPlaybackController; 43 import com.android.launcher3.anim.Interpolators; 44 import com.android.launcher3.anim.PendingAnimation; 45 import com.android.launcher3.compat.AccessibilityManagerCompat; 46 import com.android.launcher3.config.FeatureFlags; 47 import com.android.launcher3.states.StateAnimationConfig; 48 import com.android.launcher3.touch.SingleAxisSwipeDetector; 49 import com.android.launcher3.util.TouchController; 50 import com.android.quickstep.TaskUtils; 51 import com.android.quickstep.util.AnimatorControllerWithResistance; 52 import com.android.quickstep.util.AssistantUtilities; 53 import com.android.quickstep.util.OverviewToHomeAnim; 54 import com.android.quickstep.views.RecentsView; 55 56 /** 57 * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps. 58 */ 59 public class NavBarToHomeTouchController implements TouchController, 60 SingleAxisSwipeDetector.Listener { 61 62 private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3; 63 // The min amount of overview scrim we keep during the transition. 64 private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f; 65 66 private final Launcher mLauncher; 67 private final SingleAxisSwipeDetector mSwipeDetector; 68 private final float mPullbackDistance; 69 70 private boolean mNoIntercept; 71 private LauncherState mStartState; 72 private LauncherState mEndState = NORMAL; 73 private AnimatorPlaybackController mCurrentAnimation; 74 NavBarToHomeTouchController(Launcher launcher)75 public NavBarToHomeTouchController(Launcher launcher) { 76 mLauncher = launcher; 77 mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this, 78 SingleAxisSwipeDetector.VERTICAL); 79 mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance); 80 } 81 82 @Override onControllerInterceptTouchEvent(MotionEvent ev)83 public final boolean onControllerInterceptTouchEvent(MotionEvent ev) { 84 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 85 mStartState = mLauncher.getStateManager().getState(); 86 mNoIntercept = !canInterceptTouch(ev); 87 if (mNoIntercept) { 88 return false; 89 } 90 mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE, 91 false /* ignoreSlop */); 92 } 93 94 if (mNoIntercept) { 95 return false; 96 } 97 98 onControllerTouchEvent(ev); 99 return mSwipeDetector.isDraggingOrSettling(); 100 } 101 canInterceptTouch(MotionEvent ev)102 private boolean canInterceptTouch(MotionEvent ev) { 103 boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0; 104 if (!cameFromNavBar) { 105 return false; 106 } 107 if (mStartState.overviewUi || mStartState == ALL_APPS) { 108 return true; 109 } 110 int typeToClose = ENABLE_ALL_APPS_EDU.get() ? TYPE_ALL & ~TYPE_ALL_APPS_EDU : TYPE_ALL; 111 if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) { 112 return true; 113 } 114 if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get() 115 && AssistantUtilities.isExcludedAssistantRunning()) { 116 return true; 117 } 118 return false; 119 } 120 121 @Override onControllerTouchEvent(MotionEvent ev)122 public final boolean onControllerTouchEvent(MotionEvent ev) { 123 return mSwipeDetector.onTouchEvent(ev); 124 } 125 getShiftRange()126 private float getShiftRange() { 127 return mLauncher.getDeviceProfile().heightPx; 128 } 129 130 @Override onDragStart(boolean start, float startDisplacement)131 public void onDragStart(boolean start, float startDisplacement) { 132 initCurrentAnimation(); 133 } 134 initCurrentAnimation()135 private void initCurrentAnimation() { 136 long accuracy = (long) (getShiftRange() * 2); 137 final PendingAnimation builder = new PendingAnimation(accuracy); 138 if (mStartState.overviewUi) { 139 RecentsView recentsView = mLauncher.getOverviewPanel(); 140 AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher, 141 builder); 142 143 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 144 builder.addOnFrameCallback(recentsView::redrawLiveTile); 145 } 146 147 AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU); 148 } else if (mStartState == ALL_APPS) { 149 AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); 150 builder.setFloat(allAppsController, ALL_APPS_PROGRESS, 151 -mPullbackDistance / allAppsController.getShiftRange(), PULLBACK_INTERPOLATOR); 152 153 // Slightly fade out all apps content to further distinguish from scrolling. 154 StateAnimationConfig config = new StateAnimationConfig(); 155 config.duration = accuracy; 156 config.setInterpolator(StateAnimationConfig.ANIM_ALL_APPS_FADE, Interpolators 157 .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f)); 158 159 allAppsController.setAlphas(mEndState, config, builder); 160 } 161 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher); 162 if (topView != null) { 163 topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder); 164 } 165 mCurrentAnimation = builder.createPlaybackController(); 166 mCurrentAnimation.getTarget().addListener(newCancelListener(this::clearState)); 167 } 168 clearState()169 private void clearState() { 170 mCurrentAnimation = null; 171 mSwipeDetector.finishedScrolling(); 172 mSwipeDetector.setDetectableScrollConditions(0, false); 173 } 174 175 @Override onDrag(float displacement)176 public boolean onDrag(float displacement) { 177 // Only allow swipe up. 178 displacement = Math.min(0, displacement); 179 float progress = Utilities.getProgress(displacement, 0, getShiftRange()); 180 mCurrentAnimation.setPlayFraction(progress); 181 return true; 182 } 183 184 @Override onDragEnd(float velocity)185 public void onDragEnd(float velocity) { 186 boolean fling = mSwipeDetector.isFling(velocity); 187 float progress = mCurrentAnimation.getProgressFraction(); 188 float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress); 189 boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS 190 || (velocity < 0 && fling); 191 if (success) { 192 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 193 RecentsView recentsView = mLauncher.getOverviewPanel(); 194 recentsView.switchToScreenshot(null, 195 () -> recentsView.finishRecentsAnimation(true /* toRecents */, null)); 196 } 197 if (mStartState.overviewUi) { 198 new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState)) 199 .animateWithVelocity(velocity); 200 } else { 201 mLauncher.getStateManager().goToState(mEndState, true, 202 forSuccessCallback(() -> onSwipeInteractionCompleted(mEndState))); 203 } 204 if (mStartState != mEndState) { 205 logHomeGesture(); 206 } 207 AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher); 208 if (topOpenView != null) { 209 AbstractFloatingView.closeAllOpenViews(mLauncher); 210 // TODO: add to WW log 211 } 212 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); 213 } else { 214 // Quickly return to the state we came from (we didn't move far). 215 ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); 216 anim.setFloatValues(progress, 0); 217 anim.addListener(forSuccessCallback(() -> onSwipeInteractionCompleted(mStartState))); 218 anim.setDuration(80).start(); 219 } 220 } 221 onSwipeInteractionCompleted(LauncherState targetState)222 private void onSwipeInteractionCompleted(LauncherState targetState) { 223 clearState(); 224 mLauncher.getStateManager().goToState(targetState, false /* animated */); 225 AccessibilityManagerCompat.sendStateEventToTest(mLauncher, targetState.ordinal); 226 } 227 logHomeGesture()228 private void logHomeGesture() { 229 mLauncher.getStatsLogManager().logger() 230 .withSrcState(mStartState.statsLogOrdinal) 231 .withDstState(mEndState.statsLogOrdinal) 232 .log(LAUNCHER_HOME_GESTURE); 233 } 234 } 235