Merge "Making sure that we drag icon far enough to cross the threshold" into ub-launcher3-qt-dev
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 1b82bcb..7809e45 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -16,16 +16,23 @@
 package com.android.quickstep;
 
 import static android.view.View.TRANSLATION_Y;
+
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION;
 
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -44,14 +51,18 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListenerEx;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.testing.TestProtocol;
@@ -100,6 +111,13 @@
     }
 
     @Override
+    public void onSwipeUpToHomeComplete(Launcher activity) {
+        // Ensure recents is at the correct position for NORMAL state. For example, when we detach
+        // recents, we assume the first task is invisible, making translation off by one task.
+        activity.getStateManager().reapplyState();
+    }
+
+    @Override
     public void onAssistantVisibilityChanged(float visibility) {
         Launcher launcher = getCreatedActivity();
         if (launcher != null) {
@@ -156,18 +174,23 @@
             public AnimatorPlaybackController createActivityAnimationToHome() {
                 // Return an empty APC here since we have an non-user controlled animation to home.
                 long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-                AnimatorSet as = new AnimatorSet();
-                as.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        activity.getStateManager().goToState(NORMAL, false);
-                    }
-                });
-                return AnimatorPlaybackController.wrap(as, accuracy);
+                return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
+                        0 /* animComponents */);
             }
 
             @Override
             public void playAtomicAnimation(float velocity) {
+                // Setup workspace with 0 duration to prepare for our staggered animation.
+                LauncherStateManager stateManager = activity.getStateManager();
+                AnimatorSetBuilder builder = new AnimatorSetBuilder();
+                // setRecentsAttachedToAppWindow() will animate recents out.
+                builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+                stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
+                builder.build().start();
+
+                // Stop scrolling so that it doesn't interfere with the translation offscreen.
+                recentsView.getScroller().forceFinished(true);
+
                 new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
             }
         };
@@ -201,7 +224,8 @@
         return new AnimationFactory() {
             private Animator mShelfAnim;
             private ShelfAnimState mShelfState;
-            private Animator mAttachToWindowAnim;
+            private Animator mAttachToWindowFadeAnim;
+            private SpringAnimation mAttachToWindowTranslationXAnim;
             private boolean mIsAttachedToWindow;
 
             @Override
@@ -267,20 +291,60 @@
                     return;
                 }
                 mIsAttachedToWindow = attached;
-                if (mAttachToWindowAnim != null) {
-                    mAttachToWindowAnim.cancel();
+                if (mAttachToWindowFadeAnim != null) {
+                    mAttachToWindowFadeAnim.cancel();
                 }
-                mAttachToWindowAnim = ObjectAnimator.ofFloat(activity.getOverviewPanel(),
+                RecentsView recentsView = activity.getOverviewPanel();
+                mAttachToWindowFadeAnim = ObjectAnimator.ofFloat(recentsView,
                         RecentsView.CONTENT_ALPHA, attached ? 1 : 0);
-                mAttachToWindowAnim.addListener(new AnimatorListenerAdapter() {
+
+                int runningTaskIndex = recentsView.getRunningTaskIndex();
+                if (runningTaskIndex == 0) {
+                    // If we are on the first task (we haven't quick switched), translate recents in
+                    // from the side. Calculate the start translation based on current scale/scroll.
+                    float currScale = recentsView.getScaleX();
+                    float scrollOffsetX = recentsView.getScrollOffset();
+
+                    float offscreenX = NORMAL.getOverviewScaleAndTranslation(activity).translationX;
+                    // The first task is hidden, so offset by its width.
+                    int firstTaskWidth = recentsView.getTaskViewAt(0).getWidth();
+                    offscreenX -= (firstTaskWidth + recentsView.getPageSpacing()) * currScale;
+                    // Offset since scale pushes tasks outwards.
+                    offscreenX += firstTaskWidth * (currScale - 1) / 2;
+                    offscreenX = Math.max(0, offscreenX);
+                    if (recentsView.isRtl()) {
+                        offscreenX = -offscreenX;
+                    }
+
+                    float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
+                    float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
+                    if (mAttachToWindowTranslationXAnim == null) {
+                        mAttachToWindowTranslationXAnim = new SpringAnimation(recentsView,
+                                SpringAnimation.TRANSLATION_X).setSpring(new SpringForce()
+                                .setDampingRatio(DAMPING_RATIO_LOW_BOUNCY)
+                                .setStiffness(STIFFNESS_LOW));
+                    }
+                    if (!recentsView.isShown() && animate) {
+                        recentsView.setTranslationX(fromTranslationX);
+                        mAttachToWindowTranslationXAnim.setStartValue(fromTranslationX);
+                    }
+                    mAttachToWindowTranslationXAnim.animateToFinalPosition(toTranslationX);
+                    if (!animate && mAttachToWindowTranslationXAnim.canSkipToEnd()) {
+                        mAttachToWindowTranslationXAnim.skipToEnd();
+                    }
+
+                    mAttachToWindowFadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
+                } else {
+                    mAttachToWindowFadeAnim.setInterpolator(ACCEL_DEACCEL);
+                }
+                mAttachToWindowFadeAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
-                        mAttachToWindowAnim = null;
+                        mAttachToWindowFadeAnim = null;
                     }
                 });
-                mAttachToWindowAnim.setInterpolator(ACCEL_DEACCEL);
-                mAttachToWindowAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0);
-                mAttachToWindowAnim.start();
+                mAttachToWindowFadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0);
+                mAttachToWindowFadeAnim.start();
             }
         };
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index c7841d9..7d17f85 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -218,7 +217,7 @@
             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
 
