Merge "Add taskbar stashing feedforward, i.e. hint at upcoming stash/unstash" into sc-v2-dev am: 5eb1045cee am: 965a9c9b35

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/15302021

Change-Id: I27a93f8c244d2d95ccc5aa86b7bd3cbc48cdee03
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 8b11154..b5c834d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -233,11 +233,6 @@
         }
     }
 
-    @Override
-    public boolean onLongPressToUnstashTaskbar() {
-        return mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
-    }
-
     /**
      * @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/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 8c14ff6..df37261 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -40,6 +40,8 @@
     private final int mStashedHandleHeight;
     private final AnimatedFloat mTaskbarStashedHandleAlpha = new AnimatedFloat(
             this::updateStashedHandleAlpha);
+    private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
+            this::updateStashedHandleHintScale);
 
     // Initialized in init.
     private TaskbarControllers mControllers;
@@ -64,6 +66,7 @@
         mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
 
         updateStashedHandleAlpha();
+        mTaskbarStashedHandleHintScale.updateValue(1f);
 
         final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
         mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
@@ -80,12 +83,24 @@
                 outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
             }
         });
+
+        mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
+            final int stashedCenterX = view.getWidth() / 2;
+            final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
+
+            view.setPivotX(stashedCenterX);
+            view.setPivotY(stashedCenterY);
+        });
     }
 
     public AnimatedFloat getStashedHandleAlpha() {
         return mTaskbarStashedHandleAlpha;
     }
 
+    public AnimatedFloat getStashedHandleHintScale() {
+        return mTaskbarStashedHandleHintScale;
+    }
+
     /**
      * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle
      * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
@@ -105,4 +120,9 @@
     protected void updateStashedHandleAlpha() {
         mStashedHandleView.setAlpha(mTaskbarStashedHandleAlpha.value);
     }
+
+    protected void updateStashedHandleHintScale() {
+        mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value);
+        mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index e11f4c1..dbe528f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -332,4 +332,20 @@
 
         AbstractFloatingView.closeAllOpenViews(this);
     }
+
+    /**
+     * Called when we detect a long press in the nav region before passing the gesture slop.
+     * @return Whether taskbar handled the long press, and thus should cancel the gesture.
+     */
+    public boolean onLongPressToUnstashTaskbar() {
+        return mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
+    }
+
+    /**
+     * Called when we detect a motion down or up/cancel in the nav region while stashed.
+     * @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
+     */
+    public void startTaskbarUnstashHint(boolean animateForward) {
+        mControllers.taskbarStashController.startUnstashHint(animateForward);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index a226db9..45eabed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -27,6 +27,7 @@
 import android.view.Display;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
@@ -188,4 +189,8 @@
         mDisplayController.removeChangeListener(this);
         mSysUINavigationMode.removeModeChangeListener(this);
     }
+
+    public @Nullable TaskbarActivityContext getCurrentActivityContext() {
+        return mTaskbarActivityContext;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 4ebdbd8..0efec53 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
+import android.view.ViewConfiguration;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -47,6 +48,22 @@
     private static final float STASHED_TASKBAR_SCALE = 0.5f;
 
     /**
+     * How long the hint animation plays, starting on motion down.
+     */
+    private static final long TASKBAR_HINT_STASH_DURATION =
+            ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+
+    /**
+     * The scale that TaskbarView animates to when hinting towards the stashed state.
+     */
+    private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
+
+    /**
+     * The scale that the stashed handle animates to when hinting towards the unstashed state.
+     */
+    private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
+
+    /**
      * The SharedPreferences key for whether user has manually stashed the taskbar.
      */
     private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed";
@@ -71,6 +88,7 @@
     private AnimatedFloat mIconTranslationYForStash;
     // Stashed handle properties.
     private AnimatedFloat mTaskbarStashedHandleAlpha;
+    private AnimatedFloat mTaskbarStashedHandleHintScale;
 
     /** Whether the user has manually invoked taskbar stashing, which we persist. */
     private boolean mIsStashedInApp;
@@ -102,6 +120,7 @@
         StashedHandleViewController stashedHandleController =
                 controllers.stashedHandleViewController;
         mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha();
+        mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
 
         mIsStashedInApp = supportsStashing()
                 && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
@@ -246,6 +265,9 @@
         if (stashedHandleRevealAnim != null) {
             fullLengthAnimatorSet.play(stashedHandleRevealAnim);
         }
+        // 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));
 
         fullLengthAnimatorSet.setDuration(duration);
         firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
@@ -271,4 +293,36 @@
         });
         return mAnimator;
     }
