Merge "Hide keyboard while the home button get pressed" into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
index 96b4ae5..d189c50 100644
--- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
+++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
@@ -31,7 +31,8 @@
     @Override
     protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
             RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
-        //TODO: Implement this based off IconRecentsView
+        // Stubbed. Recents launch animation will come from the recents view itself and will not
+        // use remote animations.
     }
 
     @Override
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 5382607..cec12a8 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -43,7 +43,7 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+    public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
         return new float[] {1f, 0f};
     }
 
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 784af7d..0b12ab0 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,16 +16,15 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
-import static com.android.quickstep.views.IconRecentsView.TRANSLATION_Y_FACTOR;
 
 import android.util.FloatProperty;
 
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherRecentsToActivityHelper;
 import com.android.quickstep.views.IconRecentsView;
 
+import androidx.annotation.NonNull;
+
 /**
  * State handler for Go's {@link IconRecentsView}.
  */
@@ -39,11 +38,6 @@
     }
 
     @Override
-    FloatProperty<IconRecentsView> getTranslationYFactorProperty() {
-        return TRANSLATION_Y_FACTOR;
-    }
-
-    @Override
     FloatProperty<IconRecentsView> getContentAlphaProperty() {
         return CONTENT_ALPHA;
     }
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
index c814a71..447e7e7 100644
--- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -61,7 +61,7 @@
 
     @Override
     public ActivityOptions getActivityLaunchOptions(View v) {
-        //TODO: Hook into recents launch animation
+        // Stubbed. Recents launch animation will come from the recents view itself.
         return null;
     }
 
diff --git a/go/quickstep/src/com/android/quickstep/TaskInputController.java b/go/quickstep/src/com/android/quickstep/TaskInputController.java
index d97ac8d..8433007 100644
--- a/go/quickstep/src/com/android/quickstep/TaskInputController.java
+++ b/go/quickstep/src/com/android/quickstep/TaskInputController.java
@@ -15,6 +15,10 @@
  */
 package com.android.quickstep;
 
