Improve quick switch from home by tracking both x and y motion

- Add NoButtonQuickSwitchTouchController which uses
  BothAxesSwipeDetector to track horizontal and vertical motion.
- Initially, we only detect swipe left to right to quick switch
  (like before), but then we allow swipe up to either go to
  overview (if you hold) or back home (if you don't hold).
- xDisplacement transitions non-overview components out (e.g. shelf
  and workspace), and translates overview in.
- yDisplacement translates overview up and scales it down

Bug: 126596417
Change-Id: Id679ad84c08246e205c667a78ed5df00d7276258
Merged-In: Id679ad84c08246e205c667a78ed5df00d7276258
diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
index 1e9ac6f..fb89013 100644
--- a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -24,4 +24,8 @@
 
     public enum ShelfAnimState {
     }
+
+    public boolean isPeeking() {
+        return false;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index cdff33b..114fd8e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -21,8 +21,15 @@
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
 import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
 
@@ -40,6 +47,7 @@
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.quickstep.util.ClipAnimationHelper;
@@ -56,6 +64,9 @@
     public static final int INDEX_SHELF_ANIM = 0;
     public static final int INDEX_RECENTS_FADE_ANIM = 1;
     public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
+    public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
+
+    public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
 
     public LauncherAppTransitionManagerImpl(Context context) {
         super(context);
@@ -144,7 +155,7 @@
 
     @Override
     public int getStateElementAnimationsCount() {
-        return 3;
+        return 4;
     }
 
     @Override
@@ -190,6 +201,20 @@
                         .setStiffness(250)
                         .setValues(values)
                         .build(mLauncher);
+            case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
+                AnimatorSetBuilder builder = new AnimatorSetBuilder();
+                builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+                builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
+                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                    builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
+                    builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
+                }
+                LauncherStateManager stateManager = mLauncher.getStateManager();
+                return stateManager.createAtomicAnimation(
+                        stateManager.getCurrentStableState(), OVERVIEW, builder,
+                        ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
+            }
+
             default:
                 return super.createStateElementAnimation(index, values);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 596bc4f..cac170c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -28,17 +28,17 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
 import com.android.launcher3.util.TouchController;
@@ -145,7 +145,7 @@
         ArrayList<TouchController> list = new ArrayList<>();
         list.add(launcher.getDragController());
         if (mode == NO_BUTTON) {
-            list.add(new QuickSwitchTouchController(launcher));
+            list.add(new NoButtonQuickSwitchTouchController(launcher));
             list.add(new NavBarToHomeTouchController(launcher));
             list.add(new FlingAndHoldTouchController(launcher));
         } else {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 25eaab1..ed5dba1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
@@ -205,6 +206,7 @@
             builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
             builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7);
             builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 38a0b66..3231f37 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -16,25 +16,20 @@
 
 package com.android.launcher3.uioverrides.touchcontrollers;
 
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
@@ -46,6 +41,7 @@
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
@@ -79,7 +75,7 @@
 
     @Override
     protected long getAtomicDuration() {
-        return 300;
+        return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
     }
 
     @Override
@@ -179,15 +175,8 @@
                 mPeekAnim.cancel();
             }
 
-            AnimatorSetBuilder builder = new AnimatorSetBuilder();
-            builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
-            if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-                builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
-                builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
-            }
-            AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
-                    NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION);
+            Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+                    INDEX_PAUSE_TO_OVERVIEW_ANIM);
             overviewAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
