Snap for 5698743 from 5cd4333b5c62b249d3f0b52020864bb9598f92c9 to qt-aml-release

Change-Id: Ie2eba3f2213f2d8cb341f9cb982fa75b52689dd7
diff --git a/Android.mk b/Android.mk
index 7956d28..9d113d9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -298,7 +298,7 @@
 LOCAL_PACKAGE_NAME := Launcher3GoIconRecents
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_PRODUCT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep Launcher3QuickStepGo
+LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep
 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index b5fefb4..cbc77d2 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -16,11 +16,6 @@
 
 package com.android.launcher3.uioverrides;
 
-import static android.view.View.VISIBLE;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-
-import android.view.View;
-
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
@@ -43,8 +38,6 @@
 public abstract class RecentsUiFactory {
 
     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = true;
-    // Scale recents takes before animating in
-    private static final float RECENTS_PREPARE_SCALE = 1.33f;
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
         ArrayList<TouchController> list = new ArrayList<>();
@@ -77,18 +70,6 @@
     }
 
     /**
-     * Prepare the recents view to animate in.
-     *
-     * @param launcher the launcher activity
-     */
-    public static void prepareToShowOverview(Launcher launcher) {
-        View overview = launcher.getOverviewPanel();
-        if (overview.getVisibility() != VISIBLE) {
-            SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
-        }
-    }
-
-    /**
      * Clean-up logic that occurs when recents is no longer in use/visible.
      *
      * @param launcher the launcher activity
@@ -108,4 +89,6 @@
     public static RotationMode getRotationMode(DeviceProfile dp) {
         return RotationMode.NORMAL;
     }
+
+    public static void clearSwipeSharedState(boolean finishAnimation) {}
 }
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 1b24fc8..d0cfcf9 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -16,15 +16,31 @@
 
 package com.android.launcher3.uioverrides.states;
 
+import static android.view.View.VISIBLE;
+
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
+import android.view.View;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.IconRecentsView;
 
 /**
@@ -32,6 +48,9 @@
  */
 public class OverviewState extends LauncherState {
 
+    // Scale recents takes before animating in
+    private static final float RECENTS_PREPARE_SCALE = 1.33f;
+
     private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
             | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
 
@@ -103,6 +122,27 @@
         return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
     }
 
+    @Override
+    public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
+            AnimatorSetBuilder builder) {
+        if (fromState == NORMAL && this == OVERVIEW) {
+            if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
+                builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL);
+                builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+            } else {
+                builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+            }
+            builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
+            builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
+            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+            builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
+
+            View overview = launcher.getOverviewPanel();
+            if (overview.getVisibility() != VISIBLE) {
+                SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+            }
+        }
+    }
 
     public static OverviewState newBackgroundState(int id) {
         return new OverviewState(id);
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 39f8448..900b94e 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -19,15 +19,21 @@
 
 import android.annotation.TargetApi;
 import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.MotionEvent;
 
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
@@ -37,7 +43,16 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
-    private static final String TAG = "TouchInteractionService";
+    private static final String TAG = "GoTouchInteractionService";
+    private boolean mIsUserUnlocked;
+    private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                initWhenUserUnlocked();
+            }
+        }
+    };
 
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
@@ -53,17 +68,21 @@
 
         @Override
         public void onOverviewToggle() {
-            mOverviewCommandHelper.onOverviewToggle();
+            if (mIsUserUnlocked) {
+                mOverviewCommandHelper.onOverviewToggle();
+            }
         }
 
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
-            mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
+            if (mIsUserUnlocked) {
+                mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
+            }
         }
 
         @Override
         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-            if (triggeredFromAltTab && !triggeredFromHomeKey) {
+            if (mIsUserUnlocked && triggeredFromAltTab && !triggeredFromHomeKey) {
                 // onOverviewShownFromAltTab hides the overview and ends at the target app
                 mOverviewCommandHelper.onOverviewHidden();
             }
@@ -71,7 +90,9 @@
 
         @Override
         public void onTip(int actionType, int viewType) {
-            mOverviewCommandHelper.onTip(actionType, viewType);
+            if (mIsUserUnlocked) {
+                mOverviewCommandHelper.onTip(actionType, viewType);
+            }
         }
 
         @Override
@@ -123,17 +144,31 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mRecentsModel = RecentsModel.INSTANCE.get(this);
-        mOverviewComponentObserver = new OverviewComponentObserver(this);
-        mOverviewCommandHelper = new OverviewCommandHelper(this,
-                mOverviewComponentObserver);
+        if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
+            initWhenUserUnlocked();
+        } else {
+            mIsUserUnlocked = false;
+            registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+        }
 
         sConnected = true;
     }
 
+    private void initWhenUserUnlocked() {
+        mRecentsModel = RecentsModel.INSTANCE.get(this);
+        mOverviewComponentObserver = new OverviewComponentObserver(this);
+        mOverviewCommandHelper = new OverviewCommandHelper(this,
+                mOverviewComponentObserver);
+        mIsUserUnlocked = true;
+        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
+    }
+
     @Override
     public void onDestroy() {
-        mOverviewComponentObserver.onDestroy();
+        if (mIsUserUnlocked) {
+            mOverviewComponentObserver.onDestroy();
+        }
+        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
         sConnected = false;
         super.onDestroy();
     }
diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle
index 49d427e..8a4d2b7 100644
--- a/iconloaderlib/build.gradle
+++ b/iconloaderlib/build.gradle
@@ -1,13 +1,3 @@
-buildscript {
-    repositories {
-        mavenCentral()
-        google()
-    }
-    dependencies {
-        classpath GRADLE_CLASS_PATH
-    }
-}
-
 apply plugin: 'com.android.library'
 
 android {
@@ -44,12 +34,6 @@
     }
 }
 
-
-repositories {
-    mavenCentral()
-    google()
-}
-
 dependencies {
     implementation "androidx.core:core:${ANDROID_X_VERSION}"
 }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
index af07aa3..97a0fd3 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
@@ -85,6 +85,14 @@
         return pos;
     }
 
+    public float[] getLeftDotPosition() {
+        return mLeftDotPosition;
+    }
+
+    public float[] getRightDotPosition() {
+        return mRightDotPosition;
+    }
+
     /**
      * Draw a circle on top of the canvas according to the given params.
      */
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index eac4dce..371161e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,25 +16,32 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
 import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
 
+import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.app.ActivityOptions;
 import android.content.Context;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -46,11 +53,12 @@
  */
 public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransitionManagerImpl {
 
-    private RecentsView mRecentsView;
+    public static final int INDEX_SHELF_ANIM = 0;
+    public static final int INDEX_RECENTS_FADE_ANIM = 1;
+    public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
 
     public LauncherAppTransitionManagerImpl(Context context) {
         super(context);
-        mRecentsView = mLauncher.getOverviewPanel();
     }
 
     @Override
@@ -133,4 +141,25 @@
             mLauncher.getStateManager().reapplyState();
         };
     }
+
+    @Override
+    public int getStateElementAnimationsCount() {
+        return 3;
+    }
+
+    @Override
+    public Animator createStateElementAnimation(int index, float... values) {
+        switch (index) {
+            case INDEX_SHELF_ANIM:
+                return mLauncher.getAllAppsController().createSpringAnimation(values);
+            case INDEX_RECENTS_FADE_ANIM:
+                return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
+                        RecentsView.CONTENT_ALPHA, values);
+            case INDEX_RECENTS_TRANSLATE_X_ANIM:
+                return new SpringObjectAnimator<>(mLauncher.getOverviewPanel(),
+                        VIEW_TRANSLATE_X, MIN_VISIBLE_CHANGE_PIXELS, 0.8f, 250, values);
+            default:
+                return super.createStateElementAnimation(index, values);
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index d84362c..6ecf1c1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.uioverrides;
 
-import static android.view.View.VISIBLE;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
@@ -48,6 +46,7 @@
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -62,9 +61,6 @@
     private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
             WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
 
-    // Scale recents takes before animating in
-    private static final float RECENTS_PREPARE_SCALE = 1.33f;
-
     public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
         @Override
         public void mapRect(int left, int top, int right, int bottom, Rect out) {
@@ -189,19 +185,10 @@
     }
 
     /**
-     * Prepare the recents view to animate in.
-     *
-     * @param launcher the launcher activity
+     * Clears the swipe shared state for the current swipe gesture.
      */
-    public static void prepareToShowOverview(Launcher launcher) {
-        if (SysUINavigationMode.getMode(launcher) == NO_BUTTON) {
-            // Overview lives on the side, so doesn't scale in from above.
-            return;
-        }
-        RecentsView overview = launcher.getOverviewPanel();
-        if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
-            SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
-        }
+    public static void clearSwipeSharedState(boolean finishAnimation) {
+        TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 1c66968..d14de70 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -83,4 +83,16 @@
     public int getVisibleElements(Launcher launcher) {
         return super.getVisibleElements(launcher) & ~RECENTS_CLEAR_ALL_BUTTON;
     }
+
+    @Override
+    public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
+        if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
+            // Translate hotseat offscreen if we show it in overview.
+            ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
+            scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher,
+                    launcher.getDeviceProfile());
+            return scaleAndTranslation;
+        }
+        return super.getHotseatScaleAndTranslation(launcher);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index bc1d4ba..c954762 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 package com.android.launcher3.uioverrides.states;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 
 public class OverviewPeekState extends OverviewState {
     public OverviewPeekState(int id) {
@@ -29,4 +36,13 @@
                 - launcher.getResources().getDimension(R.dimen.overview_peek_distance);
         return result;
     }
+
+    @Override
+    public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
+            AnimatorSetBuilder builder) {
+        if (this == OVERVIEW_PEEK && fromState == NORMAL) {
+            builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 9a99c15..5543860 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,8 +15,20 @@
  */
 package com.android.launcher3.uioverrides.states;
 
+import static android.view.View.VISIBLE;
+
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
@@ -30,8 +42,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -40,6 +55,9 @@
  */
 public class OverviewState extends LauncherState {
 
+    // Scale recents takes before animating in
+    private static final float RECENTS_PREPARE_SCALE = 1.33f;
+
     protected static final Rect sTempRect = new Rect();
 
     private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
@@ -127,6 +145,10 @@
             // We have no all apps content, so we're still at the fully down progress.
             return super.getVerticalProgress(launcher);
         }
+        return getDefaultVerticalProgress(launcher);
+    }
+
+    public static float getDefaultVerticalProgress(Launcher launcher) {
         return 1 - (getDefaultSwipeHeight(launcher)
                 / launcher.getAllAppsController().getShiftRange());
     }
@@ -156,6 +178,29 @@
         }
     }
 
+    @Override
+    public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
+            AnimatorSetBuilder builder) {
+        if (fromState == NORMAL && this == OVERVIEW) {
+            if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
+                builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL);
+                builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+            } else {
+                builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+
+                // Scale up the recents, if it is not coming from the side
+                RecentsView overview = launcher.getOverviewPanel();
+                if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
+                    SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+                }
+            }
+            builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
+            builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
+            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+            builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
+        }
+    }
+
     public static OverviewState newBackgroundState(int id) {
         return new BackgroundAppState(id);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index e3e339a..3fe4bcf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -16,11 +16,20 @@
 
 package com.android.launcher3.uioverrides.touchcontrollers;
 
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
 import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
@@ -38,14 +47,14 @@
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Touch controller which handles swipe and hold to go to Overview
  */
 public class FlingAndHoldTouchController extends PortraitStatesTouchController {
 
-    private static final long PEEK_ANIM_DURATION = 100;
+    private static final long PEEK_IN_ANIM_DURATION = 240;
+    private static final long PEEK_OUT_ANIM_DURATION = 100;
     private static final float MAX_DISPLACEMENT_PERCENT = 0.75f;
 
     private final MotionPauseDetector mMotionPauseDetector;
@@ -81,9 +90,9 @@
                 }
                 LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
                 LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
+                long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
                 mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
-                        new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT,
-                        PEEK_ANIM_DURATION);
+                        new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration);
                 mPeekAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
@@ -107,6 +116,21 @@
     }
 
     @Override
+    protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
+            LauncherState toState) {
+        if (fromState == NORMAL && toState == ALL_APPS) {
+            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+
+            // Get workspace out of the way quickly, to prepare for potential pause.
+            builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
+            builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3);
+            builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
+            return builder;
+        }
+        return super.getAnimatorSetBuilderForStates(fromState, toState);
+    }
+
+    @Override
     public boolean onDrag(float displacement, MotionEvent event) {
         float upDisplacement = -displacement;
         mMotionPauseDetector.setDisallowPause(upDisplacement < mMotionPauseMinDisplacement
@@ -123,8 +147,11 @@
             }
 
             AnimatorSetBuilder builder = new AnimatorSetBuilder();
-            builder.setInterpolator(AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
-            builder.setInterpolator(AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE, OVERSHOOT_1_2);
+            builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+            if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
+                builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
+            }
             AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
                     NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION);
             overviewAnim.addListener(new AnimatorListenerAdapter() {
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 0d06c19..07c0496 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -18,12 +18,13 @@
 import static android.view.View.TRANSLATION_Y;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_FADE_ANIM;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_RECENTS_TRANSLATE_X_ANIM;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
 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;
@@ -31,7 +32,6 @@
 import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -48,8 +48,6 @@
 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;
@@ -60,13 +58,14 @@
 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.testing.TestProtocol;
+import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -216,10 +215,7 @@
         activity.getAppsView().getContentView().setVisibility(View.GONE);
 
         return new AnimationFactory() {
-            private Animator mShelfAnim;
             private ShelfAnimState mShelfState;
-            private Animator mAttachToWindowFadeAnim;
-            private SpringAnimation mAttachToWindowTranslationXAnim;
             private boolean mIsAttachedToWindow;
 
             @Override
@@ -252,31 +248,26 @@
                     return;
                 }
                 mShelfState = shelfState;
-                if (mShelfAnim != null) {
-                    mShelfAnim.cancel();
-                }
+                activity.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
                 if (mShelfState == ShelfAnimState.CANCEL) {
                     return;
                 }
                 float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity);
                 float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity);
+                // Peek based on default overview progress so we can see hotseat if we're showing
+                // that instead of predictions in overview.
+                float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(activity);
                 float shelfPeekingProgress = shelfHiddenProgress
-                        - (shelfHiddenProgress - shelfOverviewProgress) * 0.25f;
+                        - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
                 float toProgress = mShelfState == ShelfAnimState.HIDE
                         ? shelfHiddenProgress
                         : mShelfState == ShelfAnimState.PEEK
                                 ? shelfPeekingProgress
                                 : shelfOverviewProgress;
-                mShelfAnim = createShelfAnim(activity, toProgress);
-                mShelfAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mShelfAnim = null;
-                    }
-                });
-                mShelfAnim.setInterpolator(interpolator);
-                mShelfAnim.setDuration(duration);
-                mShelfAnim.start();
+                Animator shelfAnim = activity.getStateManager()
+                        .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
+                shelfAnim.setInterpolator(interpolator);
+                shelfAnim.setDuration(duration).start();
             }
 
             @Override
@@ -285,12 +276,10 @@
                     return;
                 }
                 mIsAttachedToWindow = attached;