+import android.app.ActivityOptions;
+import android.view.View;
+
+import com.android.quickstep.views.TaskItemView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
@@ -37,9 +41,16 @@
      * @param viewHolder the task view holder that has been tapped
      */
     public void onTaskClicked(TaskHolder viewHolder) {
-        // TODO: Add app launch animation as part of the launch options here.
+        TaskItemView itemView = (TaskItemView) (viewHolder.itemView);
+        View v = itemView.getThumbnailView();
+        int left = 0;
+        int top = 0;
+        int width = v.getMeasuredWidth();
+        int height = v.getMeasuredHeight();
+
+        ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
         ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(viewHolder.getTask().key,
-                null /* options */, null /* resultCallback */, null /* resultCallbackHandler */);
+                opts, null /* resultCallback */, null /* resultCallbackHandler */);
     }
 
     public void onTaskSwiped(TaskHolder viewHolder) {
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index 3fdaefe..504f640 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -46,20 +46,6 @@
  */
 public final class IconRecentsView extends FrameLayout {
 
-    public static final FloatProperty<IconRecentsView> TRANSLATION_Y_FACTOR =
-            new FloatProperty<IconRecentsView>("translationYFactor") {
-
-                @Override
-                public void setValue(IconRecentsView view, float v) {
-                    view.setTranslationYFactor(v);
-                }
-
-                @Override
-                public Float get(IconRecentsView view) {
-                    return view.mTranslationYFactor;
-                }
-            };
-
     public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
             new FloatProperty<IconRecentsView>("contentAlpha") {
                 @Override
@@ -91,7 +77,6 @@
     private final TaskInputController mTaskInputController;
 
     private RecentsToActivityHelper mActivityHelper;
-    private float mTranslationYFactor;
     private RecyclerView mTaskRecyclerView;
     private View mEmptyView;
 
@@ -170,15 +155,6 @@
         return view.getThumbnailView();
     }
 
-    public void setTranslationYFactor(float translationFactor) {
-        mTranslationYFactor = translationFactor;
-        setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
-    }
-
-    private float computeTranslationYForFactor(float translationYFactor) {
-        return translationYFactor * (getPaddingBottom() - getPaddingTop());
-    }
-
     /**
      * Update the content view so that the appropriate view is shown based off the current list
      * of tasks.
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
index fdb80da..f712753 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
@@ -52,7 +52,7 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+    public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
         // Initialize the recents view scale to what it would be when starting swipe up
         RecentsView recentsView = launcher.getOverviewPanel();
         recentsView.getTaskSize(sTempRect);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
index 79e127a..2360eeb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -69,7 +69,7 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
+    public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
         return new float[] {1f, 0f};
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 0b3bd6c..0d5574f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 
 import android.animation.ValueAnimator;
@@ -23,8 +22,6 @@
 import android.os.Build;
 import android.util.FloatProperty;
 
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -32,6 +29,8 @@
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 
+import androidx.annotation.NonNull;
+
 /**
  * State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
  * the basic view properties, this class also manages changes in the task visuals.
@@ -80,11 +79,6 @@
     }
 
     @Override
-    FloatProperty<LauncherRecentsView> getTranslationYFactorProperty() {
-        return TRANSLATION_Y_FACTOR;
-    }
-
-    @Override
     FloatProperty<RecentsView> getContentAlphaProperty() {
         return CONTENT_ALPHA;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index d61ed72..ef46b3b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -21,7 +21,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -90,7 +89,7 @@
 
             @NonNull
             @Override
-            public Animator createActivityAnimationToHome() {
+            public AnimatorPlaybackController createActivityAnimationToHome() {
                 Animator anim = ObjectAnimator.ofFloat(recentsView, CONTENT_ALPHA, 0);
                 anim.addListener(new AnimationSuccessListener() {
                     @Override
@@ -98,7 +97,10 @@
                         recentsView.startHome();
                     }
                 });
-                return anim;
+                AnimatorSet animatorSet = new AnimatorSet();
+                animatorSet.play(anim);
+                long accuracy = 2 * Math.max(recentsView.getWidth(), recentsView.getHeight());
+                return AnimatorPlaybackController.wrap(animatorSet, accuracy);
             }
         };
     }
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 e95e2a0..279b83c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -56,9 +56,7 @@
 import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
@@ -144,10 +142,9 @@
 
             @NonNull
             @Override
-            public Animator createActivityAnimationToHome() {
+            public AnimatorPlaybackController createActivityAnimationToHome() {
                 long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-                return activity.getStateManager().createAnimationToNewWorkspace(
-                        NORMAL, accuracy).getTarget();
+                return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy);
             }
         };
     }
@@ -308,7 +305,7 @@
         // starting to line up the side pages during swipe up)
         float prevRvScale = recentsView.getScaleX();
         float prevRvTransY = recentsView.getTranslationY();
-        float targetRvScale = endState.getOverviewScaleAndTranslationYFactor(launcher)[0];
+        float targetRvScale = endState.getOverviewScaleAndTranslationY(launcher)[0];
         SCALE_PROPERTY.set(recentsView, targetRvScale);
         recentsView.setTranslationY(0);
         ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index 84097a1..c8dcf80 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -21,7 +21,6 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
-
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
@@ -45,8 +44,6 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
-import androidx.annotation.UiThread;
-
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RaceConditionTracker;
@@ -63,6 +60,8 @@
 
 import java.util.function.Consumer;
 
+import androidx.annotation.UiThread;
+
 /**
  * Input consumer for handling events originating from an activity other than Launcher
  */
@@ -131,7 +130,8 @@
         mVelocityTracker = VelocityTracker.obtain();
 
         mActivityControlHelper = activityControl;
-        mIsDeferredDownTarget = isDeferredDownTarget;
+        boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
+        mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
         mOverviewCallbacks = overviewCallbacks;
         mTaskOverlayFactory = taskOverlayFactory;
         mInputConsumer = inputConsumer;
@@ -143,8 +143,7 @@
 
         mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
         mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
-        // If active listener isn't null, we are continuing the previous gesture.
-        mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null;
+        mPassedTouchSlop = mPassedDragSlop = continuingPreviousGesture;
     }
 
     @Override
@@ -331,12 +330,13 @@
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
             float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
             float velocity = isNavBarOnRight() ? velocityX
                     : isNavBarOnLeft() ? -velocityX
-                            : mVelocityTracker.getYVelocity(mActivePointerId);
+                            : velocityY;
 
             mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
-            mInteractionHandler.onGestureEnded(velocity, velocityX);
+            mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY));
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
             // starting the gesture. In that case, just cleanup immediately.
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 fd53f9c..eb1e7b4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -43,12 +43,12 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
@@ -85,6 +85,7 @@
 import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
 import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
@@ -92,7 +93,6 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -110,7 +110,7 @@
         implements SwipeAnimationListener, OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