+
+    /**
+     * Creates and starts a partial stash animation, hinting at the new state that will trigger when
+     * long press is detected.
+     * @param animateForward Whether we are going towards the new stashed state or returning to the
+     *                       unstashed state.
+     */
+    public void startStashHint(boolean animateForward) {
+        if (isStashed() || !supportsStashing()) {
+            // Already stashed, no need to hint in that direction.
+            return;
+        }
+        mIconScaleForStash.animateToValue(
+                animateForward ? STASHED_TASKBAR_HINT_SCALE : 1)
+                .setDuration(TASKBAR_HINT_STASH_DURATION).start();
+    }
+
+    /**
+     * Creates and starts a partial unstash animation, hinting at the new state that will trigger
+     * when long press is detected.
+     * @param animateForward Whether we are going towards the new unstashed state or returning to
+     *                       the stashed state.
+     */
+    public void startUnstashHint(boolean animateForward) {
+        if (!isStashed()) {
+            // Already unstashed, no need to hint in that direction.
+            return;
+        }
+        mTaskbarStashedHandleHintScale.animateToValue(
+                animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
+                .setDuration(TASKBAR_HINT_STASH_DURATION).start();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 6d0e3c6..260cedc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -33,8 +33,4 @@
     }
 
     protected void updateContentInsets(Rect outContentInsets) { }
-
-    protected boolean onLongPressToUnstashTaskbar() {
-        return false;
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 820d40a..6f53f2b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -222,6 +222,15 @@
         return super.dispatchTouchEvent(ev);
     }
 
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!mTouchEnabled) {
+            return true;
+        }
+        mControllerCallbacks.onTouchEvent(event);
+        return super.onTouchEvent(event);
+    }
+
     public void setTouchesEnabled(boolean touchEnabled) {
         this.mTouchEnabled = touchEnabled;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 94c0ebe..6b95f08 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -17,14 +17,17 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.AnimatedFloat.VALUE;
 
 import android.graphics.Rect;
+import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.ItemInfo;
@@ -188,6 +191,11 @@
      * Callbacks for {@link TaskbarView} to interact with its controller.
      */
     public class TaskbarViewCallbacks {
+        private final float mSquaredTouchSlop = Utilities.squaredTouchSlop(mActivity);
+
+        private float mDownX, mDownY;
+        private boolean mCanceledStashHint;
+
         public View.OnClickListener getIconOnClickListener() {
             return mActivity::onTaskbarIconClicked;
         }
@@ -199,5 +207,33 @@
         public View.OnLongClickListener getBackgroundOnLongClickListener() {
             return view -> mControllers.taskbarStashController.updateAndAnimateIsStashedInApp(true);
         }
+
+        public void onTouchEvent(MotionEvent motionEvent) {
+            final float x = motionEvent.getRawX();
+            final float y = motionEvent.getRawY();
+            switch (motionEvent.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    mDownX = x;
+                    mDownY = y;
+                    mControllers.taskbarStashController.startStashHint(/* animateForward = */ true);
+                    mCanceledStashHint = false;
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    if (!mCanceledStashHint
+                            && squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
+                        mControllers.taskbarStashController.startStashHint(
+                                /* animateForward= */ false);
+                        mCanceledStashHint = true;
+                    }
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    if (!mCanceledStashHint) {
+                        mControllers.taskbarStashController.startStashHint(
+                                /* animateForward= */ false);
+                    }
+                    break;
+            }
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 1412b1a..2699b07 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -364,14 +364,6 @@
     }
 
     /**
-     * Called when we detect a long press in the nav region before passing the gesture slop.
-     * @return Whether taskbar handled the long press, and thus should cancel the gesture.
-     */
-    public boolean onLongPressToUnstashTaskbar() {
-        return false;
-    }
-
-    /**
      * Returns the color of the scrim behind overview when at rest in this state.
      * Return {@link Color#TRANSPARENT} for no scrim.
      */
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 94a47e6..5deb75e 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -318,15 +318,6 @@
     }
 
     @Override
-    public boolean onLongPressToUnstashTaskbar() {
-        LauncherTaskbarUIController taskbarController = getTaskbarController();
-        if (taskbarController == null) {
-            return super.onLongPressToUnstashTaskbar();
-        }
-        return taskbarController.onLongPressToUnstashTaskbar();
-    }
-
-    @Override
     protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher,
             LauncherState state) {
         return state.getWorkspaceScrimColor(launcher);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index af1a01a..20d7eb1 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -680,7 +680,7 @@
             StatefulActivity activity = activityInterface.getCreatedActivity();
             if (activity != null && activity.getDeviceProfile().isTaskbarPresent) {
                 base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat,
-                        activityInterface);
+                        mTaskbarManager.getCurrentActivityContext());
             }
 
             if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