-                if (mAttachToWindowFadeAnim != null) {
-                    mAttachToWindowFadeAnim.cancel();
-                }
-                RecentsView recentsView = activity.getOverviewPanel();
-                mAttachToWindowFadeAnim = ObjectAnimator.ofFloat(recentsView,
-                        RecentsView.CONTENT_ALPHA, attached ? 1 : 0);
+                LauncherRecentsView recentsView = activity.getOverviewPanel();
+                Animator fadeAnim = activity.getStateManager()
+                        .createStateElementAnimation(
+                        INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
 
                 int runningTaskIndex = recentsView.getRunningTaskIndex();
                 if (runningTaskIndex == 0) {
@@ -312,33 +301,28 @@
 
                     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(0.8f)
-                                .setStiffness(250));
-                    }
+                    activity.getStateManager()
+                            .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
+
                     if (!recentsView.isShown() && animate) {
                         recentsView.setTranslationX(fromTranslationX);
-                        mAttachToWindowTranslationXAnim.setStartValue(fromTranslationX);
-                    }
-                    mAttachToWindowTranslationXAnim.animateToFinalPosition(toTranslationX);
-                    if (!animate && mAttachToWindowTranslationXAnim.canSkipToEnd()) {
-                        mAttachToWindowTranslationXAnim.skipToEnd();
+                    } else {
+                        fromTranslationX = recentsView.getTranslationX();
                     }
 
-                    mAttachToWindowFadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
-                } else {
-                    mAttachToWindowFadeAnim.setInterpolator(ACCEL_DEACCEL);
-                }
-                mAttachToWindowFadeAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mAttachToWindowFadeAnim = null;
+                    if (!animate) {
+                        recentsView.setTranslationX(toTranslationX);
+                    } else {
+                        activity.getStateManager().createStateElementAnimation(
+                                INDEX_RECENTS_TRANSLATE_X_ANIM,
+                                fromTranslationX, toTranslationX).start();
                     }
-                });
-                mAttachToWindowFadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0);
-                mAttachToWindowFadeAnim.start();
+
+                    fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
+                } else {
+                    fadeAnim.setInterpolator(ACCEL_DEACCEL);
+                }
+                fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
             }
         };
     }
@@ -354,10 +338,10 @@
         if (!activity.getDeviceProfile().isVerticalBarLayout()
                 && SysUINavigationMode.getMode(activity) != Mode.NO_BUTTON) {
             // Don't animate the shelf when the mode is NO_BUTTON, because we update it atomically.
-            Animator shiftAnim = createShelfAnim(activity,
+            anim.play(activity.getStateManager().createStateElementAnimation(
+                    INDEX_SHELF_ANIM,
                     fromState.getVerticalProgress(activity),
-                    endState.getVerticalProgress(activity));
-            anim.play(shiftAnim);
+                    endState.getVerticalProgress(activity)));
         }
         playScaleDownAnim(anim, activity, fromState, endState);
 
@@ -375,13 +359,6 @@
         callback.accept(controller);
     }
 
-    private Animator createShelfAnim(Launcher activity, float ... progressValues) {
-        Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
-                "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
-                SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
-        return shiftAnim;
-    }
-
     /**
      * Scale down recents from the center task being full screen to being in overview.
      */
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index 5eecf17..ddd28a3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -19,6 +19,8 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.launcher3.Utilities.FLAG_NO_GESTURES;
+
 import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -182,7 +184,10 @@
             }
         }
         if (mInputConsumer != null) {
+            int flags = ev.getEdgeFlags();
+            ev.setEdgeFlags(flags | FLAG_NO_GESTURES);
             mInputConsumer.onMotionEvent(ev);
+            ev.setEdgeFlags(flags);
         }
 
         return true;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index 6689ce3..c55f656 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -72,13 +72,15 @@
         mLastAnimationRunning = false;
     }
 
-    private void clearListenerState() {
+    private void clearListenerState(boolean finishAnimation) {
         if (mRecentsAnimationListener != null) {
             mRecentsAnimationListener.removeListener(this);
             mRecentsAnimationListener.cancelListener();
             if (mLastAnimationRunning && mLastAnimationTarget != null) {
                 Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(),
-                        mLastAnimationTarget::cancelAnimation);
+                        finishAnimation
+                                ? mLastAnimationTarget::finishAnimation
+                                : mLastAnimationTarget::cancelAnimation);
                 mLastAnimationTarget = null;
             }
         }
@@ -106,7 +108,7 @@
             }
         }
 
-        clearListenerState();
+        clearListenerState(false /* finishAnimation */);
         boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false
                 : mOverviewComponentObserver.getActivityControlHelper().shouldMinimizeSplitScreen();
         mRecentsAnimationListener = new RecentsAnimationListenerSet(
@@ -138,8 +140,8 @@
         mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets();
     }
 
