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