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