-    public void clearAllState() {
-        clearListenerState();
+    public void clearAllState(boolean finishAnimation) {
+        clearListenerState(finishAnimation);
         canGestureBeContinued = false;
         recentsAnimationFinishInterrupted = false;
         nextRunningTaskId = -1;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index d0ea73a..6897c1e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -29,8 +29,6 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
@@ -123,6 +121,7 @@
                 new RemoteAnimationTargetSet(targets, MODE_OPENING);
         targetSet.addDependentTransactionApplier(applier);
 
+        final RecentsView recentsView = v.getRecentsView();
         final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
         appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
         appAnimator.addUpdateListener(new MultiValueUpdateListener() {
@@ -153,7 +152,10 @@
                 // TODO: Take into account the current fullscreen progress for animating the insets
                 params.setProgress(1 - percent);
                 RectF taskBounds = inOutHelper.applyTransform(targetSet, params);
-                if (!skipViewChanges) {
+                int taskIndex = recentsView.indexOfChild(v);
+                int centerTaskIndex = recentsView.getCurrentPage();
+                boolean parallaxCenterAndAdjacentTask = taskIndex != centerTaskIndex;
+                if (!skipViewChanges && parallaxCenterAndAdjacentTask) {
                     float scale = taskBounds.width() / mThumbnailRect.width();
                     v.setScaleX(scale);
                     v.setScaleY(scale);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 769d207..53da0f9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -81,6 +81,7 @@
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer;
 import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -224,14 +225,18 @@
     };
 
     private static boolean sConnected = false;
+    private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState();
 
     public static boolean isConnected() {
         return sConnected;
     }
 
-    private final SwipeSharedState mSwipeSharedState = new SwipeSharedState();
+    public static SwipeSharedState getSwipeSharedState() {
+        return sSwipeSharedState;
+    }
+
     private final InputConsumer mResetGestureInputConsumer =
-            new ResetGestureInputConsumer(mSwipeSharedState);
+            new ResetGestureInputConsumer(sSwipeSharedState);
 
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
@@ -267,6 +272,9 @@
     private Mode mMode = Mode.THREE_BUTTONS;
     private int mDefaultDisplayId;
     private final RectF mSwipeTouchRegion = new RectF();
+    private final RectF mAssistantLeftRegion = new RectF();
+    private final RectF mAssistantRightRegion = new RectF();
+
     private ComponentName mGestureBlockingActivity;
 
     private Region mExclusionRegion;
@@ -349,9 +357,25 @@
         defaultDisplay.getRealSize(realSize);
         mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
         if (mMode == Mode.NO_BUTTON) {
-            mSwipeTouchRegion.top = mSwipeTouchRegion.bottom -
-                    getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+            int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+            mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - touchHeight;
+
+            final int assistantWidth = getResources()
+                    .getDimensionPixelSize(R.dimen.gestures_assistant_width);
+            final float assistantHeight = Math.max(touchHeight,
+                    QuickStepContract.getWindowCornerRadius(getResources()));
+            mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mSwipeTouchRegion.bottom;
+            mAssistantLeftRegion.top = mAssistantRightRegion.top =
+                    mSwipeTouchRegion.bottom - assistantHeight;
+
+            mAssistantLeftRegion.left = 0;
+            mAssistantLeftRegion.right = assistantWidth;
+
+            mAssistantRightRegion.right = mSwipeTouchRegion.right;
+            mAssistantRightRegion.left = mSwipeTouchRegion.right - assistantWidth;
         } else {
+            mAssistantLeftRegion.setEmpty();
+            mAssistantRightRegion.setEmpty();
             switch (defaultDisplay.getRotation()) {
                 case Surface.ROTATION_90:
                     mSwipeTouchRegion.left = mSwipeTouchRegion.right
@@ -416,7 +440,7 @@
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mIsUserUnlocked = true;
 
-        mSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
+        sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
         mInputConsumer.registerInputConsumer();
         onSystemUiProxySet();
         onSystemUiFlagsChanged();
@@ -491,6 +515,15 @@
                 mConsumer = newConsumer(useSharedState, event);
                 TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
                 mUncheckedConsumer = mConsumer;
+            } else if (mIsUserUnlocked && mMode == Mode.NO_BUTTON
+                    && canTriggerAssistantAction(event)) {
+                // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
+                // not interrupt it. QuickSwitch assumes that interruption can only happen if the
+                // next gesture is also quick switch.
+                mUncheckedConsumer =
+                        new AssistantTouchConsumer(this, mISystemUiProxy,
+                                mOverviewComponentObserver.getActivityControlHelper(),
+                                InputConsumer.NO_OP, mInputMonitorCompat);
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
@@ -505,6 +538,14 @@
                         || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
     }
 
+    private boolean canTriggerAssistantAction(MotionEvent ev) {
+        return mAssistantAvailable
+                && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
+                && (mAssistantLeftRegion.contains(ev.getX(), ev.getY()) ||
+                    mAssistantRightRegion.contains(ev.getX(), ev.getY()))
+                && !ActivityManagerWrapper.getInstance().isLockToAppActive();
+    }
+
     private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
         boolean isInValidSystemUiState = validSystemUiFlags();
 
@@ -525,10 +566,7 @@
         if (mMode == Mode.NO_BUTTON) {
             final ActivityControlHelper activityControl =
                     mOverviewComponentObserver.getActivityControlHelper();
-            if (mAssistantAvailable
-                    && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
-                    && AssistantTouchConsumer.withinTouchRegion(this, event)
-                    && !ActivityManagerWrapper.getInstance().isLockToAppActive()) {
+            if (canTriggerAssistantAction(event)) {
                 base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
                         mInputMonitorCompat);
             }
@@ -555,7 +593,7 @@
     private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
         final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
         if (!useSharedState) {
-            mSwipeSharedState.clearAllState();
+            sSwipeSharedState.clearAllState(false /* finishAnimation */);
         }
         if ((mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0) {
             // This handles apps showing over the lockscreen (e.g. camera)
@@ -565,22 +603,26 @@
         final ActivityControlHelper activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
 
-        if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher
-                && !mSwipeSharedState.recentsAnimationFinishInterrupted) {
+        if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
+                && !sSwipeSharedState.recentsAnimationFinishInterrupted) {
             return mResetGestureInputConsumer;
-        } else if (mSwipeSharedState.recentsAnimationFinishInterrupted) {
+        } else if (sSwipeSharedState.recentsAnimationFinishInterrupted) {
             // If the finish animation was interrupted, then continue using the other activity input
             // consumer but with the next task as the running task
             RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
-            info.id = mSwipeSharedState.nextRunningTaskId;
+            info.id = sSwipeSharedState.nextRunningTaskId;
             return createOtherActivityInputConsumer(event, info);
-        } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
+        } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
             return createOverviewInputConsumer(event);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
             return createOverviewInputConsumer(event);
         } else if (mGestureBlockingActivity != null && runningTaskInfo != null
                 && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
             return mResetGestureInputConsumer;
+        } else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+            return new FallbackNoButtonInputConsumer(this, activityControl,
+                    mInputMonitorCompat, sSwipeSharedState, mSwipeTouchRegion,
+                    mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo);
         } else {
             return createOtherActivityInputConsumer(event, runningTaskInfo);
         }
@@ -602,13 +644,13 @@
         return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
                 mOverviewComponentObserver.getOverviewIntent(), activityControl,
                 shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive,
-                mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
+                sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
                 disableHorizontalSwipe(event));
     }
 
     private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
         if (mMode == Mode.NO_BUTTON && taskInfo != null) {
-            return new DeviceLockedInputConsumer(this, mSwipeSharedState, mInputMonitorCompat,
+            return new DeviceLockedInputConsumer(this, sSwipeSharedState, mInputMonitorCompat,
                     mSwipeTouchRegion, taskInfo.taskId);
         } else {
             return mResetGestureInputConsumer;
@@ -623,7 +665,7 @@
             return mResetGestureInputConsumer;
         }
 
-        if (activity.getRootView().hasWindowFocus() || mSwipeSharedState.goingToLauncher) {
+        if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
             return new OverviewInputConsumer(activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
@@ -670,7 +712,7 @@
                     + mOverviewComponentObserver.getActivityControlHelper().isResumed());
             pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
             if (mConsumer.useSharedSwipeState()) {
-                mSwipeSharedState.dump("    ", pw);
+                sSwipeSharedState.dump("    ", pw);
             }
             pw.println("  mConsumer=" + mConsumer.getName());
             pw.println("FeatureFlags:");
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
index 837423a..38b5a13 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
@@ -39,6 +39,8 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
@@ -47,17 +49,14 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.touch.SwipeDetector;
 import com.android.quickstep.ActivityControlHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Touch consumer for handling events to launch assistant from launcher
  */
-public class AssistantTouchConsumer extends DelegateInputConsumer
-    implements SwipeDetector.Listener {
+public class AssistantTouchConsumer extends DelegateInputConsumer {
 
     private static final String TAG = "AssistantTouchConsumer";
     private static final long RETRACT_ANIMATION_DURATION_MS = 300;
@@ -68,7 +67,6 @@
     private static final int OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE = 83;
     private static final String INVOCATION_TYPE_KEY = "invocation_type";
     private static final int INVOCATION_TYPE_GESTURE = 1;
-    private static final int INVOCATION_TYPE_FLING = 6;
 
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
@@ -90,7 +88,7 @@
     private final float mSquaredSlop;
     private final ISystemUiProxy mSysUiProxy;
     private final Context mContext;
-    private final SwipeDetector mSwipeDetector;
+    private final GestureDetector mGestureDetector;
 
     public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
             ActivityControlHelper activityControlHelper, InputConsumer delegate,
@@ -107,8 +105,8 @@
 
         mSquaredSlop = slop * slop;
         mActivityControlHelper = activityControlHelper;
-        mSwipeDetector = new SwipeDetector(mContext, this, SwipeDetector.VERTICAL);
-        mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+
+        mGestureDetector = new GestureDetector(context, new AssistantGestureListener());
     }
 
     @Override
@@ -119,7 +117,7 @@
     @Override
     public void onMotionEvent(MotionEvent ev) {
         // TODO add logging
-        mSwipeDetector.onTouchEvent(ev);
+        mGestureDetector.onTouchEvent(ev);
 
         switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
@@ -171,13 +169,8 @@
                         mStartDragPos.set(mLastPos.x, mLastPos.y);
                         mDragTime = SystemClock.uptimeMillis();
 
-                        // Determine if angle is larger than threshold for assistant detection
-                        float angle = (float) Math.toDegrees(
-                            Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x));
-                        mDirection = angle > 90 ? UPLEFT : UPRIGHT;
-                        angle = angle > 90 ? 180 - angle : angle;
-
-                        if (angle > mAngleThreshold && angle < 90) {
+                        if (isValidAssistantGestureAngle(
+                            mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y)) {
                             setActive(ev);
                         } else {
                             mState = STATE_DELEGATE_ACTIVE;
@@ -261,40 +254,41 @@
         }
     }
 
-    public static boolean withinTouchRegion(Context context, MotionEvent ev) {
-        final Resources res = context.getResources();
-        final int width = res.getDisplayMetrics().widthPixels;
-        final int height = res.getDisplayMetrics().heightPixels;
-        final int size = res.getDimensionPixelSize(R.dimen.gestures_assistant_size);
-        return (ev.getX() > width - size || ev.getX() < size) && ev.getY() > height - size;
+    /**
+     * Determine if angle is larger than threshold for assistant detection
+     */
+    private boolean isValidAssistantGestureAngle(float deltaX, float deltaY) {
+        float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+        mDirection = angle > 90 ? UPLEFT : UPRIGHT;
+
+        // normalize so that angle is measured clockwise from horizontal in the bottom right corner
+        // and counterclockwise from horizontal in the bottom left corner
+        angle = angle > 90 ? 180 - angle : angle;
+        return (angle > mAngleThreshold && angle < 90);
     }
 
-    @Override
-    public void onDragStart(boolean start) {
-        // do nothing
-    }
+    private class AssistantGestureListener extends SimpleOnGestureListener {
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+            if (isValidAssistantGestureAngle(velocityX, -velocityY)
+                && !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) {
+                mLastProgress = 1;
+                try {
+                    mSysUiProxy.onAssistantGestureCompletion(
+                        (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
+                    startAssistantInternal(FLING);
 
-    @Override
-    public boolean onDrag(float displacement) {
-        return false;
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if (fling && !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) {
-            mLastProgress = 1;
-            try {
-                mSysUiProxy.onAssistantGestureCompletion(velocity);
-                startAssistantInternal(FLING);
-
-                Bundle args = new Bundle();
-                args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
-                mSysUiProxy.startAssistant(args);
-                mLaunchedAssistant = true;
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + mLastProgress,
-                    e);
+                    Bundle args = new Bundle();
+                    args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
+                    mSysUiProxy.startAssistant(args);
+                    mLaunchedAssistant = true;
+                } catch (RemoteException e) {
+                    Log.w(TAG,
+                        "Failed to send SysUI start/send assistant progress: " + mLastProgress,
+                        e);
+                }
             }
+            return true;
         }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
new file mode 100644
index 0000000..d05ca2a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -0,0 +1,358 @@
+/*
+ * 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.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+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.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.WindowTransformSwipeHandler.MIN_SWIPE_DURATION;
+import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.quickstep.util.RecentsAnimationListenerSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.InputMonitorCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+public class FallbackNoButtonInputConsumer implements InputConsumer, SwipeAnimationListener {
+
+    private static final int STATE_NOT_FINISHED = 0;
+    private static final int STATE_FINISHED_TO_HOME = 1;
+    private static final int STATE_FINISHED_TO_APP = 2;
+
+    private static final float PROGRESS_TO_END_GESTURE = -2;
+
+    private final ActivityControlHelper mActivityControlHelper;
+    private final InputMonitorCompat mInputMonitor;
+    private final Context mContext;
+    private final NavBarPosition mNavBarPosition;
+    private final SwipeSharedState mSwipeSharedState;
+    private final OverviewComponentObserver mOverviewComponentObserver;
+    private final int mRunningTaskId;
+
+    private final ClipAnimationHelper mClipAnimationHelper;
+    private final TransformParams mTransformParams = new TransformParams();
+    private final float mTransitionDragLength;
+    private final DeviceProfile mDP;
+
+    private final RectF mSwipeTouchRegion;
+    private final boolean mDisableHorizontalSwipe;
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+
+    private int mActivePointerId = -1;
+    // Slop used to determine when we say that the gesture has started.
+    private boolean mPassedPilferInputSlop;
+
+    private VelocityTracker mVelocityTracker;
+
+    // Distance after which we start dragging the window.
+    private final float mTouchSlop;
+
+    // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
+    private float mStartDisplacement;
+    private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
+    private float mProgress;
+
+    private int mState = STATE_NOT_FINISHED;
+
+    public FallbackNoButtonInputConsumer(Context context,
+            ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
+            SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
+            OverviewComponentObserver overviewComponentObserver,
+            boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
+        mContext = context;
+        mActivityControlHelper = activityControlHelper;
+        mInputMonitor = inputMonitor;
+        mOverviewComponentObserver = overviewComponentObserver;
+        mRunningTaskId = runningTaskInfo.id;
+
+        mSwipeSharedState = swipeSharedState;
+        mSwipeTouchRegion = swipeTouchRegion;
+        mDisableHorizontalSwipe = disableHorizontalSwipe;
+
+        mNavBarPosition = new NavBarPosition(context);
+        mVelocityTracker = VelocityTracker.obtain();
+
+        mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
+                * ViewConfiguration.get(context).getScaledTouchSlop();
+
+        mClipAnimationHelper = new ClipAnimationHelper(context);
+
+        mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
+        Rect tempRect = new Rect();
+        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
+                mDP, context, tempRect);
+        mClipAnimationHelper.updateTargetRect(tempRect);
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_FALLBACK_NO_BUTTON;
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            return;
+        }
+
+        mVelocityTracker.addMovement(ev);
+        if (ev.getActionMasked() == ACTION_POINTER_UP) {
+            mVelocityTracker.clear();
+        }
+
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mActivePointerId = ev.getPointerId(0);
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                break;
+            }
+            case ACTION_POINTER_DOWN: {
+                if (!mPassedPilferInputSlop) {
+                    // Cancel interaction in case of multi-touch interaction
+                    int ptrIdx = ev.getActionIndex();
+                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+                        forceCancelGesture(ev);
+                    }
+                }
+                break;
+            }
+            case ACTION_POINTER_UP: {
+                int ptrIdx = ev.getActionIndex();
+                int ptrId = ev.getPointerId(ptrIdx);
+                if (ptrId == mActivePointerId) {
+                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+                    mDownPos.set(
+                            ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+                            ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+                    mActivePointerId = ev.getPointerId(newPointerIdx);
+                }
+                break;
+            }
+            case ACTION_MOVE: {
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == INVALID_POINTER_ID) {
+                    break;
+                }
+                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                float displacement = getDisplacement(ev);
+
+                if (!mPassedPilferInputSlop) {
+                    if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x)
+                            > Math.abs(mLastPos.y - mDownPos.y)) {
+                        // Horizontal gesture is not allowed in this region
+                        forceCancelGesture(ev);
+                        break;
+                    }
+
+                    if (Math.abs(displacement) >= mTouchSlop) {
+                        mPassedPilferInputSlop = true;
+
+                        // Deferred gesture, start the animation and gesture tracking once
+                        // we pass the actual touch slop
+                        startTouchTrackingForWindowAnimation(displacement);
+                    }
+                } else {
+                    updateDisplacement(displacement - mStartDisplacement);
+                }
+                break;
+            }
+            case ACTION_CANCEL:
+            case ACTION_UP: {
+                finishTouchTracking(ev);
+                break;
+            }
+        }
+    }
+
+    private void startTouchTrackingForWindowAnimation(float displacement) {
+        mStartDisplacement = Math.min(displacement, -mTouchSlop);
+
+        RecentsAnimationListenerSet listenerSet =
+                mSwipeSharedState.newRecentsAnimationListenerSet();
+        listenerSet.addListener(this);
+        Intent homeIntent = mOverviewComponentObserver.getHomeIntent();
+        BackgroundExecutor.get().submit(
+                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+                        homeIntent, null, listenerSet, null, null));
+
+        ActivityManagerWrapper.getInstance().closeSystemWindows(
+                CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        mInputMonitor.pilferPointers();
+    }
+
+    private void updateDisplacement(float displacement) {
+        mProgress = displacement / mTransitionDragLength;
+        mTransformParams.setProgress(mProgress);
+
+        if (mSwipeAnimationTargetSet != null) {
+            mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+        }
+    }
+
+    private void forceCancelGesture(MotionEvent ev) {
+        int action = ev.getAction();
+        ev.setAction(ACTION_CANCEL);
+        finishTouchTracking(ev);
+        ev.setAction(action);
+    }
+
+    /**
+     * Called when the gesture has ended. Does not correlate to the completion of the interaction as
+     * the animation can still be running.
+     */
+    private void finishTouchTracking(MotionEvent ev) {
+        if (ev.getAction() == ACTION_CANCEL) {
+            mState = STATE_FINISHED_TO_APP;
+        } else {
+            mVelocityTracker.computeCurrentVelocity(1000,
+                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
+            float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
+            float velocity = mNavBarPosition.isRightEdge() ? velocityX
+                    : mNavBarPosition.isLeftEdge() ? -velocityX
+                            : velocityY;
+            float flingThreshold = mContext.getResources()
+                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+            boolean isFling = Math.abs(velocity) > flingThreshold;
+
+            boolean goingHome;
+            if (!isFling) {
+                goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW;
+            } else {
+                goingHome = velocity < 0;
+            }
+
+            if (goingHome) {
+                mState = STATE_FINISHED_TO_HOME;
+            } else {
+                mState = STATE_FINISHED_TO_APP;
+            }
+        }
+
+        if (mSwipeAnimationTargetSet != null) {
+            finishAnimationTargetSet();
+        }
+    }
+
+    private void finishAnimationTargetSet() {
+        if (mState == STATE_FINISHED_TO_APP) {
+            mSwipeAnimationTargetSet.finishController(false, null, false);
+        } else {
+            if (mProgress < PROGRESS_TO_END_GESTURE) {
+                mSwipeAnimationTargetSet.finishController(true, null, true);
+            } else {
+                long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1)
+                        * MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE));
+                if (duration < 0) {
+                    duration = MIN_SWIPE_DURATION;
+                }
+
+                ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE);
+                anim.addUpdateListener(a -> {
+                    float p = (Float) anim.getAnimatedValue();
+                    mTransformParams.setProgress(p);
+                    mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+                });
+                anim.setDuration(duration);
+                anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mSwipeAnimationTargetSet.finishController(true, null, true);
+                    }
+                });
+                anim.start();
+            }
+        }
+    }
+
+    @Override
+    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+        mSwipeAnimationTargetSet = targetSet;
+        Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
+        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+
+        mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
+        if (runningTaskTarget != null) {
+            mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+        }
+        mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
+
+        overviewStackBounds
+                .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5);
+        mClipAnimationHelper.updateTargetRect(overviewStackBounds);
+        mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+
+        if (mState != STATE_NOT_FINISHED) {
+            finishAnimationTargetSet();
+        }
+    }
+
+    @Override
+    public void onRecentsAnimationCanceled() { }
+
+    private float getDisplacement(MotionEvent ev) {
+        if (mNavBarPosition.isRightEdge()) {
+            return ev.getX() - mDownPos.x;
+        } else if (mNavBarPosition.isLeftEdge()) {
+            return mDownPos.x - ev.getX();
+        } else {
+            return ev.getY() - mDownPos.y;
+        }
+    }
+
+    @Override
+    public boolean allowInterceptByParent() {
+        return !mPassedPilferInputSlop;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index a1e5d47..f5cf654 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -33,6 +33,7 @@
     int TYPE_SCREEN_PINNED = 1 << 6;
     int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
     int TYPE_RESET_GESTURE = 1 << 8;
+    int TYPE_FALLBACK_NO_BUTTON = 1 << 9;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -44,6 +45,7 @@
             "TYPE_SCREEN_PINNED",           // 6
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
+            "TYPE_FALLBACK_NO_BUTTON",      // 9
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 6bc543f..4c137d3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -79,7 +79,7 @@
     private static final String UP_EVT = "OtherActivityInputConsumer.UP";
 
     // TODO: Move to quickstep contract
-    private static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+    public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
 
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
index 56cba21..8eede81 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
@@ -39,7 +39,7 @@
     public void onMotionEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN
                 && mSwipeSharedState.getActiveListener() != null) {
-            mSwipeSharedState.clearAllState();
+            mSwipeSharedState.clearAllState(false /* finishAnimation */);
         }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 9eda2f9..07e9686 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -17,19 +17,15 @@
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.Nullable;
-import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -40,6 +36,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -147,9 +144,8 @@
         long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS);
 
         v.setTranslationY(mSpringTransY);
-        SpringObjectAnimator springTransY = new SpringObjectAnimator<>(
-                new ViewProgressProperty(v, View.TRANSLATION_Y), "staggeredSpringTransY", 1f,
-                DAMPING_RATIO, STIFFNESS, mSpringTransY, 0);
+        SpringObjectAnimator springTransY = new SpringObjectAnimator<>(v, VIEW_TRANSLATE_Y,
+                1f, DAMPING_RATIO, STIFFNESS, mSpringTransY, 0);
         springTransY.setStartDelay(startDelay);
         mAnimators.add(springTransY);
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index df9efa2..381c27a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -106,6 +106,10 @@
         finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
     }
 
+    public void finishAnimation() {
+        finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
+    }
+
     public interface SwipeAnimationListener {
 
         void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet);
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 5b2e27e..03441c8 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
@@ -33,9 +33,11 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateListener;
@@ -250,4 +252,16 @@
             setDisallowScrollToClearAll(!hasClearAllButton);
         }
     }
+
+    @Override
+    protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            // Allow touches to go through to the hotseat.
+            Hotseat hotseat = mActivity.getHotseat();
+            boolean touchingHotseat = hotseat.isShown()
+                    && mActivity.getDragLayer().isEventOverView(hotseat, ev, this);
+            return !touchingHotseat;
+        }
+        return super.shouldStealTouchFromSiblingsBelow(ev);
+    }
 }
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 9058e7e..a98df0f 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
@@ -16,9 +16,13 @@
 
 package com.android.quickstep.views;
 
+import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
+
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
@@ -79,7 +83,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
@@ -124,10 +127,6 @@
 
     private static final String TAG = RecentsView.class.getSimpleName();
 
-    public static final float SPRING_MIN_VISIBLE_CHANGE = 0.001f;
-    public static final float SPRING_DAMPING_RATIO = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
-    public static final float SPRING_STIFFNESS = SpringForce.STIFFNESS_MEDIUM;
-
     public static final FloatProperty<RecentsView> CONTENT_ALPHA =
             new FloatProperty<RecentsView>("contentAlpha") {
                 @Override
@@ -521,6 +520,10 @@
 
 
         // Do not let touch escape to siblings below this view.
+        return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev);
+    }
+
+    protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) {
         return true;
     }
 
