Stash taskbar when IME is present, including during gestures

- SysUI removes SYSUI_STATE_IME_SHOWING when starting a gesture from an app, but because unstashing has implications on the gesture transition (e.g. clips the bottom of the app), we defer handling the ime hiding until the gesture settles. Repurposed the flow that swaps the taskbar background during the gesture to support this case as well.
- Delay the unstash when IME is closing, to align with the end of the IME exit transition
- Remove TaskbarViewController.ALPHA_INDEX_IME now that we stash when IME is opening, since stashing already hides the taskbar icons
- Also support passing a starting progress to the stashed handle reveal animation, to allow it to be reversed when cancelled. For example, when returning to an app that has IME showing, we first start unstashing because we're in an app, but then we get the signal that IME is attached so we stash again almost immediately (within a frame or two).

Test: In both 3 button and fully gestural, open a keyboard in an app, ensure taskbar gets out of the way and then reappears at the end when the keyboard is dismissed
Bug: 202511986
Change-Id: I93c298a98ba369ea6310466ff3f802231c582687
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 24a88a4..90c035f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -46,16 +46,13 @@
                 }
             };
 
-    // Initialized in init.
-    TaskbarControllers mControllers;
-
     public FallbackTaskbarUIController(RecentsActivity recentsActivity) {
         mRecentsActivity = recentsActivity;
     }
 
     @Override
     protected void init(TaskbarControllers taskbarControllers) {
-        mControllers = taskbarControllers;
+        super.init(taskbarControllers);
 
         mRecentsActivity.setTaskbarUIController(this);
         mRecentsActivity.getStateManager().addStateListener(mStateListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 1c0c773..7d23439 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -25,7 +25,6 @@
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.TaskTransitionSpec;
-import android.view.View;
 import android.view.WindowManagerGlobal;
 
 import androidx.annotation.NonNull;
@@ -62,7 +61,6 @@
             this::onStashedInAppChanged;
 
     // Initialized in init.
-    private TaskbarControllers mControllers;
     private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
     private TaskbarKeyguardController mKeyguardController;
     private final TaskbarLauncherStateController
@@ -83,7 +81,7 @@
 
     @Override
     protected void init(TaskbarControllers taskbarControllers) {
-        mControllers = taskbarControllers;
+        super.init(taskbarControllers);
 
         mTaskbarLauncherStateController.init(mControllers, mLauncher);
         mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
@@ -168,10 +166,6 @@
         return mControllers.taskbarDragController.isDragging();
     }
 
-    public View getRootView() {
-        return mControllers.taskbarActivityContext.getDragLayer();
-    }
-
     @Override
     protected void onStashedInAppChanged() {
         onStashedInAppChanged(mLauncher.getDeviceProfile());
@@ -251,4 +245,12 @@
         mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
                 instanceId);
     }
+
+    @Override
+    public void setSystemGestureInProgress(boolean inProgress) {
+        super.setSystemGestureInProgress(inProgress);
+        // Launcher's ScrimView will draw the background throughout the gesture. But once the
+        // gesture ends, start drawing taskbar's background again since launcher might stop drawing.
+        forceHideBackground(inProgress);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 0565f7e..f6e0426 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
-import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_IME;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -140,11 +139,6 @@
         mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
         mNavButtonTranslationYMultiplier.value = 1;
 
-        mPropertyHolders.add(new StatePropertyHolder(
-                mControllers.taskbarViewController.getTaskbarIconAlpha()
-                        .getProperty(ALPHA_INDEX_IME),
-                flags -> (flags & FLAG_IME_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
-
         boolean isThreeButtonNav = mContext.isThreeButtonNav();
         // IME switcher
         View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
@@ -196,7 +190,7 @@
             }
 
             // Animate taskbar background when any of these flags are enabled
-            int flagsToShowBg = FLAG_IME_VISIBLE | FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
+            int flagsToShowBg = FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
                     | FLAG_NOTIFICATION_SHADE_EXPANDED;
             mPropertyHolders.add(new StatePropertyHolder(
                     mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 2c80f06..22ca63f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -16,6 +16,8 @@
 package com.android.launcher3.taskbar;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.graphics.Outline;
@@ -23,8 +25,6 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.RevealOutlineAnimation;
@@ -66,7 +66,10 @@
     private final Rect mStashedHandleBounds = new Rect();
     private float mStashedHandleRadius;
 
-    private boolean mIsAtStashedRevealBounds = true;
+    // When the reveal animation is cancelled, we can assume it's about to create a new animation,
+    // which should start off at the same point the cancelled one left off.
+    private float mStartProgressForNextRevealAnim;
+    private boolean mWasLastRevealAnimReversed;
 
     public StashedHandleViewController(TaskbarActivityContext activity,
             StashedHandleView stashedHandleView) {
@@ -148,15 +151,27 @@
      * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
      * morphs into the size of where the taskbar icons will be.
      */
-    public @Nullable Animator createRevealAnimToIsStashed(boolean isStashed) {
-        if (mIsAtStashedRevealBounds == isStashed) {
-            return null;
-        }
-        mIsAtStashedRevealBounds = isStashed;
+    public Animator createRevealAnimToIsStashed(boolean isStashed) {
         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
                 mStashedHandleRadius, mStashedHandleRadius,
                 mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
-        return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed);
+
+        boolean isReversed = !isStashed;
+        boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
+        mWasLastRevealAnimReversed = isReversed;
+        if (changingDirection) {
+            mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
+        }
+
+        ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
+                isReversed, mStartProgressForNextRevealAnim);
+        revealAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
+            }
+        });
+        return revealAnim;
     }
 
     public void onIsStashed(boolean isStashed) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 248c40d..806b390 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -44,6 +44,7 @@
     private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
     private final AnimatedFloat mNotificationShadeBgTaskbar = new AnimatedFloat(
             this::updateBackgroundAlpha);
+    private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
     // Used to hide our background color when someone else (e.g. ScrimView) is handling it.
     private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
 
@@ -74,6 +75,7 @@
         mBgTaskbar.value = 1;
         mKeyguardBgTaskbar.value = 1;
         mNotificationShadeBgTaskbar.value = 1;
+        mImeBgTaskbar.value = 1;
         mBgOverride.value = 1;
         updateBackgroundAlpha();
     }
@@ -108,6 +110,10 @@
         return mNotificationShadeBgTaskbar;
     }
 
+    public AnimatedFloat getImeBgTaskbar() {
+        return mImeBgTaskbar;
+    }
+
     public AnimatedFloat getOverrideBackgroundAlpha() {
         return mBgOverride;
     }
@@ -119,7 +125,7 @@
     private void updateBackgroundAlpha() {
         final float bgNavbar = mBgNavbar.value;
         final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
-                * mNotificationShadeBgTaskbar.value;
+                * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value;
         mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
         mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 60842c8..8965dc4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -19,6 +19,7 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
 import android.animation.Animator;
@@ -48,11 +49,13 @@
     public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
     public static final int FLAG_STASHED_IN_APP_EMPTY = 1 << 3; // no hotseat icons
     public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
-    public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 5;
+    public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
+    public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
 
     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
     public static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
-            | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP;
+            | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
+            | FLAG_STASHED_IN_APP_IME;
 
     /**
      * How long to stash/unstash when manually invoked via long press.
@@ -60,6 +63,11 @@
     public static final long TASKBAR_STASH_DURATION = 300;
 
     /**
+     * How long to stash/unstash when keyboard is appearing/disappearing.
+     */
+    private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
+
+    /**
      * The scale TaskbarView animates to when being stashed.
      */
     private static final float STASHED_TASKBAR_SCALE = 0.5f;
@@ -100,6 +108,7 @@
     private TaskbarControllers mControllers;
     // Taskbar background properties.
     private AnimatedFloat mTaskbarBackgroundOffset;
+    private AnimatedFloat mTaskbarImeBgAlpha;
     // TaskbarView icon properties.
     private AlphaProperty mIconAlphaForStash;
     private AnimatedFloat mIconScaleForStash;
@@ -113,6 +122,8 @@
     private int mState;
 
     private @Nullable AnimatorSet mAnimator;
+    private boolean mIsSystemGestureInProgress;
+    private boolean mIsImeShowing;
 
     // Evaluate whether the handle should be stashed
     private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
@@ -137,6 +148,7 @@
 
         TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
         mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
+        mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
 
         TaskbarViewController taskbarViewController = controllers.taskbarViewController;
         mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
@@ -271,17 +283,21 @@
      * Create a stash animation and save to {@link #mAnimator}.
      * @param isStashed whether it's a stash animation or an unstash animation
      * @param duration duration of the animation
+     * @param startDelay how many milliseconds to delay the animation after starting it.
      */
-    private void createAnimToIsStashed(boolean isStashed, long duration) {
+    private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
         if (mAnimator != null) {
             mAnimator.cancel();
         }
         mAnimator = new AnimatorSet();
 
         if (!supportsVisualStashing()) {
-            // Just hide/show the icons instead of stashing into a handle.
+            // Just hide/show the icons and background instead of stashing into a handle.
             mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
                     .setDuration(duration));
+            mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
+                    hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
+            mAnimator.setStartDelay(startDelay);
             mAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -332,11 +348,8 @@
             );
         }
 
-        Animator stashedHandleRevealAnim = mControllers.stashedHandleViewController
-                .createRevealAnimToIsStashed(isStashed);
-        if (stashedHandleRevealAnim != null) {
-            fullLengthAnimatorSet.play(stashedHandleRevealAnim);
-        }
+        fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
+                .createRevealAnimToIsStashed(isStashed));
         // Return the stashed handle to its default scale in case it was changed as part of the
         // feedforward hint. Note that the reveal animation above also visually scales it.
         fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
@@ -348,6 +361,7 @@
 
         mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
                 secondHalfAnimatorSet);