-    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
+    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
 
     private static int getFlagForIndex(int index, String name) {
         if (DEBUG_STATES) {
@@ -133,37 +133,33 @@
             getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
     private static final int STATE_SCALED_CONTROLLER_RECENTS =
             getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
-    private static final int STATE_SCALED_CONTROLLER_LAST_TASK =
-            getFlagForIndex(6, "STATE_SCALED_CONTROLLER_LAST_TASK");
 
     private static final int STATE_HANDLER_INVALIDATED =
-            getFlagForIndex(7, "STATE_HANDLER_INVALIDATED");
+            getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
     private static final int STATE_GESTURE_STARTED =
-            getFlagForIndex(8, "STATE_GESTURE_STARTED");
+            getFlagForIndex(7, "STATE_GESTURE_STARTED");
     private static final int STATE_GESTURE_CANCELLED =
-            getFlagForIndex(9, "STATE_GESTURE_CANCELLED");
+            getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
     private static final int STATE_GESTURE_COMPLETED =
-            getFlagForIndex(10, "STATE_GESTURE_COMPLETED");
+            getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
 
     private static final int STATE_CAPTURE_SCREENSHOT =
-            getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT");
+            getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
     private static final int STATE_SCREENSHOT_CAPTURED =
-            getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED");
+            getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
-            getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN");
+            getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
 
     private static final int STATE_RESUME_LAST_TASK =
-            getFlagForIndex(14, "STATE_RESUME_LAST_TASK");
+            getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
     private static final int STATE_START_NEW_TASK =
-            getFlagForIndex(15, "STATE_START_NEW_TASK");
+            getFlagForIndex(14, "STATE_START_NEW_TASK");
     private static final int STATE_CURRENT_TASK_FINISHED =
-            getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED");
+            getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
 
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
-    // For debugging, keep in sync with above states
-
     enum GestureEndTarget {
         HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE),
 
@@ -172,7 +168,7 @@
 
         NEW_TASK(0, STATE_START_NEW_TASK, false, true, ContainerType.APP),
 
-        LAST_TASK(0, STATE_SCALED_CONTROLLER_LAST_TASK, false, false, ContainerType.APP);
+        LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP);
 
         GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
                 int containerType) {
@@ -234,6 +230,7 @@
     private ThumbnailData mTaskSnapshot;
 
     private MultiStateCallback mStateCallback;
+    // Used to control launcher components throughout the swipe gesture.
     private AnimatorPlaybackController mLauncherTransitionController;
 
     private T mActivity;
@@ -274,10 +271,8 @@
     private void initStateCallbacks() {
         mStateCallback = new MultiStateCallback(STATE_NAMES);
 
-        // Re-setup the recents UI when gesture starts, as the state could have been changed during
-        // that time by a previous window transition.
-        mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_GESTURE_STARTED,
-                this::setupRecentsViewUi);
+        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+                this::onLauncherPresentAndGestureStarted);
 
         mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
                 this::initializeLauncherAnimationController);
@@ -285,9 +280,6 @@
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
                 this::launcherFrameDrawn);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
-                this::notifyGestureStartedAsync);
-
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
                         | STATE_GESTURE_CANCELLED,
                 this::resetStateForAnimationCancel);
@@ -295,8 +287,6 @@
         mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
                 this::sendRemoteAnimationsToAnimationFactory);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_LAST_TASK,
-                this::resumeLastTaskForQuickstep);
         mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
                 this::resumeLastTask);
         mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_APP_CONTROLLER_RECEIVED,
@@ -326,8 +316,7 @@
         mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                 this::invalidateHandlerWithLauncher);
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED
-                | STATE_SCALED_CONTROLLER_LAST_TASK,
+        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
                 this::notifyTransitionCancelled);
 
         mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -414,6 +403,8 @@
         } else {
             activity.setOnStartCallback(this::onLauncherStart);
         }
+
+        setupRecentsViewUi();
         return true;
     }
 
@@ -425,8 +416,12 @@
             return;
         }
 
-        mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
-                mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
+        // If we've already ended the gesture and are going home, don't prepare recents UI,
+        // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
+        if (mGestureEndTarget != HOME) {
+            mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+                    mWasLauncherAlreadyVisible, true, this::onAnimatorPlaybackControllerCreated);
+        }
         AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
 
         if (mWasLauncherAlreadyVisible) {
@@ -450,11 +445,18 @@
             });
         }
 
-        setupRecentsViewUi();
         activity.getRootView().setOnApplyWindowInsetsListener(this);
         mStateCallback.setState(STATE_LAUNCHER_STARTED);
     }
 
+    private void onLauncherPresentAndGestureStarted() {
+        // Re-setup the recents UI when gesture starts, as the state could have been changed during
+        // that time by a previous window transition.
+        setupRecentsViewUi();
+
+        notifyGestureStartedAsync();
+    }
+
     private void setupRecentsViewUi() {
         if (mContinuingLastGesture) {
             return;
@@ -677,15 +679,19 @@
         }
     }
 
+    /**
+     * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
+     * @param velocity The x and y components of the velocity when the gesture ends.
+     */
     @UiThread
