Merge "Have responsive taskbar UI during swipe up gesture." into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 89f0f98..174f97f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -192,7 +192,15 @@
      */
     public Animator createAnimToLauncher(@NonNull LauncherState toState,
             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
-        return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
+        AnimatorSet set = new AnimatorSet();
+        Animator taskbarState = mTaskbarLauncherStateController
+                .createAnimToLauncher(toState, callbacks, duration);
+        long halfDuration = Math.round(duration * 0.5f);
+        Animator translation =
+                mControllers.taskbarTranslationController.createAnimToLauncher(halfDuration);
+
+        set.playTogether(taskbarState, translation);
+        return set;
     }
 
     public boolean isDraggingItem() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 12dbcb3..45d5739 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.RevealOutlineAnimation;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -66,6 +67,7 @@
 
     // Initialized in init.
     private TaskbarControllers mControllers;
+    private int mTaskbarSize;
 
     // The bounds we want to clip to in the settled state when showing the stashed handle.
     private final Rect mStashedHandleBounds = new Rect();
@@ -96,15 +98,18 @@
         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         Resources resources = mActivity.getResources();
         if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) {
-            mStashedHandleView.getLayoutParams().height =
-                    resources.getDimensionPixelSize(R.dimen.taskbar_size);
+            mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_size);
             mStashedHandleWidth =
                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
         } else {
-            mStashedHandleView.getLayoutParams().height = deviceProfile.taskbarSize;
+            mTaskbarSize = deviceProfile.taskbarSize;
             mStashedHandleWidth = resources
                     .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
         }
+        int taskbarBottomMargin = DisplayController.isTransientTaskbar(mActivity)
+                ? resources.getDimensionPixelSize(R.dimen.transient_taskbar_margin)
+                : 0;
+        mStashedHandleView.getLayoutParams().height = mTaskbarSize + taskbarBottomMargin;
 
         mTaskbarStashedHandleAlpha.get(ALPHA_INDEX_STASHED).setValue(
                 isPhoneGestureNavMode(deviceProfile) ? 1 : 0);
@@ -181,9 +186,17 @@
      * morphs into the size of where the taskbar icons will be.
      */
     public Animator createRevealAnimToIsStashed(boolean isStashed) {
+        Rect visualBounds = new Rect(mControllers.taskbarViewController.getIconLayoutBounds());
+
+        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;
+        }
+
         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
-                mStashedHandleRadius, mStashedHandleRadius,
-                mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
+                mStashedHandleRadius, mStashedHandleRadius, visualBounds, mStashedHandleBounds);
 
         boolean isReversed = !isStashed;
         boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
@@ -220,6 +233,13 @@
     }
 
     /**
+     * Sets the translation of the stashed handle during the swipe up gesture.
+     */
+    protected void setTranslationYForSwipe(float transY) {
+        mStashedHandleView.setTranslationY(transY);
+    }
+
+    /**
      * Should be called when the home button is disabled, so we can hide this handle as well.
      */
     public void setIsHomeButtonDisabled(boolean homeDisabled) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index d0256c5..4882842 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -79,6 +79,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.AutohideSuspendFlag;
+import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
 import com.android.launcher3.testing.TestLogging;
@@ -224,6 +225,7 @@
                 new TaskbarAllAppsController(),
                 new TaskbarInsetsController(this),
                 new VoiceInteractionWindowController(this),
+                new TaskbarTranslationController(this),
                 isDesktopMode
                         ? new DesktopTaskbarRecentAppsController(this)
                         : TaskbarRecentAppsController.DEFAULT);
@@ -836,6 +838,20 @@
     }
 
     /**
+     * Called to start the taskbar translation spring to its settled translation (0).
+     */
+    public void startTranslationSpring() {
+        mControllers.taskbarTranslationController.startSpring();
+    }
+
+    /**
+     * Returns a callback to help monitor the swipe gesture.
+     */
+    public TransitionCallback getTranslationCallbacks() {
+        return mControllers.taskbarTranslationController.getTransitionCallback();
+    }
+
+    /**
      * Called when a transient Autohide flag suspend status changes.
      */
     public void onTransientAutohideSuspendFlagChanged(boolean isSuspended) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index a948fb3..ff7e8e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -34,6 +34,7 @@
 
     val paint: Paint = Paint()
     var backgroundHeight = context.deviceProfile.taskbarSize.toFloat()
+    var translationYForSwipe = 0f
 
     private var maxBackgroundHeight = context.deviceProfile.taskbarSize.toFloat()
     private val transientBackgroundBounds = context.transientTaskbarBounds