+        mAnimator.setStartDelay(startDelay);
         mAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -406,6 +420,10 @@
         mStatePropertyHolder.setState(mState, duration, true);
     }
 
+    public void applyState(long duration, long startDelay) {
+        mStatePropertyHolder.setState(mState, duration, startDelay, true);
+    }
+
     public Animator applyStateWithoutStart() {
         return applyStateWithoutStart(TASKBAR_STASH_DURATION);
     }
@@ -414,11 +432,50 @@
         return mStatePropertyHolder.setState(mState, duration, false);
     }
 
+    /**
+     * Should be called when a system gesture starts and settles, so we can defer updating
+     * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
+     */
+    public void setSystemGestureInProgress(boolean inProgress) {
+        mIsSystemGestureInProgress = inProgress;
+        // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+        if (!mIsSystemGestureInProgress) {
+            updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+            applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
+        }
+    }
+
+    /**
+     * When hiding the IME, delay the unstash animation to align with the end of the transition.
+     */
+    private long getTaskbarStashStartDelayForIme() {
+        if (mIsImeShowing) {
+            // Only delay when IME is exiting, not entering.
+            return 0;
+        }
+        // This duration is based on input_method_extract_exit.xml.
+        long imeExitDuration = mControllers.taskbarActivityContext.getResources()
+                .getInteger(android.R.integer.config_shortAnimTime);
+        return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
+    }
+
     /** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
     public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
+        long animDuration = TASKBAR_STASH_DURATION;
+        long startDelay = 0;
+
         updateStateForFlag(FLAG_STASHED_IN_APP_PINNED,
                 hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
-        applyState(skipAnim ? 0 : TASKBAR_STASH_DURATION);
+
+        // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+        mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
+        if (!mIsSystemGestureInProgress) {
+            updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+            animDuration = TASKBAR_STASH_DURATION_FOR_IME;
+            startDelay = getTaskbarStashStartDelayForIme();
+        }
+
+        applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
     }
 
     /**
@@ -474,16 +531,34 @@
             mStashCondition = stashCondition;
         }
 
+        /**
+         * @see #setState(int, long, long, boolean) with a default startDelay = 0.
+         */
         public Animator setState(int flags, long duration, boolean start) {
+            return setState(flags, duration, 0 /* startDelay */, start);
+        }
+
+        /**
+         * Applies the latest state, potentially calling onStateChangeApplied() and creating a new
+         * animation (stored in mAnimator) which is started if {@param start} is true.
+         * @param flags The latest flags to apply (see the top of this file).
+         * @param duration The length of the animation.
+         * @param startDelay How long to delay the animation after calling start().
+         * @param start Whether to start mAnimator immediately.
+         * @return mAnimator if mIsStashed changed, else null.
+         */
+        public Animator setState(int flags, long duration, long startDelay, boolean start) {
+            int changedFlags = mPrevFlags ^ flags;
             if (mPrevFlags != flags) {
-                int changedFlags = mPrevFlags ^ flags;
                 onStateChangeApplied(changedFlags);
                 mPrevFlags = flags;
             }
             boolean isStashed = mStashCondition.test(flags);
             if (mIsStashed != isStashed) {
                 mIsStashed = isStashed;
-                createAnimToIsStashed(mIsStashed, duration);
+
+                // This sets mAnimator.
+                createAnimToIsStashed(mIsStashed, duration, startDelay);
                 if (start) {
                     mAnimator.start();
                 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index d8360e0..f713dca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import android.graphics.Rect;
+import android.view.View;
 
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -29,7 +30,12 @@
 
     public static final TaskbarUIController DEFAULT = new TaskbarUIController();
 
-    protected void init(TaskbarControllers taskbarControllers) { }
+    // Initialized in init.
+    protected TaskbarControllers mControllers;
+
+    protected void init(TaskbarControllers taskbarControllers) {
+        mControllers = taskbarControllers;
+    }
 
     protected void onDestroy() { }
 
@@ -46,4 +52,16 @@
     }
 
     public void onTaskbarIconLaunched(WorkspaceItemInfo item) { }
+
+    public View getRootView() {
+        return mControllers.taskbarActivityContext.getDragLayer();
+    }
+
+    /**
+     * Called when swiping from the bottom nav region in fully gestural mode.
+     * @param inProgress True if the animation started, false if we just settled on an end target.
+     */
+    public void setSystemGestureInProgress(boolean inProgress) {
+        mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index c47bde9..445a23b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -45,12 +45,11 @@
     private static final Runnable NO_OP = () -> { };
 
     public static final int ALPHA_INDEX_HOME = 0;
-    public static final int ALPHA_INDEX_IME = 1;
-    public static final int ALPHA_INDEX_KEYGUARD = 2;
-    public static final int ALPHA_INDEX_STASH = 3;
-    public static final int ALPHA_INDEX_RECENTS_DISABLED = 4;
-    public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 5;
-    private static final int NUM_ALPHA_CHANNELS = 6;
+    public static final int ALPHA_INDEX_KEYGUARD = 1;
+    public static final int ALPHA_INDEX_STASH = 2;
+    public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
+    public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
+    private static final int NUM_ALPHA_CHANNELS = 5;
 
     private final TaskbarActivityContext mActivity;
     private final TaskbarView mTaskbarView;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 45b2081..c9cbba1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -121,8 +121,8 @@
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.function.Consumer;
 
 /**
@@ -687,14 +687,17 @@
     }
 
     private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
+        boolean isFirstCreation = mLauncherTransitionController == null;
         mLauncherTransitionController = anim;
-        mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
-            // Wait until the gesture is started (touch slop was passed) to start in sync with
-            // mWindowTransitionController. This ensures we don't hide the taskbar background when
-            // long pressing to stash it, for instance.
-            mLauncherTransitionController.getNormalController().dispatchOnStart();
-            updateLauncherTransitionProgress();
-        });
+        if (isFirstCreation) {
+            mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
+                // Wait until the gesture is started (touch slop was passed) to start in sync with
+                // mWindowTransitionController. This ensures we don't hide the taskbar background
+                // when long pressing to stash it, for instance.
+                mLauncherTransitionController.getNormalController().dispatchOnStart();
+                updateLauncherTransitionProgress();
+            });
+        }
     }
 
     public Intent getLaunchIntent() {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index e15aa92..a566765 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -30,6 +30,7 @@
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -398,6 +399,11 @@
      * (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
      */
     public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
+        TaskbarUIController taskbarUIController = getTaskbarController();
+        if (taskbarUIController != null) {
+            taskbarUIController.setSystemGestureInProgress(false);
+            return taskbarUIController.getRootView();
+        }
         return null;
     }
 
@@ -533,6 +539,16 @@
             pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
                     recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
             pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
+
+            pa.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    TaskbarUIController taskbarUIController = getTaskbarController();
+                    if (taskbarUIController != null) {
+                        taskbarUIController.setSystemGestureInProgress(true);
+                    }
+                }
+            });
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index aa9435b..719c2ae 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -25,12 +25,10 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.content.Context;
 import android.graphics.Rect;
 import android.view.MotionEvent;