-    public void onGestureEnded(float endVelocity, float velocityX) {
+    public void onGestureEnded(float endVelocity, PointF velocity) {
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
         boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
         setStateOnUiThread(STATE_GESTURE_COMPLETED);
 
         mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
-        handleNormalGestureEnd(endVelocity, isFling, velocityX);
+        handleNormalGestureEnd(endVelocity, isFling, velocity);
     }
 
     @UiThread
@@ -703,9 +709,8 @@
     }
 
     @UiThread
-    private void handleNormalGestureEnd(float endVelocity, boolean isFling, float velocityX) {
-        float velocityPxPerMs = endVelocity / 1000;
-        float velocityXPxPerMs = velocityX / 1000;
+    private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity) {
+        PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
         final GestureEndTarget endTarget;
@@ -720,7 +725,7 @@
             final int lastTaskIndex = mRecentsView.getTaskViewCount() - 1;
             final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
             taskToLaunch = nextPage <= lastTaskIndex ? nextPage : lastTaskIndex;
-            goingToNewTask = mRecentsView != null && taskToLaunch != runningTaskIndex;
+            goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex;
         } else {
             goingToNewTask = false;
         }
@@ -750,7 +755,7 @@
         } else {
             if (SWIPE_HOME.get() && endVelocity < 0 && !mIsShelfPeeking) {
                 // If swiping at a diagonal, base end target on the faster velocity.
-                endTarget = goingToNewTask && Math.abs(velocityX) > Math.abs(endVelocity)
+                endTarget = goingToNewTask && Math.abs(velocity.x) > Math.abs(endVelocity)
                         ? NEW_TASK : HOME;
             } else if (endVelocity < 0 && (!goingToNewTask || reachedOverviewThreshold)) {
                 // If user scrolled to a new task, only go to recents if they already passed
@@ -760,14 +765,15 @@
                 endTarget = goingToNewTask ? NEW_TASK : LAST_TASK;
             }
             endShift = endTarget.endShift;
-            startShift = Utilities.boundToRange(currentShift - velocityPxPerMs
+            startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
                     * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1);
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
                 if (endTarget == RECENTS) {
                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
-                            startShift, endShift, endShift, velocityPxPerMs, mTransitionDragLength);
+                            startShift, endShift, endShift, velocityPxPerMs.y,
+                            mTransitionDragLength);
                     endShift = overshoot.end;
                     interpolator = overshoot.interpolator;
                     duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
@@ -778,7 +784,7 @@
                     // we want the page's snap velocity to approximately match the velocity at
                     // which the user flings, so we scale the duration by a value near to the
                     // derivative of the scroll interpolator at zero, ie. 2.
-                    long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
+                    long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
                 }
             }
@@ -803,7 +809,7 @@
             }
         } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
             // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
-            // or resumeLastTaskForQuickstep().
+            // or resumeLastTask().
             if (mRecentsView != null) {
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
@@ -835,14 +841,14 @@
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
     @UiThread
     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
-            GestureEndTarget target, float velocityPxPerMs) {
+            GestureEndTarget target, PointF velocityPxPerMs) {
         mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
                 interpolator, target, velocityPxPerMs));
     }
 
     @UiThread
     private void animateToProgressInternal(float start, float end, long duration,
-            Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
+            Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
         mGestureEndTarget = target;
 
         if (mGestureEndTarget.canBeContinued) {
@@ -855,9 +861,8 @@
             RecentsModel.INSTANCE.get(mContext).endStabilizationSession();
         }
 
-        HomeAnimationFactory homeAnimFactory;
-        Animator windowAnim;
         if (mGestureEndTarget == HOME) {
+            HomeAnimationFactory homeAnimFactory;
             if (mActivity != null) {
                 homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
             } else {
@@ -872,27 +877,33 @@
 
                     @NonNull
                     @Override
-                    public Animator createActivityAnimationToHome() {
-                        return new AnimatorSet();
+                    public AnimatorPlaybackController createActivityAnimationToHome() {
+                        return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
                     }
                 };
                 mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                         isPresent -> mRecentsView.startHome());
             }
-            windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+            RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+            windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    setStateOnUiThread(target.endState);
+                }
+            });
+            windowAnim.start(velocityPxPerMs);
             mLauncherTransitionController = null;
         } else {
-            windowAnim = mCurrentShift.animateToValue(start, end);
-            homeAnimFactory = null;
+            Animator windowAnim = mCurrentShift.animateToValue(start, end);
+            windowAnim.setDuration(duration).setInterpolator(interpolator);
+            windowAnim.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    setStateOnUiThread(target.endState);
+                }
+            });
+            windowAnim.start();
         }