new file mode 100644
index 0000000..7ffffed
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
+import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.BothAxesSwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.LauncherRecentsView;
+
+/**
+ * Handles quick switching to a recent task from the home screen. To give as much flexibility to
+ * the user as possible, also handles swipe up and hold to go to overview and swiping back home.
+ */
+public class NoButtonQuickSwitchTouchController implements TouchController,
+        BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener {
+
+    /** The minimum progress of the scale/translationY animation until drag end. */
+    private static final float Y_ANIM_MIN_PROGRESS = 0.15f;
+    private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5;
+    private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
+    private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
+
+    private final Launcher mLauncher;
+    private final BothAxesSwipeDetector mSwipeDetector;
+    private final float mXRange;
+    private final float mYRange;
+    private final MotionPauseDetector mMotionPauseDetector;
+    private final float mMotionPauseMinDisplacement;
+
+    private boolean mNoIntercept;
+    private LauncherState mStartState;
+
+    private ShelfPeekAnim mShelfPeekAnim;
+    private boolean mIsHomeScreenVisible = true;
+
+    // As we drag, we control 3 animations: one to get non-overview components out of the way,
+    // and the other two to set overview properties based on x and y progress.
+    private AnimatorPlaybackController mNonOverviewAnim;
+    private AnimatorPlaybackController mXOverviewAnim;
+    private AnimatorPlaybackController mYOverviewAnim;
+
+    public NoButtonQuickSwitchTouchController(Launcher launcher) {
+        mLauncher = launcher;
+        mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
+        mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
+        mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile());
+        mMotionPauseDetector = new MotionPauseDetector(mLauncher);
+        mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
+                R.dimen.motion_pause_detector_min_displacement_from_app);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = !canInterceptTouch(ev);
+            if (mNoIntercept) {
+                return false;
+            }
+
+            // Only detect horizontal swipe for intercept, then we will allow swipe up as well.
+            mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT,
+                    false /* ignoreSlopWhenSettling */);
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mSwipeDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mSwipeDetector.onTouchEvent(ev);
+    }
+
+    private boolean canInterceptTouch(MotionEvent ev) {
+        if (!mLauncher.isInState(LauncherState.NORMAL)) {
+            return false;
+        }
+        if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
+            return false;
+        }
+        int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+        if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        mMotionPauseDetector.clear();
+        if (start) {
+            mShelfPeekAnim = ((QuickstepAppTransitionManagerImpl) mLauncher
+                    .getAppTransitionManager()).getShelfPeekAnim();
+
+            mStartState = mLauncher.getStateManager().getState();
+
+            mMotionPauseDetector.setOnMotionPauseListener(this);
+
+            // We have detected horizontal drag start, now allow swipe up as well.
+            mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP,
+                    false /* ignoreSlopWhenSettling */);
+
+            setupAnimators();
+        }
+    }
+
+    @Override
+    public void onMotionPauseChanged(boolean isPaused) {
+        ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
+        if (shelfState == PEEK) {
+            // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
+            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+            allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher),
+                    new AnimationConfig(), builder);
+            builder.build().setDuration(0).start();
+
+            if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                // Hotseat was hidden, but we need it visible when peeking.
+                mLauncher.getHotseat().setAlpha(1);
+            }
+        }
+        mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
+                ShelfPeekAnim.DURATION);
+        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+    }
+
+    private void setupAnimators() {
+        // Animate the non-overview components (e.g. workspace, shelf) out of the way.
+        AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder();
+        nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR);
+        nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR);
+        nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR);
+        nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
+        updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL);
+        mNonOverviewAnim.dispatchOnStart();
+
+        setupOverviewAnimators();
+    }
+
+    /** Create state animation to control non-overview components. */
+    private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
+            @LauncherStateManager.AnimationComponents int animComponents) {
+        builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW);
+        long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
+        mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
+                builder, accuracy, this::clearState, animComponents);
+    }
+
+    private void setupOverviewAnimators() {
+        final LauncherState fromState = QUICK_SWITCH;
+        final LauncherState toState = OVERVIEW;
+        LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState
+                .getOverviewScaleAndTranslation(mLauncher);
+        LauncherState.ScaleAndTranslation toScaleAndTranslation = toState
+                .getOverviewScaleAndTranslation(mLauncher);
+        // Update RecentView's translationX to have it start offscreen.
+        LauncherRecentsView recentsView = mLauncher.getOverviewPanel();
+        float startScale = Utilities.mapRange(
+                SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS),
+                fromScaleAndTranslation.scale,
+                toScaleAndTranslation.scale);
+        fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale);
+
+        // Set RecentView's initial properties.
+        recentsView.setScaleX(fromScaleAndTranslation.scale);
+        recentsView.setScaleY(fromScaleAndTranslation.scale);
+        recentsView.setTranslationX(fromScaleAndTranslation.translationX);
+        recentsView.setTranslationY(fromScaleAndTranslation.translationY);
+        recentsView.setContentAlpha(1);
+
+        // As we drag right, animate the following properties:
+        //   - RecentsView translationX
+        //   - OverviewScrim
+        AnimatorSet xOverviewAnim = new AnimatorSet();
+        xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X,
+                toScaleAndTranslation.translationX));
+        xOverviewAnim.play(ObjectAnimator.ofFloat(
+                mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
+                toState.getOverviewScrimAlpha(mLauncher)));
+        long xAccuracy = (long) (mXRange * 2);
+        xOverviewAnim.setDuration(xAccuracy);
+        mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy);
+        mXOverviewAnim.dispatchOnStart();
+
+        // As we drag up, animate the following properties:
+        //   - RecentsView translationY
+        //   - RecentsView scale
+        //   - RecentsView fullscreenProgress
+        AnimatorSet yAnimation = new AnimatorSet();
+        Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y,
+                toScaleAndTranslation.translationY);
+        Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
+                toScaleAndTranslation.scale);
+        Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS,
+                fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress());
+        scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
+        fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
+        yAnimation.play(translateYAnim);
+        yAnimation.play(scaleAnim);
+        yAnimation.play(fullscreenProgressAnim);
+        long yAccuracy = (long) (mYRange * 2);
+        yAnimation.setDuration(yAccuracy);
+        mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy);
+        mYOverviewAnim.dispatchOnStart();
+    }
+
+    @Override
+    public boolean onDrag(PointF displacement, MotionEvent ev) {
+        float xProgress = Math.max(0, displacement.x) / mXRange;
+        float yProgress = Math.max(0, -displacement.y) / mYRange;
+        yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f);
+
+        boolean wasHomeScreenVisible = mIsHomeScreenVisible;
+        if (wasHomeScreenVisible && mNonOverviewAnim != null) {
+            mNonOverviewAnim.setPlayFraction(xProgress);
+        }
+        mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress)
+                <= 1 - ALPHA_CUTOFF_THRESHOLD;
+
+        if (wasHomeScreenVisible && !mIsHomeScreenVisible) {
+            // Get the shelf all the way offscreen so it pops up when we decide to peek it.
+            mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0);
+        }
+
+        // Only allow motion pause if the home screen is invisible, since some
+        // home screen elements will appear in the shelf on motion pause.
+        mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible
+                || -displacement.y < mMotionPauseMinDisplacement);
+        mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime());
+
+        if (mIsHomeScreenVisible) {
+            // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
+            mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0);
+        }
+
+        if (mXOverviewAnim != null) {
+            mXOverviewAnim.setPlayFraction(xProgress);
+        }
+        if (mYOverviewAnim != null) {
+            mYOverviewAnim.setPlayFraction(yProgress);
+        }
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(PointF velocity) {
+        boolean horizontalFling = mSwipeDetector.isFling(velocity.x);
+        boolean verticalFling = mSwipeDetector.isFling(velocity.y);
+        boolean noFling = !horizontalFling && !verticalFling;
+        int logAction = noFling ? Touch.SWIPE : Touch.FLING;
+        if (mMotionPauseDetector.isPaused() && noFling) {
+            cancelAnimations();
+
+            Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+                    INDEX_PAUSE_TO_OVERVIEW_ANIM);
+            overviewAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    onAnimationToStateCompleted(OVERVIEW, logAction);
+                }
+            });
+            overviewAnim.start();
+            return;
+        }
+
+        final LauncherState targetState;
+        if (horizontalFling && verticalFling) {
+            // Flinging left and up, left and down, or right and up all go back home.
+            // Only flinging right and down goes to quick switch.
+            targetState = velocity.x < 0 || velocity.y < 0 ? NORMAL : QUICK_SWITCH;
+        } else if (horizontalFling) {
+            targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL;
+        } else if (verticalFling) {
+            targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL;
+        } else {
+            // If user isn't flinging, just snap to the closest state based on x progress.
+            boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
+            targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL;
+        }
+
+        // Animate the various components to the target state.
+
+        float xProgress = mXOverviewAnim.getProgressFraction();
+        float startXProgress = Utilities.boundToRange(xProgress
+                + velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f);
+        final float endXProgress = targetState == NORMAL ? 0 : 1;
+        long xDuration = BaseSwipeDetector.calculateDuration(velocity.x,
+                Math.abs(endXProgress - startXProgress));
+        ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer();
+        xOverviewAnim.setFloatValues(startXProgress, endXProgress);
+        xOverviewAnim.setDuration(xDuration)
+                .setInterpolator(scrollInterpolatorForVelocity(velocity.x));
+        mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x);
+
+        boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
+
+        float yProgress = mYOverviewAnim.getProgressFraction();
+        float startYProgress = Utilities.boundToRange(yProgress
+                - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f);
+        final float endYProgress;
+        if (flingUpToNormal) {
+            endYProgress = 1;
+        } else if (targetState == NORMAL) {
+            // Keep overview at its current scale/translationY as it slides off the screen.
+            endYProgress = startYProgress;
+        } else {
+            endYProgress = 0;
+        }
+        long yDuration = BaseSwipeDetector.calculateDuration(velocity.y,
+                Math.abs(endYProgress - startYProgress));
+        ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
+        yOverviewAnim.setFloatValues(startYProgress, endYProgress);
+        yOverviewAnim.setDuration(yDuration);
+        mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y);
+
+        ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
+        if (flingUpToNormal && !mIsHomeScreenVisible) {
+            // We are flinging to home while workspace is invisible, run the same staggered
+            // animation as from an app.
+            // Update mNonOverviewAnim to do nothing so it doesn't interfere.
+            updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */);
+            nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
+
+            new StaggeredWorkspaceAnim(mLauncher, null, velocity.y,
+                    false /* animateOverviewScrim */).start();
+        } else {
+            boolean canceled = targetState == NORMAL;
+            if (canceled) {
+                // Let the state manager know that the animation didn't go to the target state,
+                // but don't clean up yet (we already clean up when the animation completes).
+                mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable();
+            }
+            float startProgress = mNonOverviewAnim.getProgressFraction();
+            float endProgress = canceled ? 0 : 1;
+            nonOverviewAnim.setFloatValues(startProgress, endProgress);
+            mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress,
+                    horizontalFling ? velocity.x : velocity.y);
+        }
+
+        nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
+        mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction));
+
+        cancelAnimations();
+        xOverviewAnim.start();
+        yOverviewAnim.start();
+        nonOverviewAnim.start();
+    }
+
+    private void onAnimationToStateCompleted(LauncherState targetState, int logAction) {
+        mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+                getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(),
+                LauncherLogProto.ContainerType.NAVBAR,
+                mStartState.containerType,
+                targetState.containerType,
+                mLauncher.getWorkspace().getCurrentPage());
+        mLauncher.getStateManager().goToState(targetState, false, this::clearState);
+    }
+
+    private int getDirectionForLog() {
+        return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT;
+    }
+
+    private void cancelAnimations() {
+        if (mNonOverviewAnim != null) {
+            mNonOverviewAnim.getAnimationPlayer().cancel();
+        }
+        if (mXOverviewAnim != null) {
+            mXOverviewAnim.getAnimationPlayer().cancel();
+        }
+        if (mYOverviewAnim != null) {
+            mYOverviewAnim.getAnimationPlayer().cancel();
+        }
+        mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
+        mMotionPauseDetector.clear();
+    }
+
+    private void clearState() {
+        cancelAnimations();
+        mNonOverviewAnim = null;
+        mXOverviewAnim = null;
+        mYOverviewAnim = null;
+        mIsHomeScreenVisible = true;
+        mSwipeDetector.finishedScrolling();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index c84b818..eefde12 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -53,11 +52,9 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListenerEx;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -169,18 +166,8 @@
 
             @Override
             public void playAtomicAnimation(float velocity) {
-                // Setup workspace with 0 duration to prepare for our staggered animation.
-                LauncherStateManager stateManager = activity.getStateManager();
-                AnimatorSetBuilder builder = new AnimatorSetBuilder();
-                // setRecentsAttachedToAppWindow() will animate recents out.
-                builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
-                stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
-                builder.build().start();
-
-                // Stop scrolling so that it doesn't interfere with the translation offscreen.
-                recentsView.getScroller().forceFinished(true);
-
-                new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
+                new StaggeredWorkspaceAnim(activity, workspaceView, velocity,
+                        true /* animateOverviewScrim */).start();
             }
         };
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
index 41be683..83bc416 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -30,6 +30,7 @@
 /**
  * Animates the shelf between states HIDE, PEEK, and OVERVIEW.
  */
