Translate the taskbar icons to match nav handle shape.

- We need to reset icon alignment whenever icon layout bound
  changes. This fixes the issue where we build an icon
  alignment animator before any of the views are laid out.
- Separated animation logic between.
  createTransientAnimToIsStashed and createAnimToIsStashed
* The values still require a bit more tuning but this gets us
  a lot closer to spec for many of the motion polish.

Bug: 267806083
Bug: 246634367
Bug: 246635237
Test: manual
Change-Id: Id122134b22ef4e418ce632e4a8137239dc8bb313
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 6d778ef..ff7c138 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
-import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
 
 import android.animation.Animator;
 
@@ -93,7 +92,8 @@
     }
 
     private void animateToRecentsState(RecentsState toState) {
-        Animator anim = createAnimToRecentsState(toState, TASKBAR_STASH_DURATION);
+        Animator anim = createAnimToRecentsState(toState,
+                mControllers.taskbarStashController.getStashDuration());
         if (anim != null) {
             anim.start();
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index ac584bf..dcef6d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -394,6 +394,11 @@
     }
 
     @Override
+    protected void onIconLayoutBoundsChanged() {
+        mTaskbarLauncherStateController.resetIconAlignment();
+    }
+
+    @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         super.dumpLogs(prefix, pw);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 0f25ba1..f082fc6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.PrintWriter;
 
@@ -152,6 +151,14 @@
         }
     }
 
+    /**
+     * Returns the stashed handle bounds.
+     * @param out The destination rect.
+     */
+    public void getStashedHandleBounds(Rect out) {
+        out.set(mStashedHandleBounds);
+    }
+
     private void initRegionSampler() {
         mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
                 new RegionSamplingHelper.SamplingCallback() {
@@ -194,16 +201,19 @@
      */
     public Animator createRevealAnimToIsStashed(boolean isStashed) {
         Rect visualBounds = new Rect(mControllers.taskbarViewController.getIconLayoutBounds());
+        float startRadius = mStashedHandleRadius;
 
         if (DisplayController.isTransientTaskbar(mActivity)) {
             // Account for the full visual height of the transient taskbar.
             int heightDiff = (mTaskbarSize - visualBounds.height()) / 2;
             visualBounds.top -= heightDiff;
             visualBounds.bottom += heightDiff;
+
+            startRadius = visualBounds.height() / 2f;
         }
 
         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
-                mStashedHandleRadius, mStashedHandleRadius, visualBounds, mStashedHandleBounds);
+                startRadius, mStashedHandleRadius, visualBounds, mStashedHandleBounds);
 
         boolean isReversed = !isStashed;
         boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index 82f27ae..2d20582 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -21,6 +21,7 @@
 import android.graphics.Paint
 import android.graphics.Path
 import com.android.launcher3.R
+import com.android.launcher3.Utilities.mapRange
 import com.android.launcher3.Utilities.mapToRange
 import com.android.launcher3.anim.Interpolators
 import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
@@ -51,6 +52,9 @@
     private val invertedLeftCornerPath: Path = Path()
     private val invertedRightCornerPath: Path = Path()
 
+    private val stashedHandleWidth =
+        context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width)
+
     init {
         paint.color = context.getColor(R.color.taskbar_background)
         paint.flags = Paint.ANTI_ALIAS_FLAG
@@ -111,12 +115,11 @@
             canvas.drawPath(invertedRightCornerPath, paint)
         } else {
             // Approximates the stash/unstash animation to transform the background.
-            val scaleFactor = backgroundHeight / maxBackgroundHeight
-            val width = transientBackgroundBounds.width()
-            val widthScale = mapToRange(scaleFactor, 0f, 1f, 0.2f, 1f, Interpolators.LINEAR)
-            val newWidth = widthScale * width
-            val delta = width - newWidth
-            canvas.translate(0f, bottomMargin * ((1f - scaleFactor) / 2f))
+            val progress = backgroundHeight / maxBackgroundHeight
+            val fullWidth = transientBackgroundBounds.width()
+            val newWidth = mapRange(progress, stashedHandleWidth.toFloat(), fullWidth.toFloat())
+            val delta = fullWidth - newWidth
+            canvas.translate(0f, bottomMargin * ((1f - progress) / 2f))
 
             // Draw shadow.
             val shadowAlpha =
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 80cdbe9..a25ec69 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
-import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
 import static com.android.systemui.animation.Interpolators.EMPHASIZED;
 
@@ -41,6 +40,7 @@
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
+import com.android.launcher3.util.window.RefreshRateTracker;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.views.RecentsView;
@@ -139,8 +139,7 @@
         mIconAlphaForHome = mControllers.taskbarViewController
                 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME);
 