index 83f689f..dbe260a 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -15,12 +15,15 @@
  */
 package com.android.quickstep.inputconsumers;
 
+import static com.android.launcher3.Utilities.squaredHypot;
+
 import android.content.Context;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
 
-import com.android.quickstep.BaseActivityInterface;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -29,13 +32,18 @@
  */
 public class TaskbarStashInputConsumer extends DelegateInputConsumer {
 
-    private final BaseActivityInterface mActivityInterface;
+    private final TaskbarActivityContext mTaskbarActivityContext;
     private final GestureDetector mLongPressDetector;
+    private final float mSquaredTouchSlop;
+
+    private float mDownX, mDownY;
+    private boolean mCanceledUnstashHint;
 
     public TaskbarStashInputConsumer(Context context, InputConsumer delegate,
-            InputMonitorCompat inputMonitor, BaseActivityInterface activityInterface) {
+            InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext) {
         super(delegate, inputMonitor);
-        mActivityInterface = activityInterface;
+        mTaskbarActivityContext = taskbarActivityContext;
+        mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
 
         mLongPressDetector = new GestureDetector(context, new SimpleOnGestureListener() {
             @Override
@@ -55,11 +63,41 @@
         mLongPressDetector.onTouchEvent(ev);
         if (mState != STATE_ACTIVE) {
             mDelegate.onMotionEvent(ev);
+
+            if (mTaskbarActivityContext != null) {
+                final float x = ev.getRawX();
+                final float y = ev.getRawY();
+                switch (ev.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        mDownX = x;
+                        mDownY = y;
+                        mTaskbarActivityContext.startTaskbarUnstashHint(
+                                /* animateForward = */ true);
+                        mCanceledUnstashHint = false;
+                        break;
+                    case MotionEvent.ACTION_MOVE:
+                        if (!mCanceledUnstashHint
+                                && squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
+                            mTaskbarActivityContext.startTaskbarUnstashHint(
+                                    /* animateForward = */ false);
+                            mCanceledUnstashHint = true;
+                        }
+                        break;
+                    case MotionEvent.ACTION_UP:
+                    case MotionEvent.ACTION_CANCEL:
+                        if (!mCanceledUnstashHint) {
+                            mTaskbarActivityContext.startTaskbarUnstashHint(
+                                    /* animateForward = */ false);
+                        }
+                        break;
+                }
+            }
         }
     }
 
     private void onLongPressDetected(MotionEvent motionEvent) {
-        if (mActivityInterface.onLongPressToUnstashTaskbar()) {
+        if (mTaskbarActivityContext != null
+                && mTaskbarActivityContext.onLongPressToUnstashTaskbar()) {
             setActive(motionEvent);
         }
     }