@@ -1027,9 +1030,10 @@
     private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) {
         addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
         if (QUICKSTEP_SPRINGS.get() && taskView instanceof TaskView)
-            addAnim(new SpringObjectAnimator<>(new ViewProgressProperty(taskView,
-                            View.TRANSLATION_Y), "taskViewTransY", SPRING_MIN_VISIBLE_CHANGE,
-                            SPRING_DAMPING_RATIO, SPRING_STIFFNESS, 0, -taskView.getHeight()),
+            addAnim(new SpringObjectAnimator<>(taskView, VIEW_TRANSLATE_Y,
+                            MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
+                            SpringForce.STIFFNESS_MEDIUM,
+                            0, -taskView.getHeight()),
                     duration, LINEAR, anim);
         else {
             addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
@@ -1105,10 +1109,10 @@
                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
                 if (scrollDiff != 0) {
                     if (QUICKSTEP_SPRINGS.get() && child instanceof TaskView) {
-                        addAnim(new SpringObjectAnimator<>(
-                                new ViewProgressProperty(child, View.TRANSLATION_X),
-                                "taskViewTransX", SPRING_MIN_VISIBLE_CHANGE, SPRING_DAMPING_RATIO,
-                                SPRING_STIFFNESS, 0, scrollDiff), duration, ACCEL, anim);
+                        addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X,
+                                MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
+                                SpringForce.STIFFNESS_MEDIUM,
+                                0, scrollDiff), duration, ACCEL, anim);
                     } else {
                         addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration,
                                 ACCEL, anim);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 6f10b42..d55a520 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -367,8 +367,11 @@
         }
 
         mRotated = isRotated;
-        updateOverlay();
         invalidate();
+
+        // Update can be called from {@link #onSizeChanged} during layout, post handling of overlay
+        // as overlay could modify the views in the overlay as a side effect of its update.
+        post(this::updateOverlay);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index e7e4189..b26fdce 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -368,6 +368,9 @@
         } else {
             mSnapshotView.setThumbnail(null, null);
             setIcon(null);
+            // Reset the task thumbnail reference as well (it will be fetched from the cache or
+            // reloaded next time we need it)
+            mTask.thumbnail = null;
         }
     }
 
@@ -488,9 +491,6 @@
         mSnapshotView.setThumbnail(mTask, null);
         setOverlayEnabled(false);
         onTaskListVisibilityChanged(false);
-        if (mTask != null) {
-            mTask.thumbnail = null;
-        }
     }
 
     @Override
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 82d1aa6..71259fd 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -24,7 +24,7 @@
 
     <dimen name="recents_page_spacing">10dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
-    <dimen name="overview_peek_distance">32dp</dimen>
+    <dimen name="overview_peek_distance">96dp</dimen>
 
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
              loading full resolution screenshots. -->
@@ -66,7 +66,8 @@
     <dimen name="shelf_surface_offset">24dp</dimen>
 
     <!-- Assistant Gestures -->
-    <dimen name="gestures_assistant_size">48dp</dimen>
+    <!-- Distance from the vertical edges of the screen in which assist gestures are recognized -->
+    <dimen name="gestures_assistant_width">48dp</dimen>
     <dimen name="gestures_assistant_drag_threshold">55dp</dimen>
 
     <!-- Distance to move elements when swiping up to go home from launcher -->
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 8643160..44324cb 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -765,7 +765,7 @@
                 LauncherAnimationRunner.AnimationResult result) {
             if (!mLauncher.hasBeenResumed()) {
                 // If launcher is not resumed, wait until new async-frame after resume
-                mLauncher.setOnResumeCallback(() ->
+                mLauncher.addOnResumeCallback(() ->
                         postAsyncCallback(mHandler, () ->
                                 onCreateAnimation(targetCompats, result)));
                 return;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index ab24f5f..85a9545 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.SysUINavigationMode;
 
 /**
  * Definition for AllApps state
@@ -63,7 +64,13 @@
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
                 .getWorkspaceScaleAndTranslation(launcher);
-        scaleAndTranslation.scale = 1;
+        if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
+            float normalScale = 1;
+            // Scale down halfway to where we'd be in overview, to prepare for a potential pause.
+            scaleAndTranslation.scale = (scaleAndTranslation.scale + normalScale) / 2;
+        } else {
+            scaleAndTranslation.scale = 1;
+        }
         return scaleAndTranslation;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index fee1820..f5ba372 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -17,17 +17,22 @@
 
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.ACTION_CANCEL;
 
+import android.graphics.PointF;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.view.Window;
+import android.view.WindowManager;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.touch.TouchEventTranslator;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.RecentsModel;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -36,18 +41,29 @@
 
 /**
  * TouchController for handling touch events that get sent to the StatusBar. Once the
- * Once the event delta y passes the touch slop, the events start getting forwarded.
+ * Once the event delta mDownY passes the touch slop, the events start getting forwarded.
  * All events are offset by initial Y value of the pointer.
  */
 public class StatusBarTouchController implements TouchController {
 
     private static final String TAG = "StatusBarController";
 
+    /**
+     * Window flag: Enable touches to slide out of a window into neighboring
+     * windows in mid-gesture instead of being captured for the duration of
+     * the gesture.
+     *
+     * This flag changes the behavior of touch focus for this window only.
+     * Touches can slide out of the window but they cannot necessarily slide
+     * back in (unless the other window with touch focus permits it).
+     */
+    private static final int FLAG_SLIPPERY = 0x20000000;
+
     protected final Launcher mLauncher;
-    protected final TouchEventTranslator mTranslator;
     private final float mTouchSlop;
     private ISystemUiProxy mSysUiProxy;
     private int mLastAction;
+    private final SparseArray<PointF> mDownEvents;
 
     /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
     private boolean mCanIntercept;
@@ -56,7 +72,7 @@
         mLauncher = l;
         // Guard against TAPs by increasing the touch slop.
         mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
-        mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev));
+        mDownEvents = new SparseArray<>();
     }
 
     @Override
@@ -64,7 +80,6 @@
         writer.println(prefix + "mCanIntercept:" + mCanIntercept);
         writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
         writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
-
     }
 
     private void dispatchTouchEvent(MotionEvent ev) {
@@ -81,26 +96,31 @@
     @Override
     public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
+        int idx = ev.getActionIndex();
+        int pid = ev.getPointerId(idx);
         if (action == ACTION_DOWN) {
             mCanIntercept = canInterceptTouch(ev);
             if (!mCanIntercept) {
                 return false;
             }
-            mTranslator.reset();
-            mTranslator.setDownParameters(0, ev);
+            mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
         } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-            // Check!! should only set it only when threshold is not entered.
-            mTranslator.setDownParameters(ev.getActionIndex(), ev);
+           // Check!! should only set it only when threshold is not entered.
+           mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
         }
         if (!mCanIntercept) {
             return false;
         }
         if (action == ACTION_MOVE) {
-            float dy = ev.getY() - mTranslator.getDownY();
-            float dx = ev.getX() - mTranslator.getDownX();
-            if (dy > mTouchSlop && dy > Math.abs(dx)) {
-                mTranslator.dispatchDownEvents(ev);
-                mTranslator.processMotionEvent(ev);
+            float dy = ev.getY(idx) - mDownEvents.get(pid).y;
+            float dx = ev.getX(idx) - mDownEvents.get(pid).x;
+            // Currently input dispatcher will not do touch transfer if there are more than
+            // one touch pointer. Hence, even if slope passed, only set the slippery flag
+            // when there is single touch event. (context: InputDispatcher.cpp line 1445)
+            if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) {
+                ev.setAction(ACTION_DOWN);
+                dispatchTouchEvent(ev);
+                setWindowSlippery(true);
                 return true;
             }
             if (Math.abs(dx) > mTouchSlop) {
@@ -110,13 +130,27 @@
         return false;
     }
 
-
     @Override
     public final boolean onControllerTouchEvent(MotionEvent ev) {
-        mTranslator.processMotionEvent(ev);
+        if (ev.getAction() == ACTION_UP || ev.getAction() == ACTION_CANCEL) {
+            dispatchTouchEvent(ev);
+            setWindowSlippery(false);
+            return true;
+        }
         return true;
     }
 
+    private void setWindowSlippery(boolean enable) {
+        Window w = mLauncher.getWindow();
+        WindowManager.LayoutParams wlp = w.getAttributes();
+        if (enable) {
+            wlp.flags |= FLAG_SLIPPERY;
+        } else {
+            wlp.flags &= ~FLAG_SLIPPERY;
+        }
+        w.setAttributes(wlp);
+    }
+
     private boolean canInterceptTouch(MotionEvent ev) {
         if (!mLauncher.isInState(LauncherState.NORMAL) ||
                 AbstractFloatingView.getTopOpenViewWithType(mLauncher,
@@ -132,4 +166,4 @@
         mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
         return mSysUiProxy != null;
     }
-}
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0a73b8b..0738aff 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -33,7 +33,6 @@
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
-import com.android.systemui.shared.system.QuickStepContract;
 import java.util.ArrayList;
 
 /**
@@ -58,7 +57,9 @@
     private String mUpdateRegisteredPackage;
     private ActivityControlHelper mActivityControlHelper;
     private Intent mOverviewIntent;
+    private Intent mHomeIntent;
     private int mSystemUiStateFlags;
+    private boolean mIsHomeAndOverviewSame;
 
     public OverviewComponentObserver(Context context) {
         mContext = context;
@@ -93,11 +94,14 @@
 
         final String overviewIntentCategory;
         ComponentName overviewComponent;
+        mHomeIntent = null;
+
         if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
                 (defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
             overviewComponent = mMyHomeComponent;
             mActivityControlHelper = new LauncherActivityControllerHelper();
+            mIsHomeAndOverviewSame = true;
             overviewIntentCategory = Intent.CATEGORY_HOME;
 
             if (mUpdateRegisteredPackage != null) {
@@ -109,8 +113,12 @@
             // The default home app is a different launcher. Use the fallback Overview instead.
             overviewComponent = new ComponentName(mContext, RecentsActivity.class);
             mActivityControlHelper = new FallbackActivityControllerHelper();
+            mIsHomeAndOverviewSame = false;
             overviewIntentCategory = Intent.CATEGORY_DEFAULT;
 
+            mHomeIntent = new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_HOME)
+                    .setComponent(defaultHome);
             // User's default home app can change as a result of package updates of this app (such
             // as uninstalling the app or removing the "Launcher" feature in an update).
             // Listen for package updates of this app (and remove any previously attached
@@ -135,6 +143,9 @@
                 .addCategory(overviewIntentCategory)
                 .setComponent(overviewComponent)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (mHomeIntent == null) {
+            mHomeIntent = mOverviewIntent;
+        }
     }
 
     /**
@@ -159,6 +170,20 @@
     }
 
     /**
+     * Get the current intent for going to the home activity.
+     */
+    public Intent getHomeIntent() {
+        return mHomeIntent;
+    }
+
+    /**
+     * Returns true if home and overview are same activity.
+     */
+    public boolean isHomeAndOverviewSame() {
+        return mIsHomeAndOverviewSame;
+    }
+
+    /**
      * Get the current activity control helper for managing interactions to the overview activity.
      *
      * @return the current activity control helper
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
similarity index 80%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepProcessInitializer.java
rename to quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index befeee0..7bfa9a0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.UserManager;
@@ -22,17 +23,29 @@
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.MainProcessInitializer;
+import com.android.launcher3.Utilities;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 
 @SuppressWarnings("unused")
 public class QuickstepProcessInitializer extends MainProcessInitializer {
 
     private static final String TAG = "QuickstepProcessInitializer";
+    private static final int HEAP_LIMIT_MB = 250;
 
     public QuickstepProcessInitializer(Context context) { }
 
     @Override
     protected void init(Context context) {
+        if (Utilities.IS_DEBUG_DEVICE) {
+            try {
+                // Trigger a heap dump if the PSS reaches beyond the target heap limit
+                final ActivityManager am = context.getSystemService(ActivityManager.class);
+                am.setWatchHeapLimit(HEAP_LIMIT_MB * 1024 * 1024);
+            } catch (SecurityException e) {
+                // Do nothing
+            }
+        }
+
         // Workaround for b/120550382, an external app can cause the launcher process to start for
         // a work profile user which we do not support. Disable the application immediately when we
         // detect this to be the case.
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 3538373..f27ba85 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -84,7 +84,7 @@
         final int requestLoadId = mChangeId;
         Runnable resultCallback = callback == null
                 ? () -> { }
-                : () -> callback.accept(mTasks);
+                : () -> callback.accept(copyOf(mTasks));
 
         if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
             // The list is up to date, callback with the same list
@@ -183,4 +183,14 @@
 
         return allTasks;
     }
+
+    private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
+        ArrayList<Task> newTasks = new ArrayList<>();
+        for (int i = 0; i < tasks.size(); i++) {
+            Task t = tasks.get(i);
+            newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable,
+                    t.isLocked, t.taskDescription, t.topActivity));
+        }
+        return newTasks;
+    }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index c8aed81..050bdff 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -110,7 +110,7 @@
         float y = insets.top + Math.max(topIconMargin,
                 (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
         outRect.set(Math.round(x), Math.round(y),
-                Math.round(x + outWidth), Math.round(y + outHeight));
+                Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
     }
 
     public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 36521e5..b6ddb5f 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
@@ -29,6 +30,7 @@
 import android.graphics.Path.Direction;
 import android.graphics.Path.Op;
 import android.util.AttributeSet;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
@@ -69,6 +71,9 @@
     private int mMidAlpha;
     private float mMidProgress;
 
+    private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
+    private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
+
     private float mShiftRange;
 
     private final float mShelfOffset;
@@ -120,6 +125,15 @@
     @Override
     public void onNavigationModeChanged(Mode newMode) {
         mSysUINavigationMode = newMode;
+        // Note that these interpolators are inverted because progress goes 1 to 0.
+        if (mSysUINavigationMode == Mode.NO_BUTTON) {
+            // Show the shelf more quickly before reaching overview progress.
+            mBeforeMidProgressColorInterpolator = ACCEL_2;
+            mAfterMidProgressColorInterpolator = ACCEL;
+        } else {
+            mBeforeMidProgressColorInterpolator = ACCEL;
+            mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f);
+        }
     }
 
     @Override
@@ -171,7 +185,7 @@
             mRemainingScreenColor = 0;
 
             int alpha = Math.round(Utilities.mapToRange(
-                    mProgress, mMidProgress, 1, mMidAlpha, 0, ACCEL));
+                    mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator));
             mShelfColor = setColorAlphaBound(mEndScrim, alpha);
         } else {
             mDragHandleOffset += mShiftRange * (mMidProgress - mProgress);
@@ -179,7 +193,7 @@
             // Note that these ranges and interpolators are inverted because progress goes 1 to 0.
             int alpha = Math.round(
                     Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha,
-                            (float) mMidAlpha, Interpolators.clampToProgress(ACCEL, 0.5f, 1f)));
+                            (float) mMidAlpha, mAfterMidProgressColorInterpolator));
             mShelfColor = setColorAlphaBound(mEndScrim, alpha);
 
             int remainingScrimAlpha = Math.round(
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 0135911..e5f949b 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -43,10 +43,12 @@
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
@@ -62,10 +64,14 @@
     private final LauncherInstrumentation mLauncher;
     private final ActivityInfo mOtherLauncherActivity;
 
-    @Rule public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
-    @Rule public final TestRule mQuickstepOnOffExecutor;
+    @Rule
+    public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
 
-    @Rule public final TestRule mSetLauncherCommand;
+    @Rule
+    public final TestRule mSetLauncherCommand;
+
+    @Rule
+    public final TestRule mOrderSensitiveRules;
 
     public FallbackRecentsTest() throws RemoteException {
         Instrumentation instrumentation = getInstrumentation();
@@ -74,7 +80,10 @@
         mDevice.setOrientationNatural();
         mLauncher = new LauncherInstrumentation(instrumentation);
 
-        mQuickstepOnOffExecutor = new NavigationModeSwitchRule(mLauncher);
+        mOrderSensitiveRules = RuleChain.
+                outerRule(new NavigationModeSwitchRule(mLauncher)).
+                around(new FailureWatcher(mDevice));
+
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
                 getHomeIntentInPackage(context),
                 MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 90763b8..3b35c86 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -159,13 +159,20 @@
                     }
 
                     for (int i = 0; i != 100; ++i) {
-                        if (mLauncher.getNavigationModel() == expectedMode) {
-                            Thread.sleep(5000);
-                            return;
-                        }
+                        if (mLauncher.getNavigationModel() == expectedMode) break;
                         Thread.sleep(100);
                     }
-                    Assert.fail("Couldn't switch to " + overlayPackage);
+                    Assert.assertTrue("Couldn't switch to " + overlayPackage,
+                            mLauncher.getNavigationModel() == expectedMode);
+
+                    for (int i = 0; i != 100; ++i) {
+                        if (mLauncher.getNavigationModeMismatchError() == null) break;
+                        Thread.sleep(100);
+                    }
+                    final String error = mLauncher.getNavigationModeMismatchError();
+                    Assert.assertTrue("Switching nav mode: " + error, error == null);
+
+                    Thread.sleep(5000);
                 }
 
                 private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index f02859f..9e3bf2f 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -208,7 +208,7 @@
 
     @Test
     @NavigationModeSwitch
-    @PortraitLandscape
+//    @PortraitLandscape
     public void testBackground() throws Exception {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         final Background background = mLauncher.getBackground();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 7085c60..1619e36 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -43,6 +43,7 @@
 import android.widget.TextView;
 
 import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DrawableFactory;
@@ -227,6 +228,18 @@
         applyFromWorkspaceItem(info, false);
     }
 
+    @Override
+    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+        if (delegate instanceof LauncherAccessibilityDelegate) {
+            super.setAccessibilityDelegate(delegate);
+        } else {
+            // NO-OP
+            // Workaround for b/129745295 where RecyclerView is setting our Accessibility
+            // delegate incorrectly. There are no cases when we shouldn't be using the
+            // LauncherAccessibilityDelegate for BubbleTextView.
+        }
+    }
+
     public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean promiseStateChanged) {
         applyIconAndLabel(info);
         setTag(info);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 80ea78f..d9af4da 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -244,7 +244,7 @@
 
     @Thunk boolean mWorkspaceLoading = true;
 
-    private OnResumeCallback mOnResumeCallback;
+    private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
 
     private ViewOnDrawExecutor mPendingExecutor;
 
@@ -430,6 +430,10 @@
         super.onConfigurationChanged(newConfig);
     }
 
+    public void reload() {
+        onIdpChanged(mDeviceProfile.inv);
+    }
+
     private boolean supportsFakeLandscapeUI() {
         return FeatureFlags.FAKE_LANDSCAPE_UI.get() && !mRotationHelper.homeScreenCanRotate();
     }
@@ -865,6 +869,7 @@
     @Override
     protected void onStop() {
         super.onStop();
+
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStop();
         }
@@ -947,7 +952,10 @@
         mHandler.removeCallbacks(mHandleDeferredResume);
         Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
 
-        setOnResumeCallback(null);
+        for (OnResumeCallback cb : mOnResumeCallbacks) {
+            cb.onLauncherResume();
+        }
+        mOnResumeCallbacks.clear();
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onResume();
@@ -1801,6 +1809,16 @@
             android.util.Log.d(TestProtocol.NO_START_TAG,
                     "startActivitySafely outer");
         }
+
+        if (!hasBeenResumed()) {
+            // Workaround an issue where the WM launch animation is clobbered when finishing the
+            // recents animation into launcher. Defer launching the activity until Launcher is
+            // next resumed.
+            addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
+            UiFactory.clearSwipeSharedState(true /* finishAnimation */);
+            return true;
+        }
+
         boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
         if (success && v instanceof BubbleTextView) {
             // This is set to the view that launched the activity that navigated the user away
@@ -1809,7 +1827,7 @@
             // state when we return to launcher.
             BubbleTextView btv = (BubbleTextView) v;
             btv.setStayPressed(true);
-            setOnResumeCallback(btv);
+            addOnResumeCallback(btv);
         }
         return success;
     }
