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