-        windowAnim.setDuration(duration).setInterpolator(interpolator);
-        windowAnim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                setStateOnUiThread(target.endState);
-            }
-        });
-        windowAnim.start();
         // Always play the entire launcher animation when going home, since it is separate from
         // the animation that has been controlled thus far.
         if (mGestureEndTarget == HOME) {
@@ -903,12 +914,6 @@
         // interpolate over the remaining progress (end - start).
         TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
                 interpolator, start, end);
-        if (homeAnimFactory != null) {
-            Animator homeAnim = homeAnimFactory.createActivityAnimationToHome();
-            homeAnim.setDuration(duration).setInterpolator(adjustedInterpolator);
-            homeAnim.start();
-            mLauncherTransitionController = null;
-        }
         if (mLauncherTransitionController == null) {
             return;
         }
@@ -920,50 +925,41 @@
             mLauncherTransitionController.getAnimationPlayer().setDuration(duration);
 
             if (QUICKSTEP_SPRINGS.get()) {
-                mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs);
+                mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
             }
             mLauncherTransitionController.getAnimationPlayer().start();
         }
     }
 
     /**
-     * Creates an Animator that transforms the current app window into the home app.
+     * Creates an animation that transforms the current app window into the home app.
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
      * @param homeAnimationFactory The home animation factory.
      */
-    private Animator createWindowAnimationToHome(float startProgress,
+    private RectFSpringAnim createWindowAnimationToHome(float startProgress,
             HomeAnimationFactory homeAnimationFactory) {
         final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
-        RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
+        final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
                 mTransformParams.setProgress(startProgress)));
-        RectF originalTarget = new RectF(mClipAnimationHelper.getTargetRect());
-        final RectF finalTarget = homeAnimationFactory.getWindowTargetRect();
-
-        final RectFEvaluator rectFEvaluator = new RectFEvaluator();
-        final RectF targetRect = new RectF();
-        final RectF currentRect = new RectF();
+        final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
 
         final View floatingView = homeAnimationFactory.getFloatingView();
         final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
 
-        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect);
         if (isFloatingIconView) {
-            anim.addListener((FloatingIconView) floatingView);
+            anim.addAnimatorListener((FloatingIconView) floatingView);
         }
 
+        AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
+
         // We want the window alpha to be 0 once this threshold is met, so that the
         // FolderIconView can be seen morphing into the icon shape.
         final float windowAlphaThreshold = isFloatingIconView ? 0.75f : 1f;
-        anim.addUpdateListener(animation -> {
-            float progress = animation.getAnimatedFraction();
+        anim.addOnUpdateListener((currentRect, progress) -> {
             float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress);
-            // Initially go towards original target (task view in recents),
-            // but accelerate towards the final target.
-            // TODO: This is technically not correct. Instead, motion should continue at
-            // the released velocity but accelerate towards the target.
-            targetRect.set(rectFEvaluator.evaluate(interpolatedProgress,
-                    originalTarget, finalTarget));
-            currentRect.set(rectFEvaluator.evaluate(interpolatedProgress, startRect, targetRect));
+
+            homeAnim.setPlayFraction(progress);
 
             float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
                     windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
@@ -975,10 +971,17 @@
                 ((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress,
                         windowAlphaThreshold);
             }
+
         });
-        anim.addListener(new AnimationSuccessListener() {
+        anim.addAnimatorListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                homeAnim.dispatchOnStart();
+            }
+
             @Override
             public void onAnimationSuccess(Animator animator) {
+                homeAnim.getAnimationPlayer().end();
                 if (mRecentsView != null) {
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
                 }
@@ -988,16 +991,11 @@
     }
 
     @UiThread
-    private void resumeLastTaskForQuickstep() {
-        setStateOnUiThread(STATE_RESUME_LAST_TASK);
-        doLogGesture(LAST_TASK);
-        reset();
-    }
-
-    @UiThread
     private void resumeLastTask() {
         mRecentsAnimationWrapper.finish(false /* toRecents */, null);
         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
+        doLogGesture(LAST_TASK);
+        reset();
     }
 
     @UiThread
@@ -1005,18 +1003,15 @@
         // Launch the task user scrolled to (mRecentsView.getNextPage()).
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // We finish recents animation inside launchTask() when live tile is enabled.
-            mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
-                    result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
-                    mMainThreadHandler);
+            mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false);
         } else {
             mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
-                mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false,
-                        result -> setStateOnUiThread(STATE_HANDLER_INVALIDATED),
-                        mMainThreadHandler);
+                mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false);
             });
         }
         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
         doLogGesture(NEW_TASK);
+        reset();
     }
 
     public void reset() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