-        mIconAlignment.finishAnimation();
-        onIconAlignmentRatioChanged();
+        resetIconAlignment();
 
         mLauncher.getStateManager().addStateListener(mStateListener);
 
@@ -234,7 +233,7 @@
     }
 
     public void applyState() {
-        applyState(TASKBAR_STASH_DURATION);
+        applyState(mControllers.taskbarStashController.getStashDuration());
     }
 
     public void applyState(long duration) {
@@ -242,7 +241,7 @@
     }
 
     public Animator applyState(boolean start) {
-        return applyState(TASKBAR_STASH_DURATION, start);
+        return applyState(mControllers.taskbarStashController.getStashDuration(), start);
     }
 
     public Animator applyState(long duration, boolean start) {
@@ -329,8 +328,17 @@
                         + mTaskbarBackgroundAlpha.value
                         + " -> " + backgroundAlpha + ": " + duration);
             }
-            animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(backgroundAlpha)
-                    .setDuration(duration));
+
+            Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha
+                    .animateToValue(backgroundAlpha)
+                    .setDuration(duration);
+            // Add a single frame delay to the taskbar bg to avoid too many moving parts during the
+            // app launch animation.
+            taskbarBackgroundAlpha.setStartDelay(
+                    (hasAnyFlag(changedFlags, FLAG_RESUMED) && !goingToLauncher)
+                            ? RefreshRateTracker.getSingleFrameMs(mLauncher)
+                            : 0);
+            animatorSet.play(taskbarBackgroundAlpha);
         }
 
         float cornerRoundness = goingToLauncher ? 0 : 1;
@@ -433,6 +441,14 @@
         return (mState & FLAGS_LAUNCHER) != 0;
     }
 
+    /**
+     * Resets and updates the icon alignment.
+     */
+    protected void resetIconAlignment() {
+        mIconAlignment.finishAnimation();
+        onIconAlignmentRatioChanged();
+    }
+
     private void onIconAlignmentRatioChanged() {
         float currentValue = mIconAlphaForHome.getValue();
         boolean taskbarWillBeVisible = mIconAlignment.value < 1;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 2f69b07..50f5d9e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -17,8 +17,10 @@
 
 import static android.view.HapticFeedbackConstants.LONG_PRESS;
 
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.FORCE_PERSISTENT_TASKBAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
@@ -41,6 +43,7 @@
 import android.view.InsetsController;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -101,11 +104,20 @@
 
     /**
      * How long to stash/unstash when manually invoked via long press.
+     *
+     * Use {@link #getStashDuration()} to query duration
      */
-    public static final long TASKBAR_STASH_DURATION =
+    private static final long TASKBAR_STASH_DURATION =
             InsetsController.ANIMATION_DURATION_RESIZE;
 
     /**
+     * How long to stash/unstash transient taskbar.
+     *
+     * Use {@link #getStashDuration()} to query duration.
+     */
+    private static final long TRANSIENT_TASKBAR_STASH_DURATION = 417;
+
+    /**
      * How long to stash/unstash when keyboard is appearing/disappearing.
      */
     private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
@@ -113,7 +125,7 @@
     /**
      * The scale TaskbarView animates to when being stashed.
      */
-    private static final float STASHED_TASKBAR_SCALE = 0.3f;
+    protected static final float STASHED_TASKBAR_SCALE = 0.5f;
 
     /**
      * How long the hint animation plays, starting on motion down.
@@ -122,6 +134,21 @@
             ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
 
     /**
+     * How long to delay the icon/stash handle alpha.
+     */
+    private static final long TASKBAR_STASH_ALPHA_START_DELAY = 33;
+
+    /**
+     * How long the icon/stash handle alpha animation plays.
+     */
+    private static final long TASKBAR_STASH_ALPHA_DURATION = 50;
+
+    /**
+     * How long to delay the icon/stash handle alpha for the home to app taskbar animation.
+     */
+    private static final long TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY = 66;
+
+    /**
      * The scale that TaskbarView animates to when hinting towards the stashed state.
      */
     private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
@@ -299,7 +326,16 @@
         boolean hideTaskbar = isVisible || !mActivity.isUserSetupComplete();
         updateStateForFlag(FLAG_IN_SETUP, hideTaskbar);
         updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, hideTaskbar);