@@ -1857,11 +1875,8 @@
         return result;
     }
 
-    public void setOnResumeCallback(OnResumeCallback callback) {
-        if (mOnResumeCallback != null) {
-            mOnResumeCallback.onLauncherResume();
-        }
-        mOnResumeCallback = callback;
+    public void addOnResumeCallback(OnResumeCallback callback) {
+        mOnResumeCallbacks.add(callback);
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 04f2b52..74362ed 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
 import android.util.Property;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
@@ -47,15 +48,15 @@
                 }
             };
 
-    public static final Property<View, Float> SCALE_PROPERTY =
-            new Property<View, Float>(Float.class, "scale") {
+    public static final FloatProperty<View> SCALE_PROPERTY =
+            new FloatProperty<View>("scale") {
                 @Override
                 public Float get(View view) {
                     return view.getScaleX();
                 }
 
                 @Override
-                public void set(View view, Float scale) {
+                public void setValue(View view, float scale) {
                     view.setScaleX(scale);
                     view.setScaleY(scale);
                 }
@@ -92,23 +93,31 @@
                 }
             };
 
-    public static class ViewProgressProperty implements ProgressInterface {
-        View mView;
-        Property<View, Float> mProperty;
+    public static final FloatProperty<View> VIEW_TRANSLATE_X =
+            View.TRANSLATION_X instanceof FloatProperty ? (FloatProperty) View.TRANSLATION_X
+                    : new FloatProperty<View>("translateX") {
+                        @Override
+                        public void setValue(View view, float v) {
+                            view.setTranslationX(v);
+                        }
 
-        public ViewProgressProperty(View view, Property<View, Float> property) {
-            mView = view;
-            mProperty = property;
-        }
+                        @Override
+                        public Float get(View view) {
+                            return view.getTranslationX();
+                        }
+                    };
 
-        @Override
-        public void setProgress(float progress) {
-            mProperty.set(mView, progress);
-        }
+    public static final FloatProperty<View> VIEW_TRANSLATE_Y =
+            View.TRANSLATION_Y instanceof FloatProperty ? (FloatProperty) View.TRANSLATION_Y
+                    : new FloatProperty<View>("translateY") {
+                        @Override
+                        public void setValue(View view, float v) {
+                            view.setTranslationY(v);
+                        }
 
-        @Override
-        public float getProgress() {
-            return mProperty.get(mView);
-        }
-    }
+                        @Override
+                        public Float get(View view) {
+                            return view.getTranslationY();
+                        }
+                    };
 }
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index 4bddc6a..c55c120 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 
+import android.animation.Animator;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.graphics.Rect;
@@ -55,4 +56,15 @@
     public boolean supportsAdaptiveIconAnimation() {
         return false;
     }
+
+    /**
+     * Number of animations which run on state properties.
+     */
+    public int getStateElementAnimationsCount() {
+        return 0;
+    }
+
+    public Animator createStateElementAnimation(int index, float... values) {
+        throw new RuntimeException("Unknown gesture animation " + index);
+    }
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ac392a6..d79f5d5 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -301,7 +301,8 @@
                 }
             }
         } else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
-            forceReload();
+            Launcher l = (Launcher) getCallback();
+            l.reload();
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 1480648..dcfd272 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -17,7 +17,18 @@
 
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+import static android.view.View.VISIBLE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -30,11 +41,11 @@
 
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.states.SpringLoadedState;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.uioverrides.states.AllAppsState;
 import com.android.launcher3.uioverrides.states.OverviewState;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 import java.util.Arrays;
@@ -272,6 +283,46 @@
         }
     }
 
+    /**
+     * Prepares for a non-user controlled animation from fromState to this state. Preparations
+     * include:
+     * - Setting interpolators for various animations included in the state transition.
+     * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
+     */
+    public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
+            AnimatorSetBuilder builder) {
+        if (this == NORMAL && fromState == OVERVIEW) {
+            builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
+            builder.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
+            builder.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
+            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
+            builder.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+            Workspace workspace = launcher.getWorkspace();
+
+            // Start from a higher workspace scale, but only if we're invisible so we don't jump.
+            boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
+            if (isWorkspaceVisible) {
+                CellLayout currentChild = (CellLayout) workspace.getChildAt(
+                        workspace.getCurrentPage());
+                isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
+                        && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
+            }
+            if (!isWorkspaceVisible) {
+                workspace.setScaleX(0.92f);
+                workspace.setScaleY(0.92f);
+            }
+            Hotseat hotseat = launcher.getHotseat();
+            boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
+            if (!isHotseatVisible) {
+                hotseat.setScaleX(0.92f);
+                hotseat.setScaleY(0.92f);
+            }
+        } else if (this == NORMAL && fromState == OVERVIEW_PEEK) {
+            // Keep fully visible until the very end (when overview is offscreen) to make invisible.
+            builder.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
+        }
+    }
+
     protected static void dispatchWindowStateChanged(Launcher launcher) {
         launcher.getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
     }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 8b03691..2c8c208 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -16,23 +16,7 @@
 
 package com.android.launcher3;
 
-import static android.view.View.VISIBLE;
-
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 
 import android.animation.Animator;
@@ -42,8 +26,6 @@
 import android.os.Looper;
 import android.util.Log;
 
-import androidx.annotation.IntDef;
-
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
@@ -58,6 +40,8 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
+import androidx.annotation.IntDef;
+
 /**
  * TODO: figure out what kind of tests we can write for this
  *
@@ -127,6 +111,9 @@
     private final Launcher mLauncher;
     private final ArrayList<StateListener> mListeners = new ArrayList<>();
 
+    // Animators which are run on properties also controlled by state animations.
+    private Animator[] mStateElementAnimators;
+
     private StateHandler[] mStateHandlers;
     private LauncherState mState = NORMAL;
 
@@ -223,13 +210,18 @@
     }
 
     public void reapplyState(boolean cancelCurrentAnimation) {
+        boolean wasInAnimation = mConfig.mCurrentAnimation != null;
         if (cancelCurrentAnimation) {
+            cancelAllStateElementAnimation();
             cancelAnimation();
         }
         if (mConfig.mCurrentAnimation == null) {
             for (StateHandler handler : getStateHandlers()) {
                 handler.setState(mState);
             }
+            if (wasInAnimation) {
+                onStateTransitionEnd(mState);
+            }
         }
     }
 
@@ -262,6 +254,7 @@
         mConfig.reset();
 
         if (!animated) {
+            cancelAllStateElementAnimation();
             onStateTransitionStart(state);
             for (StateHandler handler : getStateHandlers()) {
                 handler.setState(state);
@@ -310,47 +303,7 @@
      */
     public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
             AnimatorSetBuilder builder) {
-        if (fromState == NORMAL && toState == OVERVIEW) {
-            builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
-            builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
-            builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
-            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
-            builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
-
-            // Start from a higher overview scale, but only if we're invisible so we don't jump.
-            UiFactory.prepareToShowOverview(mLauncher);
-        } else if (fromState == OVERVIEW && toState == NORMAL) {
-            builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
-            builder.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
-            builder.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
-            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
-            builder.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
-            Workspace workspace = mLauncher.getWorkspace();
-
-            // Start from a higher workspace scale, but only if we're invisible so we don't jump.
-            boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
-            if (isWorkspaceVisible) {
-                CellLayout currentChild = (CellLayout) workspace.getChildAt(
-                        workspace.getCurrentPage());
-                isWorkspaceVisible = currentChild.getVisibility() == VISIBLE
-                        && currentChild.getShortcutsAndWidgets().getAlpha() > 0;
-            }
-            if (!isWorkspaceVisible) {
-                workspace.setScaleX(0.92f);
-                workspace.setScaleY(0.92f);
-            }
-            Hotseat hotseat = workspace.getHotseat();
-            boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
-            if (!isHotseatVisible) {
-                hotseat.setScaleX(0.92f);
-                hotseat.setScaleY(0.92f);
-            }
-        } else if (fromState == NORMAL && toState == OVERVIEW_PEEK) {
-            builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
-        } else if (fromState == OVERVIEW_PEEK && toState == NORMAL) {
-            // Keep fully visible until the very end (when overview is offscreen) to make invisible.
-            builder.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
-        }
+        toState.prepareForAtomicAnimation(mLauncher, fromState, builder);
     }
 
     public AnimatorSet createAtomicAnimation(LauncherState fromState, LauncherState toState,
@@ -573,6 +526,47 @@
         mConfig.setAnimation(anim, null);
     }
 
+    private void cancelAllStateElementAnimation() {
+        if (mStateElementAnimators == null) {
+            return;
+        }
+
+        for (Animator animator : mStateElementAnimators) {
+            if (animator != null) {
+                animator.cancel();
+            }
+        }
+    }
+
+    /**
+     * Cancels a currently running gesture animation
+     */
+    public void cancelStateElementAnimation(int index) {
+        if (mStateElementAnimators == null) {
+            return;
+        }
+        if (mStateElementAnimators[index] != null) {
+            mStateElementAnimators[index].cancel();
+        }
+    }
+
+    public Animator createStateElementAnimation(int index, float... values) {
+        cancelStateElementAnimation(index);
+        LauncherAppTransitionManager latm = mLauncher.getAppTransitionManager();
+        if (mStateElementAnimators == null) {
+            mStateElementAnimators = new Animator[latm.getStateElementAnimationsCount()];
+        }
+        Animator anim = latm.createStateElementAnimation(index, values);
+        mStateElementAnimators[index] = anim;
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mStateElementAnimators[index] = null;
+            }
+        });
+        return anim;
+    }
+
     private void clearCurrentAnimation() {
         if (mConfig.mCurrentAnimation != null) {
             mConfig.mCurrentAnimation.removeListener(mConfig);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 80e17c9..2eeb132 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.Utilities.shouldDisableGestures;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
@@ -844,10 +845,11 @@
          * If we return true, onTouchEvent will be called and we do the actual
          * scrolling there.
          */
-        acquireVelocityTrackerAndAddMovement(ev);
 
         // Skip touch handling if there are no pages to swipe
-        if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
+        if (getChildCount() <= 0 || shouldDisableGestures(ev)) return false;
+
+        acquireVelocityTrackerAndAddMovement(ev);
 
         /*
          * Shortcut the most recurring case: the user is in the dragging
@@ -1093,7 +1095,7 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         // Skip touch handling if there are no pages to swipe
-        if (getChildCount() <= 0) return false;
+        if (getChildCount() <= 0 || shouldDisableGestures(ev)) return false;
 
         acquireVelocityTrackerAndAddMovement(ev);
 
@@ -1204,6 +1206,7 @@
                     if (((initialScrollX >= mMaxScrollX) && (isVelocityXLeft || !isFling)) ||
                             ((initialScrollX <= mMinScrollX) && (!isVelocityXLeft || !isFling))) {
                         mScroller.springBack(getScrollX(), mMinScrollX, mMaxScrollX);
+                        mNextPage = getPageNearestToCenterOfScreen();
                     } else {
                         mScroller.setInterpolator(mDefaultInterpolator);
                         mScroller.fling(initialScrollX, -velocityX,
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 0cf6e44..55cb6f2 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -179,7 +179,7 @@
             DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
             if (target != null) {
                 deferred.mPackageName = target.getPackageName();
-                mLauncher.setOnResumeCallback(deferred);
+                mLauncher.addOnResumeCallback(deferred);
             } else {
                 deferred.sendFailure();
             }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7bdbb95..65aa3a7 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -128,6 +128,16 @@
     public static final int EDGE_NAV_BAR = 1 << 8;
 
     /**
+     * Set on a motion event do disallow any gestures and only handle touch.
+     * See {@link MotionEvent#setEdgeFlags(int)}.
+     */
+    public static final int FLAG_NO_GESTURES = 1 << 9;
+
+    public static boolean shouldDisableGestures(MotionEvent ev) {
+        return (ev.getEdgeFlags() & FLAG_NO_GESTURES) == FLAG_NO_GESTURES;
+    }
+
+    /**
      * Indicates if the device has a debug build. Should only be used to store additional info or
      * add extra logging and not for changing the app behavior.
      */
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 065d065..40c6b5f 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -19,6 +19,8 @@
 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
@@ -104,7 +106,10 @@
                 hotseat.setPivotY(workspacePivot[1]);
             }
             float hotseatScale = hotseatScaleAndTranslation.scale;
-            propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, scaleInterpolator);
+            Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
+                    scaleInterpolator);
+            propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
+                    hotseatScaleInterpolator);
 
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
             propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