new file mode 100644
index 0000000..2edeb3a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 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.quickstep.util;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.FlingSpringAnim;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
+/**
+ * Applies spring forces to animate from a starting rect to a target rect,
+ * while providing update callbacks to the caller.
+ */
+public class RectFSpringAnim {
+
+    /**
+     * Although the rect position animation takes an indefinite amount of time since it depends on
+     * the initial velocity and applied forces, scaling from the starting rect to the target rect
+     * can be done in parallel at a fixed duration. Update callbacks are sent based on the progress
+     * of this animation, while the end callback is sent after all animations finish.
+     */
+    private static final long RECT_SCALE_DURATION = 180;
+
+    private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
+            new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
+                @Override
+                public float getValue(RectFSpringAnim anim) {
+                    return anim.mCurrentCenterX;
+                }
+
+                @Override
+                public void setValue(RectFSpringAnim anim, float currentCenterX) {
+                    anim.mCurrentCenterX = currentCenterX;
+                    anim.onUpdate();
+                }
+            };
+
+    private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_Y =
+            new FloatPropertyCompat<RectFSpringAnim>("rectCenterYSpring") {
+                @Override
+                public float getValue(RectFSpringAnim anim) {
+                    return anim.mCurrentCenterY;
+                }
+
+                @Override
+                public void setValue(RectFSpringAnim anim, float currentCenterY) {
+                    anim.mCurrentCenterY = currentCenterY;
+                    anim.onUpdate();
+                }
+            };
+
+    private static final FloatProperty<RectFSpringAnim> RECT_SCALE_PROGRESS =
+            new FloatProperty<RectFSpringAnim>("rectScaleProgress") {
+                @Override
+                public Float get(RectFSpringAnim anim) {
+                    return anim.mCurrentScaleProgress;
+                }
+
+                @Override
+                public void setValue(RectFSpringAnim anim, float currentScaleProgress) {
+                    anim.mCurrentScaleProgress = currentScaleProgress;
+                    anim.onUpdate();
+                }
+            };
+
+    private final RectF mStartRect;
+    private final RectF mTargetRect;
+    private final RectF mCurrentRect = new RectF();
+    private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>();
+    private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
+
+    private float mCurrentCenterX;
+    private float mCurrentCenterY;
+    private float mCurrentScaleProgress;
+    private boolean mRectXAnimEnded;
+    private boolean mRectYAnimEnded;
+    private boolean mRectScaleAnimEnded;
+
+    public RectFSpringAnim(RectF startRect, RectF targetRect) {
+        mStartRect = startRect;
+        mTargetRect = targetRect;
+        mCurrentCenterX = mStartRect.centerX();
+        mCurrentCenterY = mStartRect.centerY();
+    }
+
+    public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
+        mOnUpdateListeners.add(onUpdateListener);
+    }
+
+    public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
+        mAnimatorListeners.add(animatorListener);
+    }
+
+    public void start(PointF velocityPxPerMs) {
+        // Only tell caller that we ended if both x and y animations have ended.
+        OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
+            mRectXAnimEnded = true;
+            maybeOnEnd();
+        });
+        OnAnimationEndListener onYEndListener = ((animation, canceled, centerY, velocityY) -> {
+            mRectYAnimEnded = true;
+            maybeOnEnd();
+        });
+        FlingSpringAnim rectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX,
+                mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener);
+        FlingSpringAnim rectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY,
+                mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener);
+
+        ValueAnimator rectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+                PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1))
+                .setDuration(RECT_SCALE_DURATION);
+        rectScaleAnim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mRectScaleAnimEnded = true;
+                maybeOnEnd();
+            }
+        });
+
+        rectXAnim.start();
+        rectYAnim.start();
+        rectScaleAnim.start();
+        for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+            animatorListener.onAnimationStart(null);
+        }
+    }
+
+    private void onUpdate() {
+        if (!mOnUpdateListeners.isEmpty()) {
+            float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
+                    mTargetRect.width());
+            float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(),
+                    mTargetRect.height());
+            mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentCenterY - currentHeight / 2,
+                    mCurrentCenterX + currentWidth / 2, mCurrentCenterY + currentHeight / 2);
+            for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+                onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress);
+            }
+        }
+    }
+
+    private void maybeOnEnd() {
+        if (mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
+            for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+                animatorListener.onAnimationEnd(null);
+            }
+        }
+    }
+
+    public interface OnUpdateListener {
+        void onUpdate(RectF currentRect, float progress);
+    }
+}
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 cf42a36..19e9cb4 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
@@ -198,7 +198,7 @@
             final ActivityOptions options = ActivityOptions.makeScaleUpAnimation(
                     this, 0, 0,
                     getWidth(), getHeight());