-        applyState(hideTaskbar ? 0 : TASKBAR_STASH_DURATION);
+        applyState(hideTaskbar ? 0 : getStashDuration());
+    }
+
+    /**
+     * Returns how long the stash/unstash animation should play.
+     */
+    public long getStashDuration() {
+        return DisplayController.isTransientTaskbar(mActivity)
+                ? TRANSIENT_TASKBAR_STASH_DURATION
+                : TASKBAR_STASH_DURATION;
     }
 
     /**
@@ -514,7 +550,10 @@
         }
         mAnimator = new AnimatorSet();
         addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
-        final float stashTranslation = isPhoneMode() ? 0 : (mUnstashedHeight - mStashedHeight);
+        boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
+        final float stashTranslation = isPhoneMode() || isTransientTaskbar
+                ? 0
+                : (mUnstashedHeight - mStashedHeight);
 
         if (!supportsVisualStashing()) {
             // Just hide/show the icons and background instead of stashing into a handle.
@@ -522,8 +561,8 @@
                     .setDuration(duration));
             mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0)
                     .setDuration(duration));
-            mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed ?
-                            stashTranslation : 0)
+            mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed
+                            ? stashTranslation : 0)
                     .setDuration(duration));
             mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
                     hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
@@ -531,6 +570,40 @@
             return;
         }
 
+        // If Hotseat is not the top element during animation to/from Launcher, fade in/out a
+        // already stashed Taskbar.
+        boolean skipStashAnimation = !mControllers.uiController.isHotseatIconOnTopWhenAligned()
+                && hasAnyFlag(changedFlags, FLAG_IN_APP);
+        if (isTransientTaskbar) {
+            createTransientAnimToIsStashed(mAnimator, isStashed, duration, animateBg, changedFlags,
+                    skipStashAnimation);
+        } else {
+            createAnimToIsStashed(mAnimator, isStashed, duration, animateBg, skipStashAnimation,
+                    stashTranslation);
+        }
+
+        mAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mIsStashed = isStashed;
+                onIsStashedChanged(mIsStashed);
+
+                cancelTimeoutIfExists();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimator = null;
+
+                if (!mIsStashed) {
+                    tryStartTaskbarTimeout();
+                }
+            }
+        });
+    }
+
+    private void createAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+            boolean animateBg, boolean skipStashAnimation, float stashTranslation) {
         AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
         // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
         AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
@@ -539,10 +612,6 @@
         final float firstHalfDurationScale;
         final float secondHalfDurationScale;
 
-        // If Hotseat is not the top element during animation to/from Launcher, fade in/out a
-        // already stashed Taskbar.
-        boolean skipStashAnimation = !mControllers.uiController.isHotseatIconOnTopWhenAligned()
-                && hasAnyFlag(changedFlags, FLAG_IN_APP);
         if (isStashed) {
             firstHalfDurationScale = 0.75f;
             secondHalfDurationScale = 0.5f;
@@ -595,10 +664,6 @@
             }
         }
 
-        if (DisplayController.isTransientTaskbar(mActivity)) {
-            fullLengthAnimatorSet.play(mControllers.taskbarViewController
-                    .createRevealAnimToIsStashed(isStashed));
-        }
         fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
                 .createRevealAnimToIsStashed(isStashed));
         // Return the stashed handle to its default scale in case it was changed as part of the
@@ -610,26 +675,73 @@
         secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
         secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
 
-        mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
+        as.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
                 secondHalfAnimatorSet);
-        mAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mIsStashed = isStashed;
-                onIsStashedChanged(mIsStashed);
 
-                cancelTimeoutIfExists();
+    }
+
+    private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+            boolean animateBg, int changedFlags, boolean skipStashAnimation) {
+        Interpolator skipInterpolator = null;
+
+        if (isStashed) {
+            if (animateBg) {
+                play(as, mTaskbarBackgroundOffset.animateToValue(1), 0, duration, EMPHASIZED);
+            } else {
+                as.addListener(AnimatorListeners.forEndCallback(
+                        () -> mTaskbarBackgroundOffset.updateValue(1)));
             }
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mAnimator = null;
+            long alphaStartDelay = duration == 0 ? 0 : (changedFlags == FLAG_IN_APP)
+                    ? TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY
+                    : TASKBAR_STASH_ALPHA_START_DELAY;
+            long alphaDuration = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_DURATION;
+            play(as, mIconAlphaForStash.animateToValue(0), alphaStartDelay, alphaDuration, LINEAR);
+            play(as, mTaskbarStashedHandleAlpha.animateToValue(1), alphaStartDelay,
+                    Math.max(0, duration - alphaStartDelay), LINEAR);
 
-                if (!mIsStashed) {
-                    tryStartTaskbarTimeout();
-                }
+            if (skipStashAnimation) {
+                skipInterpolator = INSTANT;
             }
-        });
+        } else  {
+            if (animateBg) {
+                play(as, mTaskbarBackgroundOffset.animateToValue(0), 0, duration, EMPHASIZED);
+            } else {
+                as.addListener(AnimatorListeners.forEndCallback(
+                        () -> mTaskbarBackgroundOffset.updateValue(0)));
+            }
+
+            long alphaStartDelay = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_START_DELAY;
+            long alphaDuration = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_DURATION;
+            play(as, mIconAlphaForStash.animateToValue(1), alphaStartDelay, alphaDuration, LINEAR);
+            play(as, mTaskbarStashedHandleAlpha.animateToValue(0), 0, alphaDuration, LINEAR);
+
+            if (skipStashAnimation) {
+                skipInterpolator = FINAL_FRAME;
+            }
+        }
+        play(as, mControllers.taskbarViewController
+                .createRevealAnimToIsStashed(isStashed, isInApp()), 0, duration, EMPHASIZED);
+
+        if (skipInterpolator != null) {
+            as.setInterpolator(skipInterpolator);
+        }
+
+        play(as, mControllers.stashedHandleViewController
+                .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
+
+        // 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.
+        as.play(mTaskbarStashedHandleHintScale.animateToValue(1f)
+                .setDuration(isStashed ? duration / 2 : duration));
+    }
+
+    private static void play(AnimatorSet as, Animator a, long startDelay, long duration,
+            Interpolator interpolator) {
+        a.setDuration(duration);
+        a.setStartDelay(startDelay);
+        a.setInterpolator(interpolator);
+        as.play(a);
     }
 
     private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
@@ -711,7 +823,6 @@
         }
     }
 
-
     /**
      * Returns an animator which applies the latest state if mIsStashed is changed, or {@code null}
      * otherwise.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 76b8b6d..10d339b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -75,6 +75,11 @@
 
     protected void onStashedInAppChanged() { }
 
+    /**
+     * Called when taskbar icon layout bounds change.
+     */
+    protected void onIconLayoutBoundsChanged() { }
+
     /** Called when an icon is launched. */
     @CallSuper
     public void onTaskbarIconLaunched(ItemInfo item) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index eddc278..483f711 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -58,6 +58,8 @@
     private static final String TAG = TaskbarView.class.getSimpleName();
 
     private static final float TASKBAR_BACKGROUND_LUMINANCE = 0.30f;