@@ -125,10 +130,12 @@
         propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
                 scaleAndTranslation.translationY, translationInterpolator);
 
+        Interpolator hotseatTranslationInterpolator = builder.getInterpolator(
+                ANIM_HOTSEAT_TRANSLATE, translationInterpolator);
         propertySetter.setFloat(hotseat, View.TRANSLATION_Y,
-                hotseatScaleAndTranslation.translationY, translationInterpolator);
+                hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
         propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y,
-                hotseatScaleAndTranslation.translationY, translationInterpolator);
+                hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
 
         setScrim(propertySetter, state);
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index c62fc3d..4683893 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,6 +2,8 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
@@ -14,8 +16,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.util.FloatProperty;
 import android.util.Log;
-import android.util.Property;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -24,19 +26,15 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.ProgressInterface;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-
 /**
  * Handles AllApps view transition.
  * 1) Slides all apps view using direct manipulation
@@ -47,14 +45,13 @@
  * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
  * closer to top or closer to the page indicator.
  */
-public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener,
-        ProgressInterface {
+public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener {
 
-    public static final float SPRING_DAMPING_RATIO = 0.9f;
-    public static final float SPRING_STIFFNESS = 600f;
+    private static final float SPRING_DAMPING_RATIO = 0.9f;
+    private static final float SPRING_STIFFNESS = 600f;
 
-    public static final Property<AllAppsTransitionController, Float> ALL_APPS_PROGRESS =
-            new Property<AllAppsTransitionController, Float>(Float.class, "allAppsProgress") {
+    public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
+            new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
 
         @Override
         public Float get(AllAppsTransitionController controller) {
@@ -62,7 +59,7 @@
         }
 
         @Override
-        public void set(AllAppsTransitionController controller, Float progress) {
+        public void setValue(AllAppsTransitionController controller, float progress) {
             controller.setProgress(progress);
         }
     };
@@ -122,7 +119,6 @@
      * @see #setState(LauncherState)
      * @see #setStateWithAnimation(LauncherState, AnimatorSetBuilder, AnimationConfig)
      */
-    @Override
     public void setProgress(float progress) {
         mProgress = progress;
         mScrimView.setProgress(progress);
@@ -139,9 +135,17 @@
         } else {
             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
         }
+
+        if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+            // Translate hotseat with the shelf until reaching overview.
+            float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+            if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+                float hotseatShift = (progress - overviewProgress) * mShiftRange;
+                mLauncher.getHotseat().setTranslationY(hotseatShift);
+            }
+        }
     }
 
-    @Override
     public float getProgress() {
         return mProgress;
     }
@@ -184,8 +188,7 @@
         Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
                 ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
                 : FAST_OUT_SLOW_IN;
-        Animator anim = new SpringObjectAnimator<>(this, "allAppsSpringFromAATC", 1f / mShiftRange,
-                SPRING_DAMPING_RATIO, SPRING_STIFFNESS, mProgress, targetProgress);
+        Animator anim = createSpringAnimation(mProgress, targetProgress);
         anim.setDuration(config.duration);
         anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
         anim.addListener(getProgressAnimatorListener());
@@ -195,6 +198,11 @@
         setAlphas(toState, config, builder);
     }
 
+    public Animator createSpringAnimation(float... progressValues) {
+        return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
+                SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
+    }
+
     private void setAlphas(LauncherState toState, AnimationConfig config,
             AnimatorSetBuilder builder) {
         setAlphas(toState.getVisibleElements(mLauncher), config, builder);
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 5c498f8..52a896e 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -32,11 +32,13 @@
     public static final int ANIM_WORKSPACE_SCALE = 1;
     public static final int ANIM_WORKSPACE_TRANSLATE = 2;
     public static final int ANIM_WORKSPACE_FADE = 3;
-    public static final int ANIM_OVERVIEW_SCALE = 4;
-    public static final int ANIM_OVERVIEW_TRANSLATE_X = 5;
-    public static final int ANIM_OVERVIEW_TRANSLATE_Y = 6;
-    public static final int ANIM_OVERVIEW_FADE = 7;
-    public static final int ANIM_ALL_APPS_FADE = 8;
+    public static final int ANIM_HOTSEAT_SCALE = 4;
+    public static final int ANIM_HOTSEAT_TRANSLATE = 5;
+    public static final int ANIM_OVERVIEW_SCALE = 6;
+    public static final int ANIM_OVERVIEW_TRANSLATE_X = 7;
+    public static final int ANIM_OVERVIEW_TRANSLATE_Y = 8;
+    public static final int ANIM_OVERVIEW_FADE = 9;
+    public static final int ANIM_ALL_APPS_FADE = 10;
 
     public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
 
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index b1395af..395fed2 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 androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
+
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 
 import android.animation.Animator;
@@ -24,15 +26,12 @@
 import android.animation.ValueAnimator;
 import android.os.Handler;
 import android.os.Looper;
+import android.util.FloatProperty;
 import android.util.Log;
-import android.util.Property;
-
-import com.android.launcher3.ProgressInterface;
 
 import java.util.ArrayList;
 
 import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
@@ -40,12 +39,11 @@
  * 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.
  */
-public class SpringObjectAnimator<T extends ProgressInterface> extends ValueAnimator {
+public class SpringObjectAnimator<T> extends ValueAnimator {
 
     private static final String TAG = "SpringObjectAnimator";
     private static boolean DEBUG = false;
 
-    private T mObject;
     private ObjectAnimator mObjectAnimator;
     private float[] mValues;
 
@@ -57,29 +55,15 @@
     private boolean mAnimatorEnded = true;
     private boolean mEnded = true;
 
-    private static final FloatPropertyCompat<ProgressInterface> sFloatProperty =
-            new FloatPropertyCompat<ProgressInterface>("springObjectAnimator") {
-        @Override
-        public float getValue(ProgressInterface object) {
-            return object.getProgress();
-        }
-
-        @Override
-        public void setValue(ProgressInterface object, float progress) {
-            object.setProgress(progress);
-        }
-    };
-
-    public SpringObjectAnimator(T object, String name, float minimumVisibleChange, float damping,
-            float stiffness, float... values) {
-        mObject = object;
-        mSpring = new SpringAnimation(object, sFloatProperty);
+    public SpringObjectAnimator(T object, FloatProperty<T> property, float minimumVisibleChange,
+            float damping, float stiffness, float... values) {
+        mSpring = new SpringAnimation(object, createFloatPropertyCompat(property));
         mSpring.setMinimumVisibleChange(minimumVisibleChange);
         mSpring.setSpring(new SpringForce(0)
                 .setDampingRatio(damping)
                 .setStiffness(stiffness));
         mSpring.setStartVelocity(0.01f);
-        mProperty = new SpringProperty<T>(name, mSpring);
+        mProperty = new SpringProperty<>(property, mSpring);
         mObjectAnimator = ObjectAnimator.ofFloat(object, mProperty, values);
         mValues = values;
         mListeners = new ArrayList<>();
@@ -285,13 +269,15 @@
         mObjectAnimator.setCurrentPlayTime(playTime);
     }
 
-    public static class SpringProperty<T extends ProgressInterface> extends Property<T, Float> {
+    public static class SpringProperty<T> extends FloatProperty<T> {
 
         boolean useSpring = false;
+        final FloatProperty<T> mProperty;
         final SpringAnimation mSpring;
 
-        public SpringProperty(String name, SpringAnimation spring) {
-            super(Float.class, name);
+        public SpringProperty(FloatProperty<T> property, SpringAnimation spring) {
+            super(property.getName());
+            mProperty = property;
             mSpring = spring;
         }
 
@@ -301,15 +287,15 @@
 
         @Override
         public Float get(T object) {
-            return object.getProgress();
+            return mProperty.get(object);
         }
 
         @Override
-        public void set(T object, Float progress) {
+        public void setValue(T object, float progress) {
             if (useSpring) {
                 mSpring.animateToFinalPosition(progress);
             } else {
-                object.setProgress(progress);
+                mProperty.setValue(object, progress);
             }
         }
     }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 54d0db1..54efcb7 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -109,7 +109,7 @@
             "Show chip hints and gleams on the overview screen");
 
     public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
-            "FAKE_LANDSCAPE_UI", true,
+            "FAKE_LANDSCAPE_UI", false,
             "Rotate launcher UI instead of using transposed layout");
 
     public static void initialize(Context context) {
diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java
deleted file mode 100644
index 3fcda90..0000000
--- a/src/com/android/launcher3/touch/TouchEventTranslator.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2018 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.touch;
-
-import android.graphics.PointF;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import java.util.function.Consumer;
-
-/**
- * To minimize the size of the MotionEvent, historic events are not copied and passed via the
- * listener.
- */
-public class TouchEventTranslator {
-
-    private static final String TAG = "TouchEventTranslator";
-    private static final boolean DEBUG = false;
-
-    private class DownState {
-        long timeStamp;
-        float downX;
-        float downY;
-        public DownState(long timeStamp, float downX, float downY) {
-            this.timeStamp = timeStamp;
-            this.downX = downX;
-            this.downY = downY;
-        }
-    };
-    private final DownState ZERO = new DownState(0, 0f, 0f);
-
-    private final Consumer<MotionEvent> mListener;
-
-    private final SparseArray<DownState> mDownEvents;
-    private final SparseArray<PointF> mFingers;
-
-    private final SparseArray<Pair<PointerProperties[], PointerCoords[]>> mCache;
-
-    public TouchEventTranslator(Consumer<MotionEvent> listener) {
-        mDownEvents = new SparseArray<>();
-        mFingers = new SparseArray<>();
-        mCache = new SparseArray<>();
-
-        mListener = listener;
-    }
-
-    public void reset() {
-        mDownEvents.clear();
-        mFingers.clear();
-    }
-
-    public float getDownX() {
-        return mDownEvents.get(0).downX;
-    }
-
-    public float getDownY() {
-        return mDownEvents.get(0).downY;
-    }
-
-    public void setDownParameters(int idx, MotionEvent e) {
-        DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx));
-        mDownEvents.append(idx, ev);
-    }
-
-    public void dispatchDownEvents(MotionEvent ev) {
-        for(int i = 0; i < ev.getPointerCount() && i < mDownEvents.size(); i++) {
-            int pid = ev.getPointerId(i);
-            put(pid, i, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev);
-        }
-    }
-
-    public void processMotionEvent(MotionEvent ev) {
-        if (DEBUG) {
-            printSamples(TAG + " processMotionEvent", ev);
-        }
-        int index = ev.getActionIndex();
-        float x = ev.getX(index);
-        float y = ev.getY(index) - mDownEvents.get(index, ZERO).downY;
-        switch (ev.getActionMasked()) {
-            case MotionEvent.ACTION_POINTER_DOWN:
-                int pid = ev.getPointerId(index);
-                if(mFingers.get(pid, null) != null) {
-                    for(int i=0; i < ev.getPointerCount(); i++) {
-                        pid = ev.getPointerId(i);
-                        position(pid, x, y);
-                    }
-                    generateEvent(ev.getAction(), ev);
-                } else {
-                    put(pid, index, x, y, ev);
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                for(int i=0; i < ev.getPointerCount(); i++) {
-                    pid = ev.getPointerId(i);
-                    position(pid, x, y);
-                }
-                generateEvent(ev.getAction(), ev);
-                break;
-            case MotionEvent.ACTION_POINTER_UP:
-            case MotionEvent.ACTION_UP:
-                pid = ev.getPointerId(index);
-                lift(pid, index, x, y, ev);
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                cancel(ev);
-                break;
-            default:
-                Log.v(TAG, "Didn't process ");
-                printSamples(TAG, ev);
-
-        }
-    }
-
-    private TouchEventTranslator put(int id, int index, float x, float y, MotionEvent ev) {
-        return put(id, index, x, y, ev.getEventTime(), ev);
-    }
-
-    private TouchEventTranslator put(int id, int index, float x, float y, long ms, MotionEvent ev) {
-        checkFingerExistence(id, false);
-        boolean isInitialDown = (mFingers.size() == 0);
-
-        mFingers.put(id, new PointF(x, y));
-        int n = mFingers.size();
-
-        if (mCache.get(n) == null) {
-            PointerProperties[] properties = new PointerProperties[n];
-            PointerCoords[] coords = new PointerCoords[n];
-            for (int i = 0; i < n; i++) {
-                properties[i] = new PointerProperties();
-                coords[i] = new PointerCoords();
-            }
-            mCache.put(n, new Pair(properties, coords));
-        }
-
-        int action;
-        if (isInitialDown) {
-            action = MotionEvent.ACTION_DOWN;
-        } else {
-            action = MotionEvent.ACTION_POINTER_DOWN;
-            // Set the id of the changed pointer.
-            action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-        }
-        generateEvent(action, ms, ev);
-        return this;
-    }
-
-    public TouchEventTranslator position(int id, float x, float y) {
-        checkFingerExistence(id, true);
-        mFingers.get(id).set(x, y);
-        return this;
-    }
-
-    private TouchEventTranslator lift(int id, int index, MotionEvent ev) {
-        checkFingerExistence(id, true);
-        boolean isFinalUp = (mFingers.size() == 1);
-        int action;
-        if (isFinalUp) {
-            action = MotionEvent.ACTION_UP;
-        } else {
-            action = MotionEvent.ACTION_POINTER_UP;
-            // Set the id of the changed pointer.
-            action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-        }
-        generateEvent(action, ev);
-        mFingers.remove(id);
-        return this;
-    }
-
-    private TouchEventTranslator lift(int id, int index, float x, float y, MotionEvent ev) {
-        checkFingerExistence(id, true);
-        mFingers.get(id).set(x, y);
-        return lift(id, index, ev);
-    }
-
-    public TouchEventTranslator cancel(MotionEvent ev) {
-        generateEvent(MotionEvent.ACTION_CANCEL, ev);
-        mFingers.clear();
-        return this;
-    }
-
-    private void checkFingerExistence(int id, boolean shouldExist) {
-        if (shouldExist != (mFingers.get(id, null) != null)) {
-            throw new IllegalArgumentException(
-                    shouldExist ? "Finger does not exist" : "Finger already exists");
-        }
-    }
-
-
-    /**
-     * Used to debug MotionEvents being sent/received.
-     */
-    public void printSamples(String msg, MotionEvent ev) {
-        System.out.printf("%s %s", msg, MotionEvent.actionToString(ev.getActionMasked()));
-        final int pointerCount = ev.getPointerCount();
-        System.out.printf("#%d/%d", ev.getActionIndex(), pointerCount);
-        System.out.printf(" t=%d:", ev.getEventTime());
-        for (int p = 0; p < pointerCount; p++) {
-            System.out.printf("  id=%d: (%f,%f)",
-                    ev.getPointerId(p), ev.getX(p), ev.getY(p));
-        }
-        System.out.println();
-    }
-
-    private void generateEvent(int action, MotionEvent ev) {
-        generateEvent(action, ev.getEventTime(), ev);
-    }
-
-    private void generateEvent(int action, long ms, MotionEvent ev) {
-        Pair<PointerProperties[], PointerCoords[]> state = getFingerState();
-        MotionEvent event = MotionEvent.obtain(
-                mDownEvents.get(0).timeStamp,
-                ms,
-                action,
-                state.first.length,
-                state.first,
-                state.second,
-                ev.getMetaState(),
-                ev.getButtonState() /* buttonState */,
-                ev.getXPrecision() /* xPrecision */,
-                ev.getYPrecision() /* yPrecision */,
-                ev.getDeviceId(),
-                ev.getEdgeFlags(),
-                ev.getSource(),
-                ev.getFlags() /* flags */);
-        if (DEBUG) {
-            printSamples(TAG + " generateEvent", event);
-        }
-        if (event.getPointerId(event.getActionIndex()) < 0) {
-            printSamples(TAG + "generateEvent", event);
-            throw new IllegalStateException(event.getActionIndex() + " not found in MotionEvent");
-        }
-        mListener.accept(event);
-        event.recycle();
-    }
-
-    /**
-     * Returns the description of the fingers' state expected by MotionEvent.
-     */
-    private Pair<PointerProperties[], PointerCoords[]> getFingerState() {
-        int nFingers = mFingers.size();
-
-        Pair<PointerProperties[], PointerCoords[]> result = mCache.get(nFingers);
-        PointerProperties[] properties = result.first;
-        PointerCoords[] coordinates = result.second;
-
-        int index = 0;
-        for (int i = 0; i < mFingers.size(); i++) {
-            int id = mFingers.keyAt(i);
-            PointF location = mFingers.get(id);
-
-            PointerProperties property = properties[i];
-            property.id = id;
-            property.toolType = MotionEvent.TOOL_TYPE_FINGER;
-            properties[index] = property;
-
-            PointerCoords coordinate = coordinates[i];
-            coordinate.x = location.x;
-            coordinate.y = location.y;
-            coordinate.pressure = 1.0f;
-            coordinates[index] = coordinate;
-
-            index++;
-        }
-        return mCache.get(nFingers);
-    }
-}
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index ac152db..c1ba780 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -21,6 +21,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.Utilities.shouldDisableGestures;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -116,11 +117,25 @@
         mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
     }
 
+    /**
+     * Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer.
+     */
     public boolean isEventOverView(View view, MotionEvent ev) {
         getDescendantRectRelativeToSelf(view, mHitRect);
         return mHitRect.contains((int) ev.getX(), (int) ev.getY());
     }
 
+    /**
+     * Given a motion event in evView's coordinates, return whether the event is within another
+     * view's bounds.
+     */
+    public boolean isEventOverView(View view, MotionEvent ev, View evView) {
+        int[] xy = new int[] {(int) ev.getX(), (int) ev.getY()};
+        getDescendantCoordRelativeToSelf(evView, xy);
+        getDescendantRectRelativeToSelf(view, mHitRect);
+        return mHitRect.contains(xy[0], xy[1]);
+    }
+
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getAction();
@@ -137,6 +152,8 @@
     }
 
     private TouchController findControllerToHandleTouch(MotionEvent ev) {
+        if (shouldDisableGestures(ev)) return null;
+
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
         if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
             return topView;
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index e41916c..5cc64dc 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -69,8 +69,6 @@
         return false;
     }
 
-    public static void prepareToShowOverview(Launcher launcher) { }
-
     public static void setBackButtonAlpha(Launcher launcher, float alpha, boolean animate) { }
 
 
@@ -95,4 +93,6 @@
 
     public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
 
+    public static void clearSwipeSharedState(boolean finishAnimation) {}
+
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index abc93cd..4a0ca5c 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -35,7 +35,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.Surface;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
@@ -67,7 +66,6 @@
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
-import org.junit.runners.model.Statement;
 
 import java.io.IOException;
 import java.lang.annotation.ElementType;
@@ -124,46 +122,10 @@
     protected @interface PortraitLandscape {
     }
 
-    @Rule
-    public TestRule mPortraitLandscapeExecutor =
-            (base, description) -> false && description.getAnnotation(PortraitLandscape.class)
-                    != null ? new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    try {
-                        // Create launcher activity if necessary and bring it to the front.
-                        mLauncher.pressHome();
-                        waitForLauncherCondition("Launcher activity wasn't created",
-                                launcher -> launcher != null);
-
-                        executeOnLauncher(launcher ->
-                                launcher.getRotationHelper().forceAllowRotationForTesting(true));
-
-                        evaluateInPortrait();
-                        evaluateInLandscape();
-                    } finally {
-                        mDevice.setOrientationNatural();
-                        executeOnLauncher(launcher ->
-                                launcher.getRotationHelper().forceAllowRotationForTesting(false));
-                        mLauncher.setExpectedRotation(Surface.ROTATION_0);
-                    }
-                }
-
-                private void evaluateInPortrait() throws Throwable {
-                    mDevice.setOrientationNatural();
-                    mLauncher.setExpectedRotation(Surface.ROTATION_0);
-                    base.evaluate();
-                }
-
-                private void evaluateInLandscape() throws Throwable {
-                    mDevice.setOrientationLeft();
-                    mLauncher.setExpectedRotation(Surface.ROTATION_90);
-                    base.evaluate();
-                }
-            } : base;
-
     protected TestRule getRulesInsideActivityMonitor() {
-        return new FailureWatcher(this);
+        return RuleChain.
+                outerRule(new PortraitLandscapeRunner(this)).
+                around(new FailureWatcher(mDevice));
     }
 
     @Rule
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index 48335a6..58c74ce 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -25,26 +25,23 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.OutputStreamWriter;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiSelector;
-
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class DefaultLayoutProviderTest extends AbstractLauncherUiTest {
@@ -71,7 +68,6 @@
     }
 
     @Test
-    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
         writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
 
@@ -79,14 +75,10 @@
         mActivityMonitor.startLauncher();
         waitForModelLoaded();
 
-        // Verify widget present
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .description(getSettingsApp().getLabel().toString());
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString());
     }
 
     @Test
