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 android.view.MotionEvent.ACTION_DOWN; 19 20 import static com.android.app.animation.Interpolators.ACCELERATE_0_75; 21 import static com.android.app.animation.Interpolators.DECELERATE_3; 22 import static com.android.app.animation.Interpolators.LINEAR; 23 import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity; 24 import static com.android.launcher3.LauncherAnimUtils.newCancelListener; 25 import static com.android.launcher3.LauncherState.NORMAL; 26 import static com.android.launcher3.LauncherState.OVERVIEW; 27 import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS; 28 import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME; 29 import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe; 30 import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent; 31 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe; 32 import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD; 33 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 34 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; 35 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT; 36 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN; 37 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP; 38 import static com.android.launcher3.logging.StatsLogManager.getLauncherAtomEvent; 39 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; 40 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; 41 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; 42 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE; 43 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE; 44 import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS; 45 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; 46 import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM; 47 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT; 48 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP; 49 import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS; 50 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; 51 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; 52 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET; 53 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; 54 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; 55 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; 56 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; 57 import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA; 58 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; 59 60 import android.animation.Animator; 61 import android.animation.Animator.AnimatorListener; 62 import android.animation.AnimatorListenerAdapter; 63 import android.animation.ValueAnimator; 64 import android.graphics.PointF; 65 import android.view.MotionEvent; 66 import android.view.animation.Interpolator; 67 import android.window.DesktopModeFlags; 68 69 import com.android.internal.jank.Cuj; 70 import com.android.launcher3.LauncherState; 71 import com.android.launcher3.R; 72 import com.android.launcher3.Utilities; 73 import com.android.launcher3.anim.AnimatedFloat; 74 import com.android.launcher3.anim.AnimatorPlaybackController; 75 import com.android.launcher3.anim.PendingAnimation; 76 import com.android.launcher3.states.StateAnimationConfig; 77 import com.android.launcher3.touch.BaseSwipeDetector; 78 import com.android.launcher3.touch.BothAxesSwipeDetector; 79 import com.android.launcher3.uioverrides.QuickstepLauncher; 80 import com.android.launcher3.util.DisplayController; 81 import com.android.launcher3.util.TouchController; 82 import com.android.launcher3.util.VibratorWrapper; 83 import com.android.quickstep.SystemUiProxy; 84 import com.android.quickstep.util.AnimatorControllerWithResistance; 85 import com.android.quickstep.util.LayoutUtils; 86 import com.android.quickstep.util.MotionPauseDetector; 87 import com.android.quickstep.util.WorkspaceRevealAnim; 88 import com.android.quickstep.views.RecentsView; 89 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 90 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; 91 92 /** 93 * Handles quick switching to a recent task from the home screen. To give as much flexibility to 94 * the user as possible, also handles swipe up and hold to go to overview and swiping back home. 95 */ 96 public class NoButtonQuickSwitchTouchController implements TouchController, 97 BothAxesSwipeDetector.Listener { 98 99 private static final float Y_ANIM_MIN_PROGRESS = 0.25f; 100 private static final Interpolator FADE_OUT_INTERPOLATOR = DECELERATE_3; 101 private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCELERATE_0_75; 102 private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR; 103 private static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300; 104 105 private final QuickstepLauncher mLauncher; 106 private final BothAxesSwipeDetector mSwipeDetector; 107 private final float mXRange; 108 private final float mYRange; 109 private final float mMaxYProgress; 110 private final MotionPauseDetector mMotionPauseDetector; 111 private final float mMotionPauseMinDisplacement; 112 private final RecentsView mRecentsView; 113 protected final AnimatorListener mClearStateOnCancelListener = 114 newCancelListener(this::clearState, /* isSingleUse = */ false); 115 116 private boolean mNoIntercept; 117 private LauncherState mStartState; 118 119 private boolean mIsHomeScreenVisible = true; 120 121 // As we drag, we control 3 animations: one to get non-overview components out of the way, 122 // and the other two to set overview properties based on x and y progress. 123 private AnimatorPlaybackController mNonOverviewAnim; 124 private AnimatorPlaybackController mXOverviewAnim; 125 private AnimatedFloat mYOverviewAnim; 126 private boolean mIsTrackpadSwipe; 127 NoButtonQuickSwitchTouchController(QuickstepLauncher launcher)128 public NoButtonQuickSwitchTouchController(QuickstepLauncher launcher) { 129 mLauncher = launcher; 130 mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this); 131 mRecentsView = mLauncher.getOverviewPanel(); 132 mXRange = mLauncher.getDeviceProfile().widthPx / 2f; 133 mYRange = LayoutUtils.getShelfTrackingDistance( 134 mLauncher, 135 mLauncher.getDeviceProfile(), 136 mRecentsView.getPagedOrientationHandler(), 137 mRecentsView.getSizeStrategy()); 138 mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange; 139 mMotionPauseDetector = new MotionPauseDetector(mLauncher); 140 mMotionPauseMinDisplacement = mLauncher.getResources().getDimension( 141 R.dimen.motion_pause_detector_min_displacement_from_app); 142 } 143 144 @Override onControllerInterceptTouchEvent(MotionEvent ev)145 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 146 if (ev.getActionMasked() == ACTION_DOWN) { 147 mNoIntercept = !canInterceptTouch(ev); 148 if (mNoIntercept) { 149 return false; 150 } 151 152 // Only detect horizontal swipe for intercept, then we will allow swipe up as well. 153 mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT, 154 false /* ignoreSlopWhenSettling */); 155 } 156 157 if (mNoIntercept) { 158 return false; 159 } 160 161 onControllerTouchEvent(ev); 162 return mSwipeDetector.isDraggingOrSettling(); 163 } 164 165 @Override onControllerTouchEvent(MotionEvent ev)166 public boolean onControllerTouchEvent(MotionEvent ev) { 167 return mSwipeDetector.onTouchEvent(ev); 168 } 169 canInterceptTouch(MotionEvent ev)170 private boolean canInterceptTouch(MotionEvent ev) { 171 if (!isTrackpadMotionEvent(ev) && DisplayController.getNavigationMode(mLauncher) 172 == THREE_BUTTONS) { 173 return false; 174 } 175 if (!mLauncher.isInState(LauncherState.NORMAL)) { 176 return false; 177 } 178 if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) { 179 return false; 180 } 181 long stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags(); 182 if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) { 183 return false; 184 } 185 if (isTrackpadMultiFingerSwipe(ev)) { 186 mIsTrackpadSwipe = isTrackpadFourFingerSwipe(ev); 187 return mIsTrackpadSwipe; 188 } 189 if (DesktopModeStatus.canEnterDesktopMode(mLauncher) 190 //TODO(b/345296916): replace with dev option once in teamfood 191 && DesktopModeFlags.ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX.isTrue() 192 && mRecentsView.getNonDesktopTaskViewCount() < 1) { 193 return false; 194 } 195 return true; 196 } 197 198 @Override onDragStart(boolean start)199 public void onDragStart(boolean start) { 200 mMotionPauseDetector.clear(); 201 mMotionPauseDetector.setIsTrackpadGesture(mIsTrackpadSwipe); 202 if (start) { 203 InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH); 204 InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS, 205 "Home"); 206 207 mStartState = mLauncher.getStateManager().getState(); 208 209 mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected); 210 211 // We have detected horizontal drag start, now allow swipe up as well. 212 mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP, 213 false /* ignoreSlopWhenSettling */); 214 215 setupAnimators(); 216 } 217 } 218 onMotionPauseDetected()219 private void onMotionPauseDetected() { 220 VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); 221 } 222 setupAnimators()223 private void setupAnimators() { 224 // Animate the non-overview components (e.g. workspace, shelf) out of the way. 225 StateAnimationConfig nonOverviewBuilder = new StateAnimationConfig(); 226 nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR); 227 nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR); 228 nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_SCALE, FADE_OUT_INTERPOLATOR); 229 nonOverviewBuilder.setInterpolator(ANIM_DEPTH, FADE_OUT_INTERPOLATOR); 230 nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR); 231 updateNonOverviewAnim(QUICK_SWITCH_FROM_HOME, nonOverviewBuilder); 232 mNonOverviewAnim.dispatchOnStart(); 233 234 if (!mRecentsView.hasTaskViews()) { 235 mRecentsView.setOnEmptyMessageUpdatedListener(isEmpty -> { 236 if (!isEmpty && mSwipeDetector.isDraggingState()) { 237 // We have loaded tasks, update the animators to start at the correct scale etc. 238 setupOverviewAnimators(); 239 } 240 }); 241 } 242 243 setupOverviewAnimators(); 244 } 245 246 /** Create state animation to control non-overview components. */ updateNonOverviewAnim(LauncherState toState, StateAnimationConfig config)247 private void updateNonOverviewAnim(LauncherState toState, StateAnimationConfig config) { 248 config.duration = (long) (Math.max(mXRange, mYRange) * 2); 249 config.animFlags |= SKIP_OVERVIEW | SKIP_SCRIM; 250 mNonOverviewAnim = mLauncher.getStateManager() 251 .createAnimationToNewWorkspace(toState, config); 252 mNonOverviewAnim.getTarget().addListener(mClearStateOnCancelListener); 253 } 254 setupOverviewAnimators()255 private void setupOverviewAnimators() { 256 final LauncherState fromState = QUICK_SWITCH_FROM_HOME; 257 final LauncherState toState = OVERVIEW; 258 259 // Set RecentView's initial properties. 260 RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]); 261 ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, 1f); 262 TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, fromState.showTaskThumbnailSplash() ? 1f : 0); 263 mRecentsView.setContentAlpha(1); 264 mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress()); 265 mLauncher.getActionsView().getVisibilityAlpha().updateValue( 266 (fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f); 267 mRecentsView.setTaskIconVisible(false); 268 269 float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher); 270 // As we drag right, animate the following properties: 271 // - RecentsView translationX 272 // - OverviewScrim 273 // - RecentsView fade (if it's empty) 274 PendingAnimation xAnim = new PendingAnimation((long) (mXRange * 2)); 275 xAnim.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1], LINEAR); 276 // Use QuickSwitchState instead of OverviewState to determine scrim color, 277 // since we need to take potential taskbar into account. 278 xAnim.setViewBackgroundColor(mLauncher.getScrimView(), 279 QUICK_SWITCH_FROM_HOME.getWorkspaceScrimColor(mLauncher), LINEAR); 280 if (!mRecentsView.hasTaskViews()) { 281 xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR); 282 } 283 mXOverviewAnim = xAnim.createPlaybackController(); 284 mXOverviewAnim.dispatchOnStart(); 285 286 // As we drag up, animate the following properties: 287 // - RecentsView scale 288 // - RecentsView fullscreenProgress 289 PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2)); 290 yAnim.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0], 291 SCALE_DOWN_INTERPOLATOR); 292 yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS, 293 toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR); 294 AnimatorPlaybackController yNormalController = yAnim.createPlaybackController(); 295 AnimatorControllerWithResistance yAnimWithResistance = AnimatorControllerWithResistance 296 .createForRecents(yNormalController, mLauncher, 297 mRecentsView.getPagedViewOrientedState(), mLauncher.getDeviceProfile(), 298 mRecentsView, RECENTS_SCALE_PROPERTY, mRecentsView, 299 TASK_SECONDARY_TRANSLATION); 300 mYOverviewAnim = new AnimatedFloat(() -> { 301 if (mYOverviewAnim != null) { 302 yAnimWithResistance.setProgress(mYOverviewAnim.value, mMaxYProgress); 303 } 304 }); 305 yNormalController.dispatchOnStart(); 306 } 307 308 @Override onDrag(PointF displacement, MotionEvent ev)309 public boolean onDrag(PointF displacement, MotionEvent ev) { 310 float xProgress = Math.max(0, displacement.x) / mXRange; 311 float yProgress = Math.max(0, -displacement.y) / mYRange; 312 yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f); 313 314 boolean wasHomeScreenVisible = mIsHomeScreenVisible; 315 if (wasHomeScreenVisible && mNonOverviewAnim != null) { 316 mNonOverviewAnim.setPlayFraction(xProgress); 317 } 318 mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress) 319 <= 1 - ALPHA_CUTOFF_THRESHOLD; 320 321 mMotionPauseDetector.setDisallowPause(-displacement.y < mMotionPauseMinDisplacement); 322 mMotionPauseDetector.addPosition(ev); 323 324 if (mXOverviewAnim != null) { 325 mXOverviewAnim.setPlayFraction(xProgress); 326 } 327 if (mYOverviewAnim != null) { 328 mYOverviewAnim.updateValue(yProgress); 329 } 330 return true; 331 } 332 333 @Override onDragEnd(PointF velocity)334 public void onDragEnd(PointF velocity) { 335 boolean horizontalFling = mSwipeDetector.isFling(velocity.x); 336 boolean verticalFling = mSwipeDetector.isFling(velocity.y); 337 boolean noFling = !horizontalFling && !verticalFling; 338 if (mMotionPauseDetector.isPaused() && noFling) { 339 // Going to Overview. 340 InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH); 341 342 StateAnimationConfig config = new StateAnimationConfig(); 343 config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW; 344 Animator overviewAnim = mLauncher.getStateManager().createAtomicAnimation( 345 mStartState, OVERVIEW, config); 346 overviewAnim.addListener(new AnimatorListenerAdapter() { 347 @Override 348 public void onAnimationEnd(Animator animation) { 349 onAnimationToStateCompleted(OVERVIEW); 350 // Animate the icon after onAnimationToStateCompleted() so it doesn't clobber. 351 mRecentsView.startIconFadeInOnGestureComplete(); 352 } 353 }); 354 overviewAnim.start(); 355 356 // Create an empty state transition so StateListeners get onStateTransitionStart(). 357 mLauncher.getStateManager().createAnimationToNewWorkspace( 358 OVERVIEW, config.duration, StateAnimationConfig.SKIP_ALL_ANIMATIONS) 359 .dispatchOnStart(); 360 return; 361 } 362 InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS); 363 cancelAnimations(); 364 365 final LauncherState targetState; 366 if (horizontalFling && verticalFling) { 367 if (velocity.x < 0) { 368 // Flinging left and up or down both go back home. 369 targetState = NORMAL; 370 } else { 371 if (velocity.y > 0) { 372 // Flinging right and down goes to quick switch. 373 targetState = QUICK_SWITCH_FROM_HOME; 374 } else { 375 // Flinging up and right could go either home or to quick switch. 376 // Determine the target based on the higher velocity. 377 targetState = Math.abs(velocity.x) > Math.abs(velocity.y) 378 ? QUICK_SWITCH_FROM_HOME : NORMAL; 379 } 380 } 381 } else if (horizontalFling) { 382 targetState = velocity.x > 0 ? QUICK_SWITCH_FROM_HOME : NORMAL; 383 } else if (verticalFling) { 384 targetState = velocity.y > 0 ? QUICK_SWITCH_FROM_HOME : NORMAL; 385 } else { 386 // If user isn't flinging, just snap to the closest state. 387 boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f; 388 boolean passedVerticalThreshold = mYOverviewAnim.value > 1f; 389 targetState = passedHorizontalThreshold && !passedVerticalThreshold 390 ? QUICK_SWITCH_FROM_HOME : NORMAL; 391 } 392 393 // Animate the various components to the target state. 394 395 float xProgress = mXOverviewAnim.getProgressFraction(); 396 float startXProgress = Utilities.boundToRange(xProgress 397 + velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f); 398 final float endXProgress = targetState == NORMAL ? 0 : 1; 399 long xDuration = BaseSwipeDetector.calculateDuration(velocity.x, 400 Math.abs(endXProgress - startXProgress)); 401 ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer(); 402 xOverviewAnim.setFloatValues(startXProgress, endXProgress); 403 xOverviewAnim.setDuration(xDuration) 404 .setInterpolator(scrollInterpolatorForVelocity(velocity.x)); 405 mXOverviewAnim.dispatchOnStart(); 406 407 boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL; 408 409 float yProgress = mYOverviewAnim.value; 410 float startYProgress = Utilities.boundToRange(yProgress 411 - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, mMaxYProgress); 412 final float endYProgress; 413 if (flingUpToNormal) { 414 endYProgress = 1; 415 } else if (targetState == NORMAL) { 416 // Keep overview at its current scale/translationY as it slides off the screen. 417 endYProgress = startYProgress; 418 } else { 419 endYProgress = 0; 420 } 421 float yDistanceToCover = Math.abs(endYProgress - startYProgress) * mYRange; 422 long yDuration = (long) (yDistanceToCover / Math.max(1f, Math.abs(velocity.y))); 423 ValueAnimator yOverviewAnim = mYOverviewAnim.animateToValue(startYProgress, endYProgress); 424 yOverviewAnim.setDuration(yDuration); 425 mYOverviewAnim.updateValue(startYProgress); 426 427 ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); 428 if (flingUpToNormal && !mIsHomeScreenVisible) { 429 // We are flinging to home while workspace is invisible, run the same staggered 430 // animation as from an app. 431 StateAnimationConfig config = new StateAnimationConfig(); 432 // Update mNonOverviewAnim to do nothing so it doesn't interfere. 433 config.animFlags = SKIP_ALL_ANIMATIONS; 434 updateNonOverviewAnim(targetState, config); 435 nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer(); 436 mNonOverviewAnim.dispatchOnStart(); 437 438 new WorkspaceRevealAnim(mLauncher, false /* animateOverviewScrim */).start(); 439 } else { 440 boolean canceled = targetState == NORMAL; 441 if (canceled) { 442 // Let the state manager know that the animation didn't go to the target state, 443 // but don't clean up yet (we already clean up when the animation completes). 444 mNonOverviewAnim.getTarget().removeListener(mClearStateOnCancelListener); 445 mNonOverviewAnim.dispatchOnCancel(); 446 } 447 float startProgress = mNonOverviewAnim.getProgressFraction(); 448 float endProgress = canceled ? 0 : 1; 449 nonOverviewAnim.setFloatValues(startProgress, endProgress); 450 mNonOverviewAnim.dispatchOnStart(); 451 } 452 if (targetState == QUICK_SWITCH_FROM_HOME) { 453 // Navigating to quick switch, add scroll feedback since the first time is not 454 // considered a scroll by the RecentsView. 455 VibratorWrapper.INSTANCE.get(mLauncher).vibrate( 456 RecentsView.SCROLL_VIBRATION_PRIMITIVE, 457 RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE, 458 RecentsView.SCROLL_VIBRATION_FALLBACK); 459 } else { 460 InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH); 461 } 462 463 nonOverviewAnim.setDuration(Math.max(xDuration, yDuration)); 464 mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState)); 465 466 xOverviewAnim.start(); 467 yOverviewAnim.start(); 468 nonOverviewAnim.start(); 469 } 470 onAnimationToStateCompleted(LauncherState targetState)471 private void onAnimationToStateCompleted(LauncherState targetState) { 472 mLauncher.getStatsLogManager().logger() 473 .withSrcState(LAUNCHER_STATE_HOME) 474 .withDstState(targetState.statsLogOrdinal) 475 .log(getLauncherAtomEvent(mStartState.statsLogOrdinal, targetState.statsLogOrdinal, 476 targetState == QUICK_SWITCH_FROM_HOME 477 ? LAUNCHER_QUICKSWITCH_RIGHT 478 : targetState.ordinal > mStartState.ordinal 479 ? LAUNCHER_UNKNOWN_SWIPEUP 480 : LAUNCHER_UNKNOWN_SWIPEDOWN)); 481 482 if (targetState == QUICK_SWITCH_FROM_HOME) { 483 InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH); 484 } else if (targetState == OVERVIEW) { 485 InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS); 486 } 487 488 mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState)); 489 } 490 cancelAnimations()491 private void cancelAnimations() { 492 if (mNonOverviewAnim != null) { 493 mNonOverviewAnim.getAnimationPlayer().cancel(); 494 } 495 if (mXOverviewAnim != null) { 496 mXOverviewAnim.getAnimationPlayer().cancel(); 497 } 498 if (mYOverviewAnim != null) { 499 mYOverviewAnim.cancelAnimation(); 500 } 501 mMotionPauseDetector.clear(); 502 } 503 clearState()504 private void clearState() { 505 cancelAnimations(); 506 mNonOverviewAnim = null; 507 mXOverviewAnim = null; 508 mYOverviewAnim = null; 509 mIsHomeScreenVisible = true; 510 mSwipeDetector.finishedScrolling(); 511 mRecentsView.setOnEmptyMessageUpdatedListener(null); 512 } 513 } 514