-            launcher.startActivityForResult(intent, 0, options.toBundle());
+            launcher.startActivity(intent, options.toBundle());
             launcher.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
                     LauncherLogProto.ControlType.APP_USAGE_SETTINGS, this);
         } catch (ActivityNotFoundException e) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 97bce5e..8f2a2d7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -56,27 +56,6 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class LauncherRecentsView extends RecentsView<Launcher> {
 
-    public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
-            new FloatProperty<LauncherRecentsView>("translationYFactor") {
-
-                @Override
-                public void setValue(LauncherRecentsView view, float v) {
-                    view.setTranslationYFactor(v);
-                }
-
-                @Override
-                public Float get(LauncherRecentsView view) {
-                    return view.mTranslationYFactor;
-                }
-            };
-
-    /**
-     * A ratio representing the view's relative placement within its padded space. For example, 0
-     * is top aligned and 0.5 is centered vertically.
-     */
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private float mTranslationYFactor;
-
     private final TransformParams mTransformParams = new TransformParams();
     private ChipsContainer mChipsContainer;
 
@@ -104,12 +83,6 @@
     }
 
     @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        setTranslationYFactor(mTranslationYFactor);
-    }
-
-    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mChipsContainer = mActivity.findViewById(R.id.hints);
@@ -117,9 +90,9 @@
         params.bottomMargin = mActivity.getDeviceProfile().chipHintBottomMarginPx;
     }
 