+    private static final Rect sTmpRect = new Rect();
+
     public int mThemeIconsBackground;
 
     private final int[] mTempOutLocation = new int[2];
@@ -339,6 +341,9 @@
                     (right - navSpaceNeeded) - iconEnd;
             iconEnd += offset;
         }
+
+        sTmpRect.set(mIconLayoutBounds);
+
         // Layout the children
         mIconLayoutBounds.right = iconEnd;
         mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
@@ -379,6 +384,10 @@
             mIconLayoutBounds.right = center + distanceFromCenter;
             mIconLayoutBounds.left = center - distanceFromCenter;
         }
+
+        if (!sTmpRect.equals(mIconLayoutBounds)) {
+            mControllerCallbacks.notifyIconLayoutBoundsChanged();
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 6252e60..a25ca8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -17,9 +17,9 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.AnimatedFloat.VALUE;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
@@ -28,6 +28,7 @@
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
 
 import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.graphics.Rect;
@@ -310,13 +311,48 @@
      * @param isStashed When true, the icon crops vertically to the size of the stashed handle.
      *                  When false, the reverse happens.
      */
-    public AnimatorSet createRevealAnimToIsStashed(boolean isStashed) {
+    public AnimatorSet createRevealAnimToIsStashed(boolean isStashed, boolean isInApp) {
         AnimatorSet as = new AnimatorSet();
-        for (int i = mTaskbarView.getChildCount() - 1; i >= 0; i--) {
+
+        Rect stashedBounds = new Rect();
+        mControllers.stashedHandleViewController.getStashedHandleBounds(stashedBounds);
+
+        int numChildren = mTaskbarView.getChildCount();
+        // We do not actually modify the width of the icons, but we will use this width to position
+        // the children to overlay the nav handle.
+        float virtualChildWidth = stashedBounds.width() / (float) numChildren;
+
+        for (int i = numChildren - 1; i >= 0; i--) {
             View child = mTaskbarView.getChildAt(i);
-            if (child instanceof BubbleTextView) {
-                as.play(createRevealAnimForView(child, isStashed));
+
+            // Crop the icons to/from the nav handle shape.
+            as.play(createRevealAnimForView(child, isStashed));
+
+            // Translate the icons to/from their locations as the "nav handle."
+            // We look at 'left' and 'right' values to ensure that the children stay within the
+            // bounds of the stashed handle.
+            float iconLeft = child.getLeft();
+            float newLeft = stashedBounds.left + (virtualChildWidth * i);
+            final float croppedTransX;
+            if (iconLeft > newLeft) {
+                float newRight = stashedBounds.right - (virtualChildWidth * (numChildren - 1 - i));
+                croppedTransX = -(child.getLeft() + child.getWidth() - newRight);
+            } else {
+                croppedTransX = newLeft - iconLeft;
             }
+
+            as.play(ObjectAnimator.ofFloat(child, ICON_REVEAL_TRANSLATE_X, isStashed
+                    ? new float[] {croppedTransX}
+                    : new float[] {croppedTransX, 0}));
+
+            float croppedTransY = child.getHeight() - stashedBounds.height();
+            as.play(ObjectAnimator.ofFloat(child, ICON_REVEAL_TRANSLATE_Y, isStashed
+                    ? new float[] {croppedTransY}
+                    : new float[] {croppedTransY, 0}));
+            as.addListener(forEndCallback(() -> {
+                ICON_REVEAL_TRANSLATE_X.set(child, 0f);
+                ICON_REVEAL_TRANSLATE_Y.set(child, 0f);
+            }));
         }
         return as;
     }
@@ -409,7 +445,7 @@
                 float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight;
                 setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator);
 
-                setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+                setter.setFloat(child, ICON_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
 
                 if (mIsHotseatIconOnTopWhenAligned) {
                     setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
@@ -448,7 +484,7 @@
             float childCenter = (child.getLeft() + child.getRight()) / 2f;
             setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, interpolator);
 
-            setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+            setter.setFloat(child, ICON_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
 
             setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator);
         }
@@ -626,6 +662,13 @@
         public void clearTouchInProgress() {
             mTouchInProgress = false;
         }
+
+        /**
+         * Notifies launcher to update icon alignment.
+         */
+        public void notifyIconLayoutBoundsChanged() {
+            mControllers.uiController.onIconLayoutBoundsChanged();
+        }
     }
 
     public static final FloatProperty<View> ICON_TRANSLATE_X =
@@ -636,7 +679,7 @@
                     if (view instanceof BubbleTextView) {
                         ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
                     } else if (view instanceof FolderIcon) {
-                        ((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
+                        ((FolderIcon) view).setTranslationXForTaskbarAlignmentAnimation(v);
                     } else {
                         view.setTranslationX(v);
                     }
@@ -653,4 +696,81 @@
                     return view.getTranslationX();
                 }
             };
+
+    public static final FloatProperty<View> ICON_TRANSLATE_Y =
+            new FloatProperty<View>("taskbarAlignmentTranslateY") {
+
+                @Override
+                public void setValue(View view, float v) {
+                    if (view instanceof BubbleTextView) {
+                        ((BubbleTextView) view).setTranslationYForTaskbarAlignmentAnimation(v);
+                    } else if (view instanceof FolderIcon) {
+                        ((FolderIcon) view).setTranslationYForTaskbarAlignmentAnimation(v);
+                    } else {
+                        view.setTranslationY(v);
+                    }
+                }
+
+                @Override
+                public Float get(View view) {
+                    if (view instanceof BubbleTextView) {
+                        return ((BubbleTextView) view)
+                                .getTranslationYForTaskbarAlignmentAnimation();
+                    } else if (view instanceof FolderIcon) {
+                        return ((FolderIcon) view).getTranslationYForTaskbarAlignmentAnimation();
+                    }
+                    return view.getTranslationY();
+                }
+            };
+
+    public static final FloatProperty<View> ICON_REVEAL_TRANSLATE_X =
+            new FloatProperty<View>("taskbarRevealTranslateX") {
+
+                @Override
+                public void setValue(View view, float v) {
+                    if (view instanceof BubbleTextView) {
+                        ((BubbleTextView) view).setTranslationXForTaskbarRevealAnimation(v);
+                    } else if (view instanceof FolderIcon) {
+                        ((FolderIcon) view).setTranslationXForTaskbarRevealAnimation(v);
+                    } else {
+                        view.setTranslationX(v);
+                    }
+                }
+
+                @Override
+                public Float get(View view) {
+                    if (view instanceof BubbleTextView) {
+                        return ((BubbleTextView) view).getTranslationXForTaskbarRevealAnimation();
+                    } else if (view instanceof FolderIcon) {
+                        return ((FolderIcon) view).getTranslationXForTaskbarRevealAnimation();
+                    }
+                    return view.getTranslationX();
+                }
+            };
+
+    public static final FloatProperty<View> ICON_REVEAL_TRANSLATE_Y =
+            new FloatProperty<View>("taskbarRevealTranslateY") {
+
+                @Override
+                public void setValue(View view, float v) {
+                    if (view instanceof BubbleTextView) {
+                        ((BubbleTextView) view).setTranslationYForTaskbarRevealAnimation(v);
+                    } else if (view instanceof FolderIcon) {
+                        ((FolderIcon) view).setTranslationYForTaskbarRevealAnimation(v);
+                    } else {
+                        view.setTranslationY(v);
+                    }
+                }
+
+                @Override
+                public Float get(View view) {
+                    if (view instanceof BubbleTextView) {
+                        return ((BubbleTextView) view).getTranslationYForTaskbarRevealAnimation();
+                    } else if (view instanceof FolderIcon) {
+                        return ((FolderIcon) view).getTranslationYForTaskbarRevealAnimation();
+                    }
+                    return view.getTranslationY();
+                }
+            };
+
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index edbce10..b109e8a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -104,6 +104,10 @@
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
 
     private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+    private float mTranslationYForTaskbarAlignmentAnimation = 0f;
+
+    private float mTranslationXForTaskbarRevealAnimation = 0f;
+    private float mTranslationYForTaskbarRevealAnimation = 0f;
 
     private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
 
@@ -952,11 +956,17 @@
     }
 
     private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+        super.setTranslationX(mTranslationForReorderBounce.x
+                + mTranslationForReorderPreview.x
                 + mTranslationForMoveFromCenterAnimation.x
-                + mTranslationXForTaskbarAlignmentAnimation);
-        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
-                + mTranslationForMoveFromCenterAnimation.y);
+                + mTranslationXForTaskbarAlignmentAnimation
+                + mTranslationXForTaskbarRevealAnimation
+        );
+        super.setTranslationY(mTranslationForReorderBounce.y
+                + mTranslationForReorderPreview.y
+                + mTranslationForMoveFromCenterAnimation.y
+                + mTranslationYForTaskbarAlignmentAnimation
+                + mTranslationYForTaskbarRevealAnimation);
     }
 
     public void setReorderBounceOffset(float x, float y) {
@@ -1012,6 +1022,51 @@
         return mTranslationXForTaskbarAlignmentAnimation;
     }
 