-    private static final long SHELF_ANIM_DURATION = 120;
+    private static final long SHELF_ANIM_DURATION = 240;
     public static final long RECENTS_ATTACH_DURATION = 300;
 
     // Start resisting when swiping past this factor of mTransitionDragLength.
@@ -602,7 +601,7 @@
     }
 
     public void onMotionPauseChanged(boolean isPaused) {
-        setShelfState(isPaused ? PEEK : HIDE, FAST_OUT_SLOW_IN, SHELF_ANIM_DURATION);
+        setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
     }
 
     public void maybeUpdateRecentsAttachedState() {
@@ -625,7 +624,10 @@
                 : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
         final boolean recentsAttachedToAppWindow;
         int runningTaskIndex = mRecentsView.getRunningTaskIndex();
-        if (mContinuingLastGesture) {
+        if (mGestureEndTarget != null) {
+            recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
+        } else if (mContinuingLastGesture
+                && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
             recentsAttachedToAppWindow = true;
             animate = false;
         } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
@@ -633,17 +635,16 @@
             recentsAttachedToAppWindow = true;
             animate = false;
         } else {
-            if (mGestureEndTarget != null) {
-                recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
-            } else {
-                recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
-            }
+            recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
             if (animate) {
                 // Only animate if an adjacent task view is visible on screen.
                 TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
                 TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
+                float prevTranslationX = mRecentsView.getTranslationX();
+                mRecentsView.setTranslationX(0);
                 animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
                         || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT));
+                mRecentsView.setTranslationX(prevTranslationX);
             }
         }
         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
@@ -701,13 +702,7 @@
 
         SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
         if (controller != null) {
-            float offsetX = 0;
-            if (mRecentsView != null) {
-                int startScroll = mRecentsView.getScrollForPage(mRecentsView.indexOfChild(
-                        mRecentsView.getRunningTaskView()));
-                offsetX = startScroll - mRecentsView.getScrollX();
-                offsetX *= mRecentsView.getScaleX();
-            }
+            float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
             float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
                     mClipAnimationHelper.getTargetRect().width());
             mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
@@ -1217,6 +1212,9 @@
                 if (mRecentsView != null) {
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
                 }
+                // Make sure recents is in its final state
+                maybeUpdateRecentsAttachedState(false);
+                mActivityControlHelper.onSwipeUpToHomeComplete(mActivity);
             }
         });
         return anim;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 204dd56..5aab944 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -37,7 +37,7 @@
 
 import androidx.annotation.StringRes;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -187,12 +187,12 @@
                         mTask.getTopComponent().getPackageName()).addFlags(
                         Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         try {
-            final Launcher launcher = Launcher.getLauncher(getContext());
+            final BaseActivity activity = BaseActivity.fromContext(getContext());
             final ActivityOptions options = ActivityOptions.makeScaleUpAnimation(
                     this, 0, 0,
                     getWidth(), getHeight());
-            launcher.startActivity(intent, options.toBundle());
-            launcher.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
+            activity.startActivity(intent, options.toBundle());
+            activity.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
                     LauncherLogProto.ControlType.APP_USAGE_SETTINGS, this);
         } catch (ActivityNotFoundException e) {
             Log.e(TAG, "Failed to open app usage settings for task "
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index e38a315..90e123e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -1676,6 +1676,16 @@
         return mClearAllButton;
     }
 
+    /**
+     * @return How many pixels the running task is offset on the x-axis due to the current scrollX.
+     */
+    public float getScrollOffset() {
+        int startScroll = getScrollForPage(getRunningTaskIndex());
+        int offsetX = startScroll - getScrollX();
+        offsetX *= getScaleX();
+        return offsetX;
+    }
+
     public Consumer<MotionEvent> getEventDispatcher(RotationMode rotationMode) {
         if (rotationMode.isTransposed) {
             Matrix transform = new Matrix();
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index b17d8d6..dcc1ace 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -53,6 +53,7 @@
 
     void onSwipeUpToRecentsComplete(T activity);
 
+    default void onSwipeUpToHomeComplete(T activity) { }
     void onAssistantVisibilityChanged(float visibility);
 
     @NonNull HomeAnimationFactory prepareHomeUI(T activity);
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index e204c63..c719c1c 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.states.InternalStateHandler;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.widget.PendingItemDragHelper;
 
 import java.util.UUID;
@@ -136,6 +137,9 @@
 
     @Override
     public boolean shouldStartDrag(double distanceDragged) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_DRAG_TAG, "BIDL.shouldStartDrag");
+        }
         // Stay in pre-drag mode, if workspace is locked.
         return !mLauncher.isWorkspaceLocked();
     }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 7b14fa2..9719a18 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -36,6 +36,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
@@ -67,6 +68,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
@@ -447,6 +449,11 @@
 
             @Override
             public boolean shouldStartDrag(double distanceDragged) {
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_DRAG_TAG,
+                            "createPreDragCondition().shouldStartDrag " + distanceDragged + ", "
+                                    + mStartDragThreshold);
+                }
                 return distanceDragged > mStartDragThreshold;
             }