• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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