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