@@ -114,11 +115,13 @@
             canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
             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.4f, 1f, Interpolators.LINEAR)
             val newWidth = widthScale * width
             val delta = width - newWidth
+            canvas.translate(0f, bottomMargin * ((1f - scaleFactor) / 2f))
 
             // Draw shadow.
             val shadowAlpha = mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, 25f,
@@ -132,9 +135,9 @@
 
             canvas.drawRoundRect(
                 transientBackgroundBounds.left + (delta / 2f),
-                0f,
+                translationYForSwipe,
                 transientBackgroundBounds.right - (delta / 2f),
-                backgroundHeight,
+                backgroundHeight + translationYForSwipe,
                 radius, radius, paint
             )
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 12e6ba4..0328df9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -56,6 +56,7 @@
     public final TaskbarInsetsController taskbarInsetsController;
     public final VoiceInteractionWindowController voiceInteractionWindowController;
     public final TaskbarRecentAppsController taskbarRecentAppsController;
+    public final TaskbarTranslationController taskbarTranslationController;
     public final TaskbarOverlayController taskbarOverlayController;
 
     @Nullable private LoggableTaskbarController[] mControllersToLog = null;
@@ -92,6 +93,7 @@
             TaskbarAllAppsController taskbarAllAppsController,
             TaskbarInsetsController taskbarInsetsController,
             VoiceInteractionWindowController voiceInteractionWindowController,
+            TaskbarTranslationController taskbarTranslationController,
             TaskbarRecentAppsController taskbarRecentAppsController) {
         this.taskbarActivityContext = taskbarActivityContext;
         this.taskbarDragController = taskbarDragController;
@@ -113,6 +115,7 @@
         this.taskbarAllAppsController = taskbarAllAppsController;
         this.taskbarInsetsController = taskbarInsetsController;
         this.voiceInteractionWindowController = voiceInteractionWindowController;
+        this.taskbarTranslationController = taskbarTranslationController;
         this.taskbarRecentAppsController = taskbarRecentAppsController;
     }
 
@@ -144,6 +147,7 @@
         taskbarInsetsController.init(this);
         voiceInteractionWindowController.init(this);
         taskbarRecentAppsController.init(this);
+        taskbarTranslationController.init(this);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -151,7 +155,7 @@
                 taskbarUnfoldAnimationController, taskbarKeyguardController,
                 stashedHandleViewController, taskbarStashController, taskbarEduController,
                 taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
-                voiceInteractionWindowController
+                voiceInteractionWindowController, taskbarTranslationController
         };
         mBackgroundRendererControllers = new BackgroundRendererController[] {
                 taskbarDragLayerController, taskbarScrimViewController,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 2aa4687..d0059f7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -175,6 +175,14 @@
         invalidate();
     }
 
+    /*
+     * Sets the translation of the background during the swipe up gesture.
+     */
+    protected void setBackgroundTranslationYForSwipe(float translationY) {
+        mBackgroundRenderer.setTranslationYForSwipe(translationY);
+        invalidate();
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 3834ba7..e54fc00 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -150,6 +150,13 @@
         updateNavBarDarkIntensityMultiplier();
     }
 