-import android.view.View;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -132,19 +130,6 @@
                 pa.addFloat(getDepthController(),
                         new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
                         fromDepthRatio, toDepthRatio, LINEAR);
-
-                pa.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        LauncherTaskbarUIController taskbarUIController =
-                                activity.getTaskbarUIController();
-                        if (taskbarUIController != null) {
-                            // Launcher's ScrimView will draw the background throughout the gesture.
-                            taskbarUIController.forceHideBackground(true);
-                        }
-                    }
-                });
-
             }
         };
 
@@ -366,16 +351,4 @@
                 return NORMAL;
         }
     }
-
-    @Override
-    public View onSettledOnEndTarget(@Nullable GestureEndTarget endTarget) {
-        View superRet = super.onSettledOnEndTarget(endTarget);
-        LauncherTaskbarUIController taskbarUIController = getTaskbarController();
-        if (taskbarUIController != null) {
-            // Start drawing taskbar's background again since launcher might stop drawing.
-            taskbarUIController.forceHideBackground(false);
-            return taskbarUIController.getRootView();
-        }
-        return superRet;
-    }
 }
diff --git a/src/com/android/launcher3/anim/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index f99dabc..f5a746f 100644
--- a/src/com/android/launcher3/anim/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -26,9 +26,27 @@
     /** Sets the progress, from 0 to 1, of the reveal animation. */
     abstract void setProgress(float progress);
 
+    /**
+     * @see #createRevealAnimator(View, boolean, float) where startProgress is set to 0.
+     */
     public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
-        ValueAnimator va =
-                isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
+        return createRevealAnimator(revealView, isReversed, 0f /* startProgress */);
+    }
+
+    /**
+     * Animates the given View's ViewOutline according to {@link #setProgress(float)}.
+     * @param revealView The View whose outline we are animating.
+     * @param isReversed Whether we are hiding rather than revealing the View.
+     * @param startProgress The progress at which to start the newly created animation. Useful if
+     * the previous reveal animation was cancelled and we want to create a new animation where it
+     * left off. Note that if isReversed=true, we start at 1 - startProgress (and go to 0).
+     * @return The Animator, which the caller must start.
+     */
+    public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed,
+            float startProgress) {
+        ValueAnimator va = isReversed
+                ? ValueAnimator.ofFloat(1f - startProgress, 0f)
+                : ValueAnimator.ofFloat(startProgress, 1f);
         final float elevation = revealView.getElevation();
 
         va.addListener(new AnimatorListenerAdapter() {