+
 public class ShelfPeekAnim {
 
     public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 1aa5365..5f02de2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.Animator;
@@ -29,11 +30,11 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -41,9 +42,9 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.SpringObjectAnimator;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.views.IconLabelDotView;
+import com.android.quickstep.views.RecentsView;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -75,7 +76,9 @@
      * @param floatingViewOriginalView The FloatingIconView's original view.
      */
     public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView,
-            float velocity) {
+            float velocity, boolean animateOverviewScrim) {
+        prepareToAnimate(launcher);
+
         mVelocity = velocity;
         mOriginalView = floatingViewOriginalView;
 
@@ -133,8 +136,10 @@
             addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows);
         }
 
-        addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
-        addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+        if (animateOverviewScrim) {
+            addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
+            addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+        }
 
         AnimatorListener resetClipListener = new AnimatorListenerAdapter() {
             int numAnimations = mAnimators.size();
@@ -161,6 +166,21 @@
     }
 
     /**
+     * Setup workspace with 0 duration to prepare for our staggered animation.
+     */
+    private void prepareToAnimate(Launcher launcher) {
+        LauncherStateManager stateManager = launcher.getStateManager();
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        // setRecentsAttachedToAppWindow() will animate recents out.
+        builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+        stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
+        builder.build().start();
+
+        // Stop scrolling so that it doesn't interfere with the translation offscreen.
+        launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
+    }
+
+    /**
      * Starts the animation.
      */
     public void start() {
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 0e591ca..3320dae 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -35,6 +36,8 @@
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
@@ -44,6 +47,7 @@
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.ShelfPeekAnim;
 
 /**
  * Scrim used for all-apps and shelf in Overview
@@ -193,8 +197,12 @@
         if (mProgress >= 1) {
             mRemainingScreenColor = 0;
             mShelfColor = 0;
+            ShelfPeekAnim shelfPeekAnim = ((QuickstepAppTransitionManagerImpl)
+                    mLauncher.getAppTransitionManager()).getShelfPeekAnim();
+            LauncherState state = mLauncher.getStateManager().getState();
             if (mSysUINavigationMode == Mode.NO_BUTTON
-                    && mLauncher.getStateManager().getState() == BACKGROUND_APP) {
+                    && (state == BACKGROUND_APP || state == QUICK_SWITCH)
+                    && shelfPeekAnim.isPeeking()) {
                 // Show the shelf background when peeking during swipe up.
                 mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
             }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 848e19f..f673508 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -24,7 +24,8 @@
 import android.animation.AnimatorSet;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.Log;
+
+import androidx.annotation.IntDef;
 
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -32,7 +33,6 @@
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.UiFactory;
 
 import java.io.PrintWriter;
@@ -40,8 +40,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
-import androidx.annotation.IntDef;
-
 /**
  * TODO: figure out what kind of tests we can write for this
  *
diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java
index 8ac9d66..eabd283 100644
--- a/src/com/android/launcher3/anim/AlphaUpdateListener.java
+++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java
@@ -27,7 +27,7 @@
  */
 public class AlphaUpdateListener extends AnimationSuccessListener
         implements AnimatorUpdateListener {
-    private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
+    public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
 
     private View mView;
 
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 2c440bb..4a52795 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -26,15 +26,15 @@
 import android.animation.ValueAnimator;
 import android.util.Log;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
-
 /**
  * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
  * and durations.
@@ -250,6 +250,17 @@
         }
     }
 
+    /**
+     * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
+     * is intended to be used only if you need to cancel but want to defer cleaning up yourself.
+     */
+    public void dispatchOnCancelWithoutCancelRunnable() {
+        Runnable onCancel = mOnCancelRunnable;
+        setOnCancelRunnable(null);
+        dispatchOnCancel();
+        setOnCancelRunnable(onCancel);
+    }
+
     public void dispatchOnCancel() {
         dispatchOnCancelRecursively(mAnim);
     }
@@ -283,10 +294,6 @@
         mOnCancelRunnable = runnable;
     }
 
-    public Runnable getOnCancelRunnable() {
-        return mOnCancelRunnable;
-    }
-
     public void skipToEnd() {
         mSkipToEnd = true;
         for (SpringAnimation spring : mSprings) {
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index c45cd85..fccc120 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -39,6 +39,7 @@
     public static final Interpolator LINEAR = new LinearInterpolator();
 
     public static final Interpolator ACCEL = new AccelerateInterpolator();
+    public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f);
     public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f);
     public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2);
 
@@ -48,6 +49,7 @@
     public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
     public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
     public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
+    public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f);
 
     public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
 
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 60f6ee9..f40f976 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -413,10 +413,7 @@
         } else {
             // Let the state manager know that the animation didn't go to the target state,
             // but don't cancel ourselves (we already clean up when the animation completes).
-            Runnable onCancel = mCurrentAnimation.getOnCancelRunnable();
-            mCurrentAnimation.setOnCancelRunnable(null);
-            mCurrentAnimation.dispatchOnCancel();
-            mCurrentAnimation.setOnCancelRunnable(onCancel);
+            mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable();
 
             endProgress = 0;
             if (progress <= 0) {