+    /**
+     * Sets the translation of the background during the swipe up gesture.
+     */
+    public void setTranslationYForSwipe(float transY) {
+        mTaskbarDragLayer.setBackgroundTranslationYForSwipe(transY);
+    }
+
     private void updateBackgroundOffset() {
         mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
new file mode 100644
index 0000000..1a7ec13
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 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.taskbar;
+
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+import static com.android.quickstep.AnimatedFloat.VALUE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.AnimatedFloat;
+
+import java.io.PrintWriter;
+
+/**
+ * Class responsible for translating the transient taskbar UI during a swipe gesture.
+ *
+ * The translation is controlled, in priority order:
+ * - animation to home
+ * - a spring animation
+ * - controlled by user
+ *
+ * The spring animation will play start once the user lets go or when user pauses to go to overview.
+ * When the user goes home, the stash animation will play.
+ */
+public class TaskbarTranslationController implements TaskbarControllers.LoggableTaskbarController {
+
+    private final TaskbarActivityContext mContext;
+    private TaskbarControllers mControllers;
+    private final AnimatedFloat mTranslationYForSwipe = new AnimatedFloat(
+            this::updateTranslationYForSwipe);
+
+    private boolean mHasSprungOnceThisGesture;
+    private @Nullable ValueAnimator mSpringBounce;
+    private boolean mGestureEnded;
+    private boolean mAnimationToHomeRunning;
+
+    private final boolean mIsTransientTaskbar;
+
+    private final TransitionCallback mCallback;
+
+    public TaskbarTranslationController(TaskbarActivityContext context) {
+        mContext = context;
+        mIsTransientTaskbar = DisplayController.isTransientTaskbar(mContext);
+        mCallback = new TransitionCallback();
+    }
+
+    /**
+     * Initialization method.
+     */
+    public void init(TaskbarControllers controllers) {
+        mControllers = controllers;
+    }
+
+    /**
+     * Called to cancel any existing animations.
+     */
+    public void cancelAnimationIfExists() {
+        if (mSpringBounce != null) {
+            mSpringBounce.cancel();
+            mSpringBounce = null;
+        }
+        reset();
+    }
+
+    private void updateTranslationYForSwipe() {
+        if (!mIsTransientTaskbar) {
+            return;
+        }
+
+        float transY = mTranslationYForSwipe.value;
+        mControllers.stashedHandleViewController.setTranslationYForSwipe(transY);
+        mControllers.taskbarViewController.setTranslationYForSwipe(transY);
+        mControllers.taskbarDragLayerController.setTranslationYForSwipe(transY);
+    }
+
+    /**
+     * Starts a spring aniamtion to set the views back to the resting state.
+     */
+    public void startSpring() {
+        if (mHasSprungOnceThisGesture || mAnimationToHomeRunning) {
+            return;
+        }
+        mSpringBounce = new SpringAnimationBuilder(mContext)
+                .setStartValue(mTranslationYForSwipe.value)
+                .setEndValue(0)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+                .setStiffness(SpringForce.STIFFNESS_LOW)
+                .build(mTranslationYForSwipe, VALUE);
+        mSpringBounce.addListener(forEndCallback(() -> {
+            if (mGestureEnded) {
+                reset();
+            }
+        }));
+        mSpringBounce.start();
+        mHasSprungOnceThisGesture = true;
+    }
+
+    private void reset() {
+        mGestureEnded = false;
+        mHasSprungOnceThisGesture = false;
+    }
+
+    /**
+     * Returns a callback to help monitor the swipe gesture.
+     */
+    public TransitionCallback getTransitionCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Returns an animation to reset the taskbar translation for animation back to launcher.
+     */
+    public ObjectAnimator createAnimToLauncher(long duration) {
+        ObjectAnimator animator = ObjectAnimator.ofFloat(mTranslationYForSwipe, VALUE, 0);
+        animator.setInterpolator(Interpolators.LINEAR);
+        animator.setDuration(duration);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                cancelAnimationIfExists();
+                mAnimationToHomeRunning = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimationToHomeRunning = false;
+                reset();
+            }
+        });
+        return animator;
+    }
+
+    /**
+     * Helper class to communicate to/from  the input consumer.
+     */
+    public class TransitionCallback {
+
+        /**
+         * Called when there is movement to move the taskbar.
+         */
+        public void onActionMove(float dY) {
+            if (mAnimationToHomeRunning
+                    || (mHasSprungOnceThisGesture && !mGestureEnded)) {
+                return;
+            }
+
+            mTranslationYForSwipe.updateValue(dY);
+        }
+
+        /**
+         * Called when swipe gesture has ended.
+         */
+        public void onActionEnd() {
+            if (mHasSprungOnceThisGesture) {
+                reset();
+            } else {
+                mGestureEnded = true;
+                startSpring();
+            }
+        }
+    }
+
+    @Override
+    public void dumpLogs(String prefix, PrintWriter pw) {
+        pw.println(prefix + "TaskbarTranslationController:");
+
+        pw.println(prefix + "\tmTranslationYForSwipe=" + mTranslationYForSwipe.value);
+        pw.println(prefix + "\tmHasSprungOnceThisGesture=" + mHasSprungOnceThisGesture);
+        pw.println(prefix + "\tmAnimationToHomeRunning=" + mAnimationToHomeRunning);
+        pw.println(prefix + "\tmGestureEnded=" + mGestureEnded);
+        pw.println(prefix + "\tmSpringBounce is running=" + (mSpringBounce != null
+                && mSpringBounce.isRunning()));
+    }
+}
+
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 1152126..1014cb6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -105,6 +105,13 @@
         return mControllers.taskbarStashController.isStashed();
     }
 