-    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_widget() throws Exception {
         // A non-restored widget with no config screen gets restored automatically.
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
@@ -100,13 +92,11 @@
         waitForModelLoaded();
 
         // Verify widget present
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .className(LauncherAppWidgetHostView.class).description(info.label);
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        assertTrue("Widget is not present",
+                mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
     }
 
     @Test
-    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_folder() throws Exception {
         writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
                 .addApp(SETTINGS_APP, SETTINGS_APP)
@@ -118,10 +108,7 @@
         mActivityMonitor.startLauncher();
         waitForModelLoaded();
 
-        // Verify widget present
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .descriptionContains(mTargetContext.getString(android.R.string.copy));
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        mLauncher.getWorkspace().getHotseatFolder("Folder: Copy");
     }
 
     @After
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
new file mode 100644
index 0000000..0f36292
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -0,0 +1,63 @@
+package com.android.launcher3.ui;
+
+import android.view.Surface;
+
+import com.android.launcher3.tapl.TestHelpers;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+class PortraitLandscapeRunner implements TestRule {
+    private AbstractLauncherUiTest mTest;
+
+    public PortraitLandscapeRunner(AbstractLauncherUiTest test) {
+        mTest = test;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        if (!TestHelpers.isInLauncherProcess() ||
+                description.getAnnotation(AbstractLauncherUiTest.PortraitLandscape.class) == null) {
+            return base;
+        }
+
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    mTest.mDevice.pressHome();
+                    mTest.waitForLauncherCondition("Launcher activity wasn't created",
+                            launcher -> launcher != null);
+
+                    mTest.executeOnLauncher(launcher ->
+                            launcher.getRotationHelper().forceAllowRotationForTesting(
+                                    true));
+
+                    evaluateInPortrait();
+                    evaluateInLandscape();
+                } finally {
+                    mTest.mDevice.setOrientationNatural();
+                    mTest.executeOnLauncher(launcher ->
+                            launcher.getRotationHelper().forceAllowRotationForTesting(
+                                    false));
+                    mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
+                }
+            }
+
+            private void evaluateInPortrait() throws Throwable {
+                mTest.mDevice.setOrientationNatural();
+                mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
+                base.evaluate();
+                mTest.getDevice().pressHome();
+            }
+
+            private void evaluateInLandscape() throws Throwable {
+                mTest.mDevice.setOrientationLeft();
+                mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
+                base.evaluate();
+                mTest.getDevice().pressHome();
+            }
+        };
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index d171004..c3168f8 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -59,11 +59,7 @@
 
     public static void initialize(AbstractLauncherUiTest test) throws Exception {
         test.clearLauncherData();
-        if (TestHelpers.isInLauncherProcess()) {
-            test.mActivityMonitor.returnToHome();
-        } else {
-            test.mDevice.pressHome();
-        }
+        test.mDevice.pressHome();
         test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
         test.waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
         test.waitForResumed("Launcher internal state is still Background");
@@ -279,8 +275,6 @@
     @Test
     @PortraitLandscape
     public void testLaunchMenuItem() throws Exception {
-        if (!TestHelpers.isInLauncherProcess()) return;
-
         final AllApps allApps = mLauncher.
                 getWorkspace().
                 switchToAllApps();
@@ -327,8 +321,6 @@
     @Test
     @PortraitLandscape
     public void testDragShortcut() throws Throwable {
-        if (!TestHelpers.isInLauncherProcess()) return;
-
         // 1. Open all apps and wait for load complete.
         // 2. Find the app and long press it to show shortcuts.
         // 3. Press icon center until shortcuts appear
diff --git a/tests/src/com/android/launcher3/ui/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
index a73bde0..d13d319 100644
--- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java
+++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
@@ -54,21 +54,6 @@
         return UiDevice.getInstance(getInstrumentation());
     }
 
-    /**
-     * Opens all apps and returns the recycler view
-     */
-    public static UiObject2 openAllApps() {
-        final UiDevice device = getDevice();
-        device.waitForIdle();
-        UiObject2 hotseat = device.wait(
-                Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
-        Point start = hotseat.getVisibleCenter();
-        int endY = (int) (device.getDisplayHeight() * 0.1f);
-        // 100 px/step
-        device.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
-        return findViewById(R.id.apps_list_view);
-    }
-
     public static UiObject2 findViewById(int id) {
         return getDevice().wait(Until.findObject(getSelectorForId(id)),
                 AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 874ff19..d36126b 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,8 +15,9 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -24,43 +25,37 @@
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.os.Bundle;
 
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Set;
 
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.UiSelector;
-import java.util.concurrent.Callable;
-
 /**
  * Tests for bind widget flow.
  *
@@ -131,16 +126,14 @@
 
         setupContents(item);
 
-        // Since there is no widget to verify, just wait until the workspace is ready.
-        // TODO: fix LauncherInstrumentation#LAUNCHER_PKG
-        mLauncher.getWorkspace();
+        final Workspace workspace = mLauncher.getWorkspace();
         // Item deleted from db
         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
                 null, null, null, null, null);
         assertEquals(0, mCursor.getCount());
 
         // The view does not exist
-        assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists());
+        assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null);
     }
 
     @Test
@@ -189,12 +182,8 @@
 
         setupContents(item);
 
-        // Since there is no widget to verify, just wait until the workspace is ready.
-        // TODO: fix LauncherInstrumentation#LAUNCHER_PKG
-        mLauncher.getWorkspace();
-        // The view does not exist
-        assertFalse(mDevice.findObject(
-                new UiSelector().className(PendingAppWidgetHostView.class)).exists());
+        assertTrue("Pending widget exists",
+                mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
         // Item deleted from db
         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
                 null, null, null, null, null);
@@ -258,12 +247,12 @@
      * widget class is displayed on the homescreen.
      */
     private void setupContents(LauncherAppWidgetInfo item) {
-        int screenId = Workspace.FIRST_SCREEN_ID;
+        int screenId = FIRST_SCREEN_ID;
         // Update the screen id counter for the provider.
         LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
 
-        if (screenId > Workspace.FIRST_SCREEN_ID) {
-            screenId = Workspace.FIRST_SCREEN_ID;
+        if (screenId > FIRST_SCREEN_ID) {
+            screenId = FIRST_SCREEN_ID;
         }
 
         // Insert the item
@@ -283,15 +272,13 @@
     }
 
     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .className(LauncherAppWidgetHostView.class).description(info.label);
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        assertTrue("Widget is not present",
+                mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
     }
 
     private void verifyPendingWidgetPresent() {
-        UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
-                .className(PendingAppWidgetHostView.class);
-        assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
+        assertTrue("Pending widget is not present",
+                mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT) != null);
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index a57d7ba..6122dae 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 
@@ -26,9 +28,6 @@
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
@@ -37,15 +36,14 @@
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.tapl.AddToHomeScreenPrompt;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 import com.android.launcher3.testcomponent.RequestPinItemActivity;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.WidgetCell;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -80,15 +78,10 @@
 
     @Test
     public void testPinWidgetNoConfig() throws Throwable {
-        runTest("pinWidgetNoConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
-                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
-                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetNoConfig.class.getName());
-            }
-        });
+        runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
+                ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                        .equals(AppWidgetNoConfig.class.getName()));
     }
 
         @Test
@@ -98,28 +91,19 @@
                 RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
                 RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
 
-        runTest("pinWidgetNoConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
-                        ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
-                        ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetNoConfig.class.getName());
-            }
-        }, command);
+        runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
+                ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
+                ((LauncherAppWidgetInfo) info).providerName.getClassName()
+                        .equals(AppWidgetNoConfig.class.getName()), command);
     }
 
     @Test
     public void testPinWidgetWithConfig() throws Throwable {
-        runTest("pinWidgetWithConfig", true, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
+        runTest("pinWidgetWithConfig", true,
+                (info, view) -> info instanceof LauncherAppWidgetInfo &&
                         ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
                         ((LauncherAppWidgetInfo) info).providerName.getClassName()
-                                .equals(AppWidgetWithConfig.class.getName());
-            }
-        });
+                                .equals(AppWidgetWithConfig.class.getName()));
     }
 
     @Test
@@ -149,14 +133,14 @@
         clearHomescreen();
         mActivityMonitor.startLauncher();
 