+    /**
+     * Sets translationX for taskbar to launcher alignment animation
+     */
+    public void setTranslationYForTaskbarAlignmentAnimation(float translationY) {
+        mTranslationYForTaskbarAlignmentAnimation = translationY;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translationY value for taskbar to launcher alignment animation
+     */
+    public float getTranslationYForTaskbarAlignmentAnimation() {
+        return mTranslationYForTaskbarAlignmentAnimation;
+    }
+
+    /**
+     * Sets translationX value for taskbar reveal animation
+     */
+    public void setTranslationXForTaskbarRevealAnimation(float translationX) {
+        mTranslationXForTaskbarRevealAnimation = translationX;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translation values for taskbar reveal animation
+     */
+    public float getTranslationXForTaskbarRevealAnimation() {
+        return mTranslationXForTaskbarRevealAnimation;
+    }
+
+    /**
+     * Sets translationY value for taskbar reveal animation
+     */
+    public void setTranslationYForTaskbarRevealAnimation(float translationY) {
+        mTranslationYForTaskbarRevealAnimation = translationY;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translationY values for taskbar reveal animation
+     */
+    public float getTranslationYForTaskbarRevealAnimation() {
+        return mTranslationYForTaskbarRevealAnimation;
+    }
+
     public View getView() {
         return this;
     }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e69f781..ee1a060 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -135,6 +135,9 @@
 
     private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
     private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+    private float mTranslationYForTaskbarAlignmentAnimation = 0f;
+    private float mTranslationXForTaskbarRevealAnimation = 0f;
+    private float mTranslationYForTaskbarRevealAnimation = 0f;
 
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -416,7 +419,7 @@
                     () -> {
                         mPreviewItemManager.hidePreviewItem(finalIndex, false);
                         mFolder.showItem(item);
-                    }, 
+                    },
                     DragLayer.ANIMATION_END_DISAPPEAR, null);
 
             mFolder.hideItem(item);
@@ -768,11 +771,15 @@
     }
 
     private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+        super.setTranslationX(mTranslationForReorderBounce.x
+                + mTranslationForReorderPreview.x
                 + mTranslationForMoveFromCenterAnimation.x
-                + mTranslationXForTaskbarAlignmentAnimation);
+                + mTranslationXForTaskbarAlignmentAnimation
+                + mTranslationXForTaskbarRevealAnimation);
         super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