-    public void setTranslationYFactor(float translationFactor) {
-        mTranslationYFactor = translationFactor;
-        setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+    @Override
+    public void setTranslationY(float translationY) {
+        super.setTranslationY(translationY);
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             LauncherState state = mActivity.getStateManager().getState();
             if (state == OVERVIEW || state == ALL_APPS) {
@@ -128,10 +101,6 @@
         }
     }
 
-    public float computeTranslationYForFactor(float translationYFactor) {
-        return translationYFactor * (getPaddingBottom() - getPaddingTop());
-    }
-
     public void setHintVisibility(float v) {
         if (mChipsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
             mChipsContainer.setHintVisibility(v);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 1eaa8bc..5ae562e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -78,8 +78,9 @@
     }
 
     @Override
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
-        return new float[] {0.9f, -0.2f};
+    public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
+        float slightParallax = -launcher.getDeviceProfile().allAppsCellHeightPx * 0.3f;
+        return new float[] {0.9f, slightParallax};
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index df9dbe4..e74d84d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -26,8 +26,6 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -35,6 +33,8 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 
+import androidx.annotation.NonNull;
+
 /**
  * State handler for recents view. Manages UI changes and animations for recents view based off the
  * current {@link LauncherState}.
@@ -53,9 +53,9 @@
 
     @Override
     public void setState(@NonNull LauncherState state) {
-        float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
-        SCALE_PROPERTY.set(mRecentsView, scaleTranslationYFactor[0]);
-        getTranslationYFactorProperty().set(mRecentsView, scaleTranslationYFactor[1]);
+        float[] scaleTranslationY = state.getOverviewScaleAndTranslationY(mLauncher);
+        SCALE_PROPERTY.set(mRecentsView, scaleTranslationY[0]);
+        mRecentsView.setTranslationY(scaleTranslationY[1]);
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
     }
 
@@ -79,11 +79,11 @@
     void setStateWithAnimationInternal(@NonNull final LauncherState toState,
             @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
         PropertySetter setter = config.getPropertySetter(builder);
-        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
+        float[] scaleTranslationY = toState.getOverviewScaleAndTranslationY(mLauncher);
         Interpolator scaleAndTransYInterpolator = getScaleAndTransYInterpolator(toState, builder);
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationYFactor[0],
+        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleTranslationY[0],
                 scaleAndTransYInterpolator);
-        setter.setFloat(mRecentsView, getTranslationYFactorProperty(), scaleTranslationYFactor[1],
+        setter.setFloat(mRecentsView, View.TRANSLATION_Y, scaleTranslationY[1],
                 scaleAndTransYInterpolator);
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
@@ -102,13 +102,6 @@
     }
 
     /**
-     * Get property for translation Y factor for the recents view.
-     *
-     * @return the float property for the recents view
-     */
-    abstract FloatProperty getTranslationYFactorProperty();
-
-    /**
      * Get property for content alpha for the recents view.
      *
      * @return the float property for the view's content alpha
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 75be2e4..418f7f4 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import android.animation.Animator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
@@ -132,6 +131,6 @@
 
         @NonNull RectF getWindowTargetRect();
 
-        @NonNull Animator createActivityAnimationToHome();
+        @NonNull AnimatorPlaybackController createActivityAnimationToHome();
     }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index cee1c26..875288a 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -192,13 +192,7 @@
         return getWorkspaceScaleAndTranslation(launcher);
     }
 
-    /**
-     * Returns 2 floats designating how to transition overview:
-     *   scale for the current and adjacent pages
-     *   translationY factor where 0 is top aligned and 0.5 is centered vertically
-     */
-    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
-        // TODO: Simplify to use a constant value instead of a factor.
+    public float[] getOverviewScaleAndTranslationY(Launcher launcher) {
         return new float[] {1.1f, 0f};
     }
 
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
new file mode 100644
index 0000000..3d21d82
--- /dev/null
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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.anim;
+
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FlingAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+/**
+ * Given a property to animate and a target value and starting velocity, first apply friction to
+ * the fling until we pass the target, then apply a spring force to pull towards the target.
+ */
+public class FlingSpringAnim {
+
+    private static final float FLING_FRICTION = 1.5f;
+    // Have the spring pull towards the target if we've slowed down too much before reaching it.
+    private static final float FLING_END_THRESHOLD_PX = 50f;
+    private static final float SPRING_STIFFNESS = 350f;
+    private static final float SPRING_DAMPING = SpringForce.DAMPING_RATIO_LOW_BOUNCY;
+
+    private final FlingAnimation mFlingAnim;
+
+    public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
+            float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) {
+        mFlingAnim = new FlingAnimation(object, property)
+                .setFriction(FLING_FRICTION)
+                .setMinimumVisibleChange(FLING_END_THRESHOLD_PX)
+                .setStartVelocity(startVelocity)
+                .setMinValue(Math.min(startPosition, targetPosition))
+                .setMaxValue(Math.max(startPosition, targetPosition));
+        mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
+            SpringAnimation springAnim = new SpringAnimation(object, property)
+                    .setStartVelocity(velocity)
+                    .setSpring(new SpringForce(targetPosition)
+                            .setStiffness(SPRING_STIFFNESS)
+                            .setDampingRatio(SPRING_DAMPING));
+            springAnim.addEndListener(onEndListener);
+            springAnim.start();
+        }));
+    }
+
+    public void start() {
+        mFlingAnim.start();
+    }
+}
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index cc70e32..f74590b 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.anim;
 
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -32,8 +34,6 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-
 /**
  * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
  * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
@@ -51,9 +51,9 @@
     private SpringProperty<T> mProperty;
 
     private ArrayList<AnimatorListener> mListeners;
-    private boolean mSpringEnded = false;
-    private boolean mAnimatorEnded = false;
-    private boolean mEnded = false;
+    private boolean mSpringEnded = true;
+    private boolean mAnimatorEnded = true;
+    private boolean mEnded = true;
 
     private static final FloatPropertyCompat<ProgressInterface> sFloatProperty =
             new FloatPropertyCompat<ProgressInterface>("springObjectAnimator") {
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index e5c70da..2a5418d 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -55,8 +57,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
-import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
-
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
@@ -143,9 +143,6 @@
             setBackgroundDrawableBounds(bgScale);
 
             mRevealAnimator.setCurrentFraction(shapeRevealProgress);
-            if (Float.compare(shapeRevealProgress, 1f) >= 0f) {
-                mRevealAnimator.end();
-            }
         }
         invalidate();
         invalidateOutline();
@@ -160,6 +157,9 @@
 
     @Override
     public void onAnimationEnd(Animator animator) {
+        if (mRevealAnimator != null) {
+            mRevealAnimator.end();
+        }
         if (mEndRunnable != null) {
             mEndRunnable.run();
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 1353a23..122151e 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -44,6 +44,13 @@
         return LauncherInstrumentation.ContainerType.ALL_APPS;
     }
 
+    private boolean hasClickableIcon(UiObject2 allAppsContainer, BySelector appIconSelector) {
+        final UiObject2 icon = allAppsContainer.findObject(appIconSelector);
+        if (icon == null) return false;
+        final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
+        return icon.getVisibleBounds().bottom < navBar.getVisibleBounds().top;
+    }
+
     /**
      * Finds an icon. Fails if the icon doesn't exist. Scrolls the app list when needed to make
      * sure the icon is visible.
@@ -55,10 +62,10 @@
     public AppIcon getAppIcon(String appName) {
         final UiObject2 allAppsContainer = verifyActiveContainer();
         final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
-        if (!allAppsContainer.hasObject(appIconSelector)) {
+        if (!hasClickableIcon(allAppsContainer, appIconSelector)) {
             scrollBackToBeginning();
             int attempts = 0;
-            while (!allAppsContainer.hasObject(appIconSelector) &&
+            while (!hasClickableIcon(allAppsContainer, appIconSelector) &&
                     allAppsContainer.scroll(Direction.DOWN, 0.8f)) {
                 LauncherInstrumentation.assertTrue(
                         "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,