-        // Open all apps and wait for load complete
-        final UiObject2 appsContainer = TestViewHelpers.openAllApps();
-        Wait.atMost(null, Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT);
-
         // Open Pin item activity
         BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
                 RequestPinItemActivity.class.getName());
-        scrollAndFind(appsContainer, By.text("Test Pin Item")).click();
+        mLauncher.
+                getWorkspace().
+                switchToAllApps().
+                getAppIcon("Test Pin Item").
+                launch(getAppPackageName());
         assertNotNull(openMonitor.blockingGetExtraIntent());
 
         // Set callback
@@ -173,15 +157,11 @@
         // call the requested method to start the flow
         mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
                 RequestPinItemActivity.class, activityMethod));
-        UiObject2 widgetCell = mDevice.wait(
-                Until.findObject(By.clazz(WidgetCell.class)), DEFAULT_ACTIVITY_TIMEOUT);
-        assertNotNull(widgetCell);
+        final AddToHomeScreenPrompt addToHomeScreenPrompt = mLauncher.getAddToHomeScreenPrompt();
 
         // Accept confirmation:
         BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
-        mDevice.wait(Until.findObject(
-                By.text(mLauncher.isAvd() ? "ADD AUTOMATICALLY" : "Add automatically")),
-                DEFAULT_UI_TIMEOUT).click();
+        addToHomeScreenPrompt.addAutomatically();
         Intent result = resultReceiver.blockingGetIntent();
         assertNotNull(result);
         mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
@@ -190,7 +170,7 @@
         }
 
         // Go back to home
-        mActivityMonitor.returnToHome();
+        mLauncher.pressHome();
         Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT);
     }
 
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 09cc98d..eef2f24 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -4,7 +4,7 @@
 
 import android.util.Log;
 
-import com.android.launcher3.ui.AbstractLauncherUiTest;
+import androidx.test.uiautomator.UiDevice;
 
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
@@ -16,16 +16,16 @@
 public class FailureWatcher extends TestWatcher {
     private static final String TAG = "FailureWatcher";
     private static int sScreenshotCount = 0;
-    private AbstractLauncherUiTest mAbstractLauncherUiTest;
+    final private UiDevice mDevice;
 
-    public FailureWatcher(AbstractLauncherUiTest abstractLauncherUiTest) {
-        mAbstractLauncherUiTest = abstractLauncherUiTest;
+    public FailureWatcher(UiDevice device) {
+        mDevice = device;
     }
 
     private void dumpViewHierarchy() {
         final ByteArrayOutputStream stream = new ByteArrayOutputStream();
         try {
-            mAbstractLauncherUiTest.getDevice().dumpWindowHierarchy(stream);
+            mDevice.dumpWindowHierarchy(stream);
             stream.flush();
             stream.close();
             for (String line : stream.toString().split("\\r?\\n")) {
@@ -38,7 +38,7 @@
 
     @Override
     protected void failed(Throwable e, Description description) {
-        if (mAbstractLauncherUiTest.getDevice() == null) return;
+        if (mDevice == null) return;
         final String pathname = getInstrumentation().getTargetContext().
                 getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
         Log.e(TAG, "Failed test " + description.getMethodName() +
@@ -48,12 +48,12 @@
         dumpViewHierarchy();
 
         try {
-            final String dumpsysResult = mAbstractLauncherUiTest.getDevice().executeShellCommand(
+            final String dumpsysResult = mDevice.executeShellCommand(
                     "dumpsys activity service TouchInteractionService");
             Log.d(TAG, "TouchInteractionService: " + dumpsysResult);
         } catch (IOException ex) {
         }
 
-        mAbstractLauncherUiTest.getDevice().takeScreenshot(new File(pathname));
+        mDevice.takeScreenshot(new File(pathname));
     }
 }
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
index 145c3c8..2aba7a5 100644
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
@@ -72,11 +72,6 @@
         getInstrumentation().startActivitySync(getHomeIntentInPackage(getTargetContext()));
     }
 
-    public void returnToHome() {
-        getTargetContext().startActivity(getHomeIntentInPackage(getTargetContext()));
-        getInstrumentation().waitForIdleSync();
-    }
-
     private class MyStatement extends Statement implements ActivityLifecycleCallbacks {
 
         private final Statement mBase;
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
new file mode 100644
index 0000000..7f561a2
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -0,0 +1,41 @@
+/*
+ * 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.tapl;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiObject2;
+
+public class AddToHomeScreenPrompt {
+    private final LauncherInstrumentation mLauncher;
+    private final UiObject2 mWidgetCell;
+
+    AddToHomeScreenPrompt(LauncherInstrumentation launcher) {
+        mLauncher = launcher;
+        mWidgetCell = launcher.waitForLauncherObject(By.clazz(
+                "com.android.launcher3.widget.WidgetCell"));
+        mLauncher.assertNotNull("Can't find widget cell object", mWidgetCell);
+    }
+
+    public void addAutomatically() {
+        mLauncher.waitForObjectInContainer(
+                mWidgetCell.getParent().getParent().getParent().getParent(),
+                By.text(LauncherInstrumentation.isAvd()
+                        ? "ADD AUTOMATICALLY"
+                        : "Add automatically")).
+                click();
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 6c46192..c9eaf27 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -87,16 +87,24 @@
             }
 
             case TWO_BUTTON: {
-                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
-                final int startY = getSwipeStartY();
-                final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
-                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+                final int startX;
+                final int startY;
+                final int endX;
+                final int endY;
+                final int swipeLength = mLauncher.getTestInfo(getSwipeHeightRequestName()).
+                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD) + mLauncher.getTouchSlop();
 
-                mLauncher.swipeToState(
-                        centerX, startY, centerX,
-                        startY - swipeHeight - mLauncher.getTouchSlop(),
-                        10,
-                        expectedState);
+                if (mLauncher.getDevice().isNaturalOrientation()) {
+                    startX = endX = mLauncher.getDevice().getDisplayWidth() / 2;
+                    startY = getSwipeStartY();
+                    endY = startY - swipeLength;
+                } else {
+                    startX = getSwipeStartX();
+                    endX = startX - swipeLength;
+                    startY = endY = mLauncher.getDevice().getDisplayHeight() / 2;
+                }
+
+                mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState);
                 break;
             }
 
@@ -111,6 +119,10 @@
         return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT;
     }
 
+    protected int getSwipeStartX() {
+        return mLauncher.getRealDisplaySize().x - 1;
+    }
+
     protected int getSwipeStartY() {
         return mLauncher.getRealDisplaySize().y - 1;
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Folder.java b/tests/tapl/com/android/launcher3/tapl/Folder.java
new file mode 100644
index 0000000..6e6734d
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Folder.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tapl;
+
+import android.widget.FrameLayout;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * App folder in workspace/
+ */
+public final class Folder {
+    Folder(LauncherInstrumentation launcher, UiObject2 icon) {
+    }
+
+    static BySelector getSelector(String folderName, LauncherInstrumentation launcher) {
+        return By.clazz(FrameLayout.class).desc(folderName).pkg(launcher.getLauncherPackageName());
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 2db9d08..a7e6336 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -122,7 +122,7 @@
     private static final String APPS_RES_ID = "apps_view";
     private static final String OVERVIEW_RES_ID = "overview_panel";
     private static final String WIDGETS_RES_ID = "widgets_list_view";
-    public static final int WAIT_TIME_MS = 10000;
+    public static final int WAIT_TIME_MS = 60000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
     private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
@@ -315,25 +315,37 @@
         mExpectedRotation = expectedRotation;
     }
 
-    private UiObject2 verifyContainerType(ContainerType containerType) {
-        assertEquals("Unexpected display rotation",
-                mExpectedRotation, mDevice.getDisplayRotation());
+    public String getNavigationModeMismatchError() {
         final NavigationModel navigationModel = getNavigationModel();
         final boolean hasRecentsButton = hasSystemUiObject("recent_apps");
         final boolean hasHomeButton = hasSystemUiObject("home");
-        assertTrue("Presence of recents button doesn't match the interaction mode, mode="
-                        + navigationModel.name() + ", hasRecents=" + hasRecentsButton,
-                (navigationModel == NavigationModel.THREE_BUTTON) == hasRecentsButton);
-        assertTrue("Presence of home button doesn't match the interaction mode, mode="
-                        + navigationModel.name() + ", hasHome=" + hasHomeButton,
-                (navigationModel != NavigationModel.ZERO_BUTTON) == hasHomeButton);
+        if ((navigationModel == NavigationModel.THREE_BUTTON) != hasRecentsButton) {
+            return "Presence of recents button doesn't match the interaction mode, mode="
+                    + navigationModel.name() + ", hasRecents=" + hasRecentsButton;
+        }
+        if ((navigationModel != NavigationModel.ZERO_BUTTON) != hasHomeButton) {
+            return "Presence of home button doesn't match the interaction mode, mode="
+                    + navigationModel.name() + ", hasHome=" + hasHomeButton;
+        }
+        return null;
+    }
+
+    private UiObject2 verifyContainerType(ContainerType containerType) {
+        assertEquals("Unexpected display rotation",
+                mExpectedRotation, mDevice.getDisplayRotation());
+        final String error = getNavigationModeMismatchError();
+        assertTrue(error, error == null);
         log("verifyContainerType: " + containerType);
 
         try (Closable c = addContextLayer(
                 "but the current state is not " + containerType.name())) {
             switch (containerType) {
                 case WORKSPACE: {
-                    waitForLauncherObject(APPS_RES_ID);
+                    if (mDevice.isNaturalOrientation()) {
+                        waitForLauncherObject(APPS_RES_ID);
+                    } else {
+                        waitUntilGone(APPS_RES_ID);
+                    }
                     waitUntilGone(OVERVIEW_RES_ID);
                     waitUntilGone(WIDGETS_RES_ID);
                     return waitForLauncherObject(WORKSPACE_RES_ID);
@@ -490,6 +502,13 @@
         }
     }
 
+    @NonNull
+    public AddToHomeScreenPrompt getAddToHomeScreenPrompt() {
+        try (LauncherInstrumentation.Closable c = addContextLayer("want to get widget cell")) {
+            return new AddToHomeScreenPrompt(this);
+        }
+    }
+
     /**
      * Gets the Overview object if the current state is showing the overview panel. Fails if the
      * launcher is not in that state.
@@ -504,17 +523,6 @@
     }
 
     /**
-     * Gets the Base overview object if either Launcher is in overview state or the fallback
-     * overview activity is showing. Fails otherwise.
-     *
-     * @return BaseOverview object.
-     */
-    @NonNull
-    public BaseOverview getBaseOverview() {
-        return new BaseOverview(this);
-    }
-
-    /**
      * Gets the All Apps object if the current state is showing the all apps panel opened by swiping
      * from workspace. Fails if the launcher is not in that state. Please don't call this method if
      * App Apps was opened by swiping up from Overview, as it won't fail and will return an
@@ -605,6 +613,16 @@
     }
 
     @NonNull
+    UiObject2 waitForLauncherObject(BySelector selector) {
+        return waitForObjectBySelector(selector.pkg(getLauncherPackageName()));
+    }
+
+    @NonNull
+    UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
+        return tryWaitForObjectBySelector(selector.pkg(getLauncherPackageName()), timeout);
+    }
+
+    @NonNull
     UiObject2 waitForFallbackLauncherObject(String resName) {
         return waitForObjectBySelector(getFallbackLauncherObjectSelector(resName));
     }
@@ -615,6 +633,10 @@
         return object;
     }
 
+    private UiObject2 tryWaitForObjectBySelector(BySelector selector, long timeout) {
+        return mDevice.wait(Until.findObject(selector), timeout);
+    }
+
     BySelector getLauncherObjectSelector(String resName) {
         return By.res(getLauncherPackageName(), resName);
     }
@@ -688,7 +710,7 @@
 
     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
     // fixed interval each time.
-    private void linearGesture(int startX, int startY, int endX, int endY, int steps) {
+    void linearGesture(int startX, int startY, int endX, int endY, int steps) {
         final long downTime = SystemClock.uptimeMillis();
         final Point start = new Point(startX, startY);
         final Point end = new Point(endX, endY);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 8b12464..641c413 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -16,7 +16,8 @@
 
 package com.android.launcher3.tapl;
 
-import androidx.test.uiautomator.Direction;
+import android.graphics.Rect;
+
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
@@ -26,7 +27,6 @@
  * A recent task in the overview panel carousel.
  */
 public final class OverviewTask {
-    static final int FLING_SPEED = 3000;
     private static final long WAIT_TIME_MS = 60000;
     private final LauncherInstrumentation mLauncher;
     private final UiObject2 mTask;
@@ -51,7 +51,10 @@
                 "want to dismiss a task")) {
             verifyActiveContainer();
             // Dismiss the task via flinging it up.
-            mTask.fling(Direction.DOWN, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            final Rect taskBounds = mTask.getVisibleBounds();
+            final int centerX = taskBounds.centerX();
+            final int centerY = taskBounds.centerY();
+            mLauncher.linearGesture(centerX, centerY, centerX, 0, 10);
             mLauncher.waitForIdle();
         }
     }
diff --git a/src/com/android/launcher3/ProgressInterface.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
similarity index 66%
rename from src/com/android/launcher3/ProgressInterface.java
rename to tests/tapl/com/android/launcher3/tapl/Widget.java
index 663d8ba..128789d 100644
--- a/src/com/android/launcher3/ProgressInterface.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -14,13 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.tapl;
 
-/**
- * Progress is defined as a value with range [0, 1], and is specific to each implementor.
- * It is used when there is a transition from one state of the UI to another.
- */
-public interface ProgressInterface {
-    void setProgress(float progress);
-    float getProgress();
-}
\ No newline at end of file
+import androidx.test.uiautomator.UiObject2;
+
+public class Widget {
+    Widget(LauncherInstrumentation launcher, UiObject2 widget) {
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 33754c1..b01b6f3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -27,6 +27,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
@@ -142,11 +143,17 @@
     }
 
     @NonNull
-    private AppIcon getHotseatAppIcon(String appName) {
+    public AppIcon getHotseatAppIcon(String appName) {
         return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
                 mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
     }
 
+    @NonNull
+    public Folder getHotseatFolder(String appName) {
+        return new Folder(mLauncher, mLauncher.getObjectInContainer(
+                mHotseat, Folder.getSelector(appName, mLauncher)));
+    }
+
     static void dragIconToWorkspace(
             LauncherInstrumentation launcher, Launchable launchable, Point dest,
             String longPressIndicator) {
@@ -213,6 +220,21 @@
 
     @Override
     protected int getSwipeStartY() {
-        return mLauncher.waitForLauncherObject("hotseat").getVisibleBounds().top;
+        return mLauncher.getRealDisplaySize().y - 1;
+    }
+
+    @Nullable
+    public Widget tryGetWidget(String label, long timeout) {
+        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                By.clazz("com.android.launcher3.widget.LauncherAppWidgetHostView").desc(label),
+                timeout);
+        return widget != null ? new Widget(mLauncher, widget) : null;
+    }
+
+    @Nullable
+    public Widget tryGetPendingWidget(long timeout) {
+        final UiObject2 widget = mLauncher.tryWaitForLauncherObject(
+                By.clazz("com.android.launcher3.widget.PendingAppWidgetHostView"), timeout);
+        return widget != null ? new Widget(mLauncher, widget) : null;
     }
 }
\ No newline at end of file