-                + mTranslationForMoveFromCenterAnimation.y);
+                + mTranslationForMoveFromCenterAnimation.y
+                + mTranslationYForTaskbarAlignmentAnimation
+                + mTranslationYForTaskbarRevealAnimation);
     }
 
     public void setReorderBounceOffset(float x, float y) {
@@ -787,7 +794,7 @@
     /**
      * Sets translationX value for taskbar to launcher alignment animation
      */
-    public void setTranslationForTaskbarAlignmentAnimation(float translationX) {
+    public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
         mTranslationXForTaskbarAlignmentAnimation = translationX;
         updateTranslation();
     }
@@ -800,6 +807,51 @@
     }
 
     /**
+     * Sets translationY value for taskbar to launcher alignment animation
+     */
+    public void setTranslationYForTaskbarAlignmentAnimation(float translationY) {
+        mTranslationYForTaskbarAlignmentAnimation = translationY;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translation values for taskbar to launcher alignment animation
+     */
+    public float getTranslationYForTaskbarAlignmentAnimation() {
+        return mTranslationYForTaskbarAlignmentAnimation;
+    }
+
+    /**
+     * Sets translationX value for taskbar reveal animation
+     */
+    public void setTranslationXForTaskbarRevealAnimation(float translationX) {
+        mTranslationXForTaskbarRevealAnimation = translationX;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translation values for taskbar reveal animation
+     */
+    public float getTranslationXForTaskbarRevealAnimation() {
+        return mTranslationXForTaskbarRevealAnimation;
+    }
+
+    /**
+     * Sets translationY value for taskbar reveal animation
+     */
+    public void setTranslationYForTaskbarRevealAnimation(float translationY) {
+        mTranslationYForTaskbarRevealAnimation = translationY;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translationY values for taskbar reveal animation
+     */
+    public float getTranslationYForTaskbarRevealAnimation() {
+        return mTranslationYForTaskbarRevealAnimation;
+    }
+
+    /**
      * Sets translation values for move from center animation
      */
     public void setTranslationForMoveFromCenterAnimation(float x, float y) {