+    /**
+     * Called at the end of the swipe gesture on Transient taskbar.
+     */
+    public void startTranslationSpring() {
+        mControllers.taskbarActivityContext.startTranslationSpring();
+    }
+
     /*
      * @param ev MotionEvent in screen coordinates.
      * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index a73528b..80a31b4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -91,6 +91,7 @@
             this::updateTranslationY);
     private AnimatedFloat mTaskbarNavButtonTranslationY;
     private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay;
+    private float mTaskbarIconTranslationYForSwipe;
 
     private final int mTaskbarBottomMargin;
 
@@ -260,9 +261,18 @@
         mTaskbarView.setScaleY(scale);
     }
 
+    /**
+     * Sets the translation of the TaskbarView during the swipe up gesture.
+     */
+    public void setTranslationYForSwipe(float transY) {
+        mTaskbarIconTranslationYForSwipe = transY;
+        updateTranslationY();
+    }
+
     private void updateTranslationY() {
         mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
-                + mTaskbarIconTranslationYForStash.value);
+                + mTaskbarIconTranslationYForStash.value
+                + mTaskbarIconTranslationYForSwipe);
     }
 
     private void updateIconsBackground() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 7e4c2f6..3d59d73 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -99,6 +99,7 @@
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
@@ -662,6 +663,8 @@
                         true/* moveFocusedTask */,
                         new ActiveGestureLog.CompoundString(
                                 "motion pause detected (animate=true)"));
+                Optional.ofNullable(mActivityInterface.getTaskbarController())
+                        .ifPresent(TaskbarUIController::startTranslationSpring);
                 performHapticFeedback();
             }
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
index 1430492..b7f2022 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -27,9 +27,13 @@
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
+import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -48,7 +52,7 @@
     private final float mUnstashArea;
     private final float mScreenWidth;
 
-    private final int mTaskbarThreshold;
+    private final int mTaskbarThresholdY;
     private boolean mHasPassedTaskbarThreshold;
 
     private final PointF mDownPos = new PointF();
@@ -57,6 +61,8 @@
 
     private final boolean mIsTransientTaskbar;
 
+    private final @Nullable TransitionCallback mTransitionCallback;
+
     public TaskbarStashInputConsumer(Context context, InputConsumer delegate,
             InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext) {
         super(delegate, inputMonitor);
@@ -66,7 +72,9 @@
 
         Resources res = context.getResources();
         mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
-        mTaskbarThreshold = res.getDimensionPixelSize(R.dimen.taskbar_nav_threshold);
+        int taskbarThreshold = res.getDimensionPixelSize(R.dimen.taskbar_nav_threshold);
+        int screenHeight = taskbarActivityContext.getDeviceProfile().heightPx;
+        mTaskbarThresholdY = screenHeight - taskbarThreshold;
 
         mIsTransientTaskbar = DisplayController.isTransientTaskbar(context);
 
@@ -76,6 +84,10 @@
                 onLongPressDetected(motionEvent);
             }
         });
+
+        mTransitionCallback = mIsTransientTaskbar
+                ? taskbarActivityContext.getTranslationCallbacks()
+                : null;
     }
 
     @Override
@@ -138,16 +150,25 @@
                             break;
                         }
                         mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
-                        float displacementY = mLastPos.y - mDownPos.y;
-                        float verticalDist = Math.abs(displacementY);
-                        boolean passedTaskbarThreshold = verticalDist >= mTaskbarThreshold;
 
-                        if (!mHasPassedTaskbarThreshold
-                                && passedTaskbarThreshold
-                                && mIsTransientTaskbar) {
-                            mHasPassedTaskbarThreshold = true;
+                        if (mIsTransientTaskbar) {
+                            float dY = mLastPos.y - mDownPos.y;
+                            boolean passedTaskbarThreshold = dY < 0
+                                    && mLastPos.y < mTaskbarThresholdY;
 
-                            mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+                            if (!mHasPassedTaskbarThreshold
+                                    && passedTaskbarThreshold) {
+                                mHasPassedTaskbarThreshold = true;
+
+                                mTaskbarActivityContext.onSwipeToUnstashTaskbar();
+                            }
+
+                            if (dY < 0) {
+                                dY = -OverScroll.dampedScroll(-dY, mTaskbarThresholdY);
+                                if (mTransitionCallback != null) {
+                                    mTransitionCallback.onActionMove(dY);
+                                }
+                            }
                         }
                         break;
                     case MotionEvent.ACTION_UP:
@@ -158,6 +179,9 @@
                         }
                         mTaskbarActivityContext.setAutohideSuspendFlag(
                                 FLAG_AUTOHIDE_SUSPEND_TOUCHING, false);
+                        if (mTransitionCallback != null) {
+                            mTransitionCallback.onActionEnd();
+                        }
                         mHasPassedTaskbarThreshold = false;
                         break;
                 }