Merge "Import translations. DO NOT MERGE" into ub-launcher3-rvc-dev
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index f1b71e8..f1db144 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -48,12 +48,16 @@
     HotseatContainer hotseat = 2;
     FolderContainer folder = 3;
     AllAppsContainer all_apps_container = 4;
+    WidgetsContainer widgets_container = 5;
   }
 }
 
 message AllAppsContainer {
 }
 
+message WidgetsContainer {
+}
+
 enum Origin {
   UNKNOWN = 0;
   DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
diff --git a/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml b/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
new file mode 100644
index 0000000..df7cd8e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <solid android:color="?android:colorAccent"/>
+    <size android:height="@dimen/swipe_edu_circle_size"
+        android:width="@dimen/swipe_edu_circle_size" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml b/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
new file mode 100644
index 0000000..e7ef6e6
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.quickstep.views.AllAppsEduView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="@dimen/swipe_edu_width"
+    android:layout_height="@dimen/swipe_edu_max_height"/>
diff --git a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
index ffe906c..7b3e378 100644
--- a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
+++ b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
@@ -18,6 +18,7 @@
     android:id="@+id/drag_layer"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:clipChildren="false"
     android:fitsSystemWindows="true">
 
     <com.android.quickstep.fallback.FallbackRecentsView
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 fa0d3f3..8ff05f2 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
@@ -45,8 +45,10 @@
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
             return super.getVerticalProgress(launcher);
         }
+        RecentsView recentsView = launcher.getOverviewPanel();
         int transitionLength = LayoutUtils.getShelfTrackingDistance(launcher,
-                launcher.getDeviceProfile());
+                launcher.getDeviceProfile(),
+                recentsView.getPagedOrientationHandler());
         AllAppsTransitionController controller = launcher.getAllAppsController();
         float scrollRange = Math.max(controller.getShiftRange(), 1);
         float progressDelta = (transitionLength / scrollRange);
@@ -73,9 +75,11 @@
     public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
         if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
             // Translate hotseat offscreen if we show it in overview.
+            RecentsView recentsView = launcher.getOverviewPanel();
             ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
             scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
-                    launcher.getDeviceProfile());
+                    launcher.getDeviceProfile(),
+                    recentsView.getPagedOrientationHandler());
             return scaleAndTranslation;
         }
         return super.getHotseatScaleAndTranslation(launcher);
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 9b4e890..d174bfd 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
@@ -19,8 +19,7 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
-import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
-import static com.android.quickstep.SysUINavigationMode.getMode;
+import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
@@ -130,10 +129,8 @@
     @Override
     public int getVisibleElements(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        boolean hideShelfTwoButtonLandscape = getMode(launcher) == TWO_BUTTONS &&
-                !recentsView.getPagedOrientationHandler().isLayoutNaturalToLauncher();
         if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher) ||
-                hideShelfTwoButtonLandscape) {
+                hideShelfInTwoButtonLandscape(launcher, recentsView.getPagedOrientationHandler())) {
             return OVERVIEW_BUTTONS;
         } else if (launcher.getDeviceProfile().isVerticalBarLayout()) {
             return VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 11593a1..94c7771 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -49,64 +49,62 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.view.View;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.views.RecentsView;
 
 /**
  * Animation factory for quickstep specific transitions
  */
-public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<LauncherState> {
+public class QuickstepAtomicAnimationFactory extends
+        RecentsAtomicAnimationFactory<Launcher, LauncherState> {
 
     // Scale recents takes before animating in
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
 
-    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 static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
-    private static final int ANIM_COUNT = 4;
+    public static final int INDEX_SHELF_ANIM = RecentsAtomicAnimationFactory.NEXT_INDEX + 0;
+    public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM =
+            RecentsAtomicAnimationFactory.NEXT_INDEX + 1;
+
+    private static final int MY_ANIM_COUNT = 2;
+    protected static final int NEXT_INDEX = RecentsAtomicAnimationFactory.NEXT_INDEX
+            + MY_ANIM_COUNT;
 
     public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
 
-    private final QuickstepLauncher mLauncher;
-
-    public QuickstepAtomicAnimationFactory(QuickstepLauncher launcher) {
-        super(ANIM_COUNT);
-        mLauncher = launcher;
+    public QuickstepAtomicAnimationFactory(QuickstepLauncher activity) {
+        super(activity, MY_ANIM_COUNT);
     }
 
     @Override
     public Animator createStateElementAnimation(int index, float... values) {
         switch (index) {
             case INDEX_SHELF_ANIM: {
-                AllAppsTransitionController aatc = mLauncher.getAllAppsController();
+                AllAppsTransitionController aatc = mActivity.getAllAppsController();
                 Animator springAnim = aatc.createSpringAnimation(values);
 
-                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
                     // Translate hotseat with the shelf until reaching overview.
-                    float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
-                    ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
+                    float overviewProgress = OVERVIEW.getVerticalProgress(mActivity);
+                    ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mActivity);
                     float shiftRange = aatc.getShiftRange();
                     if (values.length == 1) {
                         values = new float[] {aatc.getProgress(), values[0]};
@@ -114,9 +112,9 @@
                     ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
                     hotseatAnim.addUpdateListener(anim -> {
                         float progress = (Float) anim.getAnimatedValue();
-                        if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+                        if (progress >= overviewProgress || mActivity.isInState(BACKGROUND_APP)) {
                             float hotseatShift = (progress - overviewProgress) * shiftRange;
-                            mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+                            mActivity.getHotseat().setTranslationY(hotseatShift + sat.translationY);
                         }
                     });
                     hotseatAnim.setInterpolator(LINEAR);
@@ -130,34 +128,21 @@
 
                 return springAnim;
             }
-            case INDEX_RECENTS_FADE_ANIM:
-                return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
-                        RecentsView.CONTENT_ALPHA, values);
-            case INDEX_RECENTS_TRANSLATE_X_ANIM: {
-                RecentsView rv = mLauncher.getOverviewPanel();
-                return new SpringAnimationBuilder(mLauncher)
-                        .setMinimumVisibleChange(1f / rv.getPageOffsetScale())
-                        .setDampingRatio(0.8f)
-                        .setStiffness(250)
-                        .setValues(values)
-                        .build(rv, ADJACENT_PAGE_OFFSET);
-            }
             case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
                 StateAnimationConfig config = new StateAnimationConfig();
                 config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
 
                 config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
                 config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
-                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
                     config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
                     config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
                 }
 
-                StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+                StateManager<LauncherState> stateManager = mActivity.getStateManager();
                 return stateManager.createAtomicAnimation(
                         stateManager.getCurrentStableState(), OVERVIEW, config);
             }
-
             default:
                 return super.createStateElementAnimation(index, values);
         }
@@ -172,7 +157,7 @@
             config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
             config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
-            Workspace workspace = mLauncher.getWorkspace();
+            Workspace workspace = mActivity.getWorkspace();
 
             // Start from a higher workspace scale, but only if we're invisible so we don't jump.
             boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
@@ -186,13 +171,13 @@
                 workspace.setScaleX(0.92f);
                 workspace.setScaleY(0.92f);
             }
-            Hotseat hotseat = mLauncher.getHotseat();
+            Hotseat hotseat = mActivity.getHotseat();
             boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
             if (!isHotseatVisible) {
                 hotseat.setScaleX(0.92f);
                 hotseat.setScaleY(0.92f);
                 if (ENABLE_OVERVIEW_ACTIONS.get()) {
-                    AllAppsContainerView qsbContainer = mLauncher.getAppsView();
+                    AllAppsContainerView qsbContainer = mActivity.getAppsView();
                     View qsb = qsbContainer.getSearchView();
                     boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
                     if (!qsbVisible) {
@@ -209,7 +194,7 @@
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
             config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
         } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
-            if (SysUINavigationMode.getMode(mLauncher) == NO_BUTTON) {
+            if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
                 config.setInterpolator(ANIM_WORKSPACE_SCALE,
                         fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
                 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
@@ -217,7 +202,7 @@
                 config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
 
                 // Scale up the recents, if it is not coming from the side
-                RecentsView overview = mLauncher.getOverviewPanel();
+                RecentsView overview = mActivity.getOverviewPanel();
                 if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
                     SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
                 }
@@ -225,7 +210,7 @@
             config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
-                    && removeShelfFromOverview(mLauncher)
+                    && removeShelfFromOverview(mActivity)
                     ? OVERSHOOT_1_2
                     : OVERSHOOT_1_7;
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 7385658..c1b68ab 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -116,7 +116,7 @@
         mRecentsView = mLauncher.getOverviewPanel();
         mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
         mYRange = LayoutUtils.getShelfTrackingDistance(
-            mLauncher, mLauncher.getDeviceProfile());
+            mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
         mMotionPauseDetector = new MotionPauseDetector(mLauncher);
         mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index dc8fb9e..f4d1629 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -77,7 +77,6 @@
                     controller.dispatchOnStart();
                     controller.getAnimationPlayer().end();
                 });
-        factory.onRemoteAnimationReceived(null);
         factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
         factory.setRecentsAttachedToAppWindow(true, false);
         mActivity = activity;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 614ba46..66f6d02 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -55,7 +55,6 @@
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -234,7 +233,6 @@
             }
             mCanceled = false;
         }
-        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
     }
 
     /**
@@ -329,6 +327,7 @@
                 mRecentsAnimationController.finish(false /* toRecents */,
                         null /* onFinishComplete */);
                 mActivityInterface.onLaunchTaskSuccess();
+                ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
             }
         }
     }
@@ -357,12 +356,13 @@
     protected void initTransitionEndpoints(DeviceProfile dp) {
         mDp = dp;
 
-        mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
-                dp, mContext, TEMP_RECT);
         mTaskViewSimulator.setDp(dp);
         mTaskViewSimulator.setLayoutRotation(
                 mDeviceState.getCurrentActiveRotation(),
                 mDeviceState.getDisplayRotation());
+        mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
+                dp, mContext, TEMP_RECT,
+                mTaskViewSimulator.getOrientationState().getOrientationHandler());
 
         if (mDeviceState.isFullyGesturalNavMode()) {
             // We can drag all the way to the top of the screen.
@@ -510,8 +510,8 @@
 
     public interface Factory {
 
-        BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs,
-                boolean continuingLastGesture, boolean isLikelyToStartNewTask);
+        BaseSwipeUpHandler newHandler(
+                GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
     }
 
     protected interface RunningWindowAnim {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
similarity index 91%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
index a8fa630..23cc6d5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -38,29 +37,24 @@
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
-import android.animation.AnimatorSet;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PointF;
-import android.graphics.RectF;
 import android.os.Build;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
 import android.view.WindowInsets;
 import android.view.animation.Interpolator;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
@@ -72,7 +66,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -80,7 +73,6 @@
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
-import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.util.TransformParams.TargetAlphaProvider;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
@@ -92,11 +84,12 @@
 
 /**
  * Handles the navigation gestures when Launcher is the default home activity.
+ * TODO: Merge this with BaseSwipeUpHandler
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsView>
-        implements OnApplyWindowInsetsListener {
-    private static final String TAG = LauncherSwipeHandler.class.getSimpleName();
+public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q extends RecentsView>
+        extends BaseSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
+    private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
 
@@ -108,9 +101,11 @@
     }
 
     // Launcher UI related states
-    private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
-    private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
-    private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+    protected static final int STATE_LAUNCHER_PRESENT =
+            getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
+    protected static final int STATE_LAUNCHER_STARTED =
+            getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
+    protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
 
     // Internal initialization states
     private static final int STATE_APP_CONTROLLER_RECEIVED =
@@ -122,7 +117,7 @@
     private static final int STATE_SCALED_CONTROLLER_RECENTS =
             getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
 
-    private static final int STATE_HANDLER_INVALIDATED =
+    protected static final int STATE_HANDLER_INVALIDATED =
             getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
     private static final int STATE_GESTURE_STARTED =
             getFlagForIndex(7, "STATE_GESTURE_STARTED");
@@ -163,7 +158,7 @@
      */
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
-    private final TaskAnimationManager mTaskAnimationManager;
+    protected final TaskAnimationManager mTaskAnimationManager;
 
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
@@ -193,7 +188,7 @@
 
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
 
-    public LauncherSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+    public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
@@ -222,9 +217,6 @@
                         | STATE_GESTURE_CANCELLED,
                 this::resetStateForAnimationCancel);
 
-        mStateCallback.runOnceAtState(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
-                this::sendRemoteAnimationsToAnimationFactory);
-
         mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
                 this::resumeLastTask);
         mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
@@ -272,7 +264,7 @@
     @Override
     protected boolean onActivityInit(Boolean alreadyOnHome) {
         super.onActivityInit(alreadyOnHome);
-        final Launcher activity = mActivityInterface.getCreatedActivity();
+        final T activity = mActivityInterface.getCreatedActivity();
         if (mActivity == activity) {
             return true;
         }
@@ -323,7 +315,7 @@
     }
 
     private void onLauncherStart() {
-        final Launcher activity = mActivityInterface.getCreatedActivity();
+        final T activity = mActivityInterface.getCreatedActivity();
         if (mActivity != activity) {
             return;
         }
@@ -411,6 +403,10 @@
             updateSysUiFlags(mCurrentShift.value);
             return;
         }
+        notifyGestureAnimationStartToRecents();
+    }
+
+    protected void notifyGestureAnimationStartToRecents() {
         mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
     }
 
@@ -418,10 +414,6 @@
         mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
     }
 
-    private void sendRemoteAnimationsToAnimationFactory() {
-        mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationTargets);
-    }
-
     private void initializeLauncherAnimationController() {
         buildAnimationController();
 
@@ -633,7 +625,7 @@
      */
     @UiThread
     private void notifyGestureStartedAsync() {
-        final Launcher curActivity = mActivity;
+        final T curActivity = mActivity;
         if (curActivity != null) {
             // Once the gesture starts, we can no longer transition home through the button, so
             // reset the force override of the activity visibility
@@ -681,7 +673,7 @@
         endLauncherTransitionController();
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // Hide the task view, if not already hidden
-            setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha);
+            setTargetAlphaProvider(BaseSwipeUpHandlerV2::getHiddenTargetAlpha);
         }
 
         StatefulActivity activity = mActivityInterface.getCreatedActivity();
@@ -717,6 +709,7 @@
                 mStateCallback.setState(STATE_RESUME_LAST_TASK);
                 break;
         }
+        ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
     }
 
     @Override
@@ -787,12 +780,6 @@
             }
         }
 
-        if (endTarget == RECENTS || endTarget == HOME) {
-            // Since we're now done quickStepping, we want to only listen for touch events
-            // for the main orientation's nav bar, instead of multiple
-            mDeviceState.enableMultipleRegions(false);
-        }
-
         if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
             return LAST_TASK;
         }
@@ -911,6 +898,8 @@
                 interpolator, target, velocityPxPerMs));
     }
 
+    protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration);
+
     @UiThread
     private void animateToProgressInternal(float start, float end, long duration,
             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
@@ -919,67 +908,7 @@
         maybeUpdateRecentsAttachedState();
 
         if (mGestureState.getEndTarget() == HOME) {
-            HomeAnimationFactory homeAnimFactory;
-            if (mActivity != null) {
-                final TaskView runningTaskView = mRecentsView.getRunningTaskView();
-                final View workspaceView;
-                if (runningTaskView != null
-                        && runningTaskView.getTask().key.getComponent() != null) {
-                    workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
-                            runningTaskView.getTask().key.getComponent().getPackageName(),
-                            UserHandle.of(runningTaskView.getTask().key.userId));
-                } else {
-                    workspaceView = null;
-                }
-                final RectF iconLocation = new RectF();
-                boolean canUseWorkspaceView =
-                        workspaceView != null && workspaceView.isAttachedToWindow();
-                FloatingIconView floatingIconView = canUseWorkspaceView
-                        ? FloatingIconView.getFloatingIconView(mActivity, workspaceView,
-                        true /* hideOriginal */, iconLocation, false /* isOpening */)
-                        : null;
-
-                mActivity.getRootView().setForceHideBackArrow(true);
-                mActivityInterface.setHintUserWillBeActive();
-
-                homeAnimFactory = new HomeAnimationFactory(floatingIconView) {
-
-                    @Override
-                    public RectF getWindowTargetRect() {
-                        if (canUseWorkspaceView) {
-                            return iconLocation;
-                        } else {
-                            return super.getWindowTargetRect();
-                        }
-                    }
-
-                    @NonNull
-                    @Override
-                    public AnimatorPlaybackController createActivityAnimationToHome() {
-                        // Return an empty APC here since we have an non-user controlled animation
-                        // to home.
-                        long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
-                        return mActivity.getStateManager().createAnimationToNewWorkspace(
-                                NORMAL, accuracy, 0 /* animComponents */);
-                    }
-
-                    @Override
-                    public void playAtomicAnimation(float velocity) {
-                        new StaggeredWorkspaceAnim(mActivity, velocity,
-                                true /* animateOverviewScrim */).start();
-                    }
-                };
-
-            } else {
-                homeAnimFactory = new HomeAnimationFactory(null) {
-                    @Override
-                    public AnimatorPlaybackController createActivityAnimationToHome() {
-                        return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
-                    }
-                };
-                mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
-                        isPresent -> mRecentsView.startHome());
-            }
+            HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
             RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
             windowAnim.addAnimatorListener(new AnimationSuccessListener() {
                 @Override
@@ -1303,14 +1232,16 @@
             // If there are no targets or the animation not started, then there is nothing to finish
             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else {
-            mRecentsAnimationController.finish(true /* toRecents */,
-                    () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
-                    true /* sendUserLeaveHint */);
+            finishRecentsControllerToHome(
+                    () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
         doLogGesture(HOME);
+        mDeviceState.enableMultipleRegions(false);
     }
 
+    protected abstract void finishRecentsControllerToHome(Runnable callback);
+
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
         mActivityInterface.onSwipeUpToRecentsComplete();
@@ -1322,6 +1253,7 @@
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
         doLogGesture(RECENTS);
+        mDeviceState.onSwipeUpToOverview(mActivityInterface);
         reset();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 4b3af31..c9ff884 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -15,26 +15,21 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
-import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
-import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 
 import android.content.Context;
-import android.graphics.PointF;
 import android.graphics.Rect;
+import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.views.RecentsView;
@@ -54,12 +49,14 @@
     public static final FallbackActivityInterface INSTANCE = new FallbackActivityInterface();
 
     private FallbackActivityInterface() {
-        super(false);
+        super(false, DEFAULT, BACKGROUND_APP);
     }
 
+    /** 2 */
     @Override
-    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
-        calculateTaskSize(context, dp, outRect);
+    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
+            PagedOrientationHandler orientationHandler) {
+        calculateTaskSize(context, dp, outRect, orientationHandler);
         if (dp.isVerticalBarLayout()
                 && SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) {
             Rect targetInsets = dp.getInsets();
@@ -70,6 +67,13 @@
         }
     }
 
+    /** 4 */
+    @Override
+    public void onSwipeUpToHomeComplete() {
+        onSwipeUpToRecentsComplete();
+    }
+
+    /** 5 */
     @Override
     public void onAssistantVisibilityChanged(float visibility) {
         // This class becomes active when the screen is locked.
@@ -77,50 +81,13 @@
         // set to zero prior to this class becoming active.
     }
 
+    /** 6 */
     @Override
     public AnimationFactory prepareRecentsUI(
             boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
-        RecentsActivity activity = getCreatedActivity();
-        if (activity == null) {
-            return (transitionLength) -> { };
-        }
-
-        activity.getStateManager().goToState(BACKGROUND_APP);
-        FallbackRecentsView rv = activity.getOverviewPanel();
-        rv.setContentAlpha(0);
-
-        return new AnimationFactory() {
-
-            boolean isAnimatingToRecents = false;
-
-            @Override
-            public void onRemoteAnimationReceived(RemoteAnimationTargets targets) {
-                isAnimatingToRecents = targets != null && targets.isAnimatingHome();
-                if (!isAnimatingToRecents) {
-                    rv.setContentAlpha(1);
-                }
-                createActivityInterface(getSwipeUpDestinationAndLength(
-                        activity.getDeviceProfile(), activity, new Rect()));
-            }
-
-            @Override
-            public void createActivityInterface(long transitionLength) {
-                PendingAnimation pa = new PendingAnimation(transitionLength * 2);
-
-                if (isAnimatingToRecents) {
-                    pa.addFloat(rv, CONTENT_ALPHA, 0, 1, LINEAR);
-                }
-
-                pa.addFloat(rv, SCALE_PROPERTY, rv.getMaxScaleForFullScreen(), 1, LINEAR);
-                pa.addFloat(rv, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
-                AnimatorPlaybackController controller = pa.createPlaybackController();
-
-                // Since we are changing the start position of the UI, reapply the state, at the end
-                controller.setEndAction(() -> activity.getStateManager().goToState(
-                        controller.getInterpolatedProgress() > 0.5 ? DEFAULT : BACKGROUND_APP));
-                callback.accept(controller);
-            }
-        };
+        DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
+        factory.initUI();
+        return factory;
     }
 
     @Override
@@ -164,6 +131,20 @@
     }
 
     @Override
+    public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+        // In non-gesture mode, user might be clicking on the home button which would directly
+        // start the home activity instead of going through recents. In that case, defer starting
+        // recents until we are sure it is a gesture.
+        return !deviceState.isFullyGesturalNavMode()
+                || super.deferStartingActivity(deviceState, ev);
+    }
+
+    @Override
+    public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+        // no-op, fake landscape not supported for 3P
+    }
+
+    @Override
     public int getContainerType() {
         RecentsActivity activity = getCreatedActivity();
         boolean visible = activity != null && activity.isStarted() && activity.hasWindowFocus();
@@ -188,12 +169,8 @@
     }
 
     @Override
-    public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) {
-        out.set(dp.widthPx, dp.heightPx);
-    }
-
-    @Override
-    protected float getExtraSpace(Context context, DeviceProfile dp) {
+    protected float getExtraSpace(Context context, DeviceProfile dp,
+            PagedOrientationHandler orientationHandler) {
         return showOverviewActions(context)
                 ? context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height)
                 : 0;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index db41bd6..7b614c2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -15,537 +15,117 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
-import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
-import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
-import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
-import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
-import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
-import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.PointF;
-import android.os.Bundle;
-import android.util.ArrayMap;
-import android.view.MotionEvent;
 
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimationSuccessListener;
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.util.ObjectWrapper;
-import com.android.quickstep.BaseActivityInterface.AnimationFactory;
-import com.android.quickstep.GestureState.GestureEndTarget;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.quickstep.util.RectFSpringAnim;
-import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.quickstep.util.TransformParams;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
 
 /**
  * Handles the navigation gestures when a 3rd party launcher is the default home activity.
  */
-public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+public class FallbackSwipeHandler extends
+        BaseSwipeUpHandlerV2<RecentsActivity, FallbackRecentsView> {
 
-    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
-
-    private static int getFlagForIndex(int index, String name) {
-        if (DEBUG_STATES) {
-            STATE_NAMES[index] = name;
-        }
-        return 1 << index;
-    }
-
-    private static final int STATE_RECENTS_PRESENT =
-            getFlagForIndex(0, "STATE_RECENTS_PRESENT");
-    private static final int STATE_HANDLER_INVALIDATED =
-            getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
-
-    private static final int STATE_GESTURE_CANCELLED =
-            getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
-    private static final int STATE_GESTURE_COMPLETED =
-            getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
-    private static final int STATE_APP_CONTROLLER_RECEIVED =
-            getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
-
-    public static class EndTargetAnimationParams {
-        private final float mEndProgress;
-        private final long mDurationMultiplier;
-        private final float mLauncherAlpha;
-
-        EndTargetAnimationParams(float endProgress, long durationMultiplier, float launcherAlpha) {
-            mEndProgress = endProgress;
-            mDurationMultiplier = durationMultiplier;
-            mLauncherAlpha = launcherAlpha;
-        }
-    }
-    private final ArrayMap<GestureEndTarget, EndTargetAnimationParams>
-            mEndTargetAnimationParams = new ArrayMap();
-
-    private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
-
-    private boolean mOverviewThresholdPassed = false;
-
-    private final boolean mInQuickSwitchMode;
-    private final boolean mContinuingLastGesture;
+    private FallbackHomeAnimationFactory mActiveAnimationFactory;
     private final boolean mRunningOverHome;
-    private final boolean mSwipeUpOverHome;
-    private boolean mTouchedHomeDuringTransition;
-
-    private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
-    private RunningWindowAnim mFinishAnimation;
-
-    // Used to control Recents components throughout the swipe gesture.
-    private AnimatorPlaybackController mLauncherTransitionController;
-    private boolean mHasLauncherTransitionControllerStarted;
-
-    private AnimationFactory mAnimationFactory = (t) -> { };
 
     public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, InputConsumerController inputConsumer,
-            boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
-        super(context, deviceState, gestureState, inputConsumer);
+            TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
+            boolean continuingLastGesture, InputConsumerController inputConsumer) {
+        super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
+                continuingLastGesture, inputConsumer);
 
-        mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
-        mContinuingLastGesture = continuingLastGesture;
         mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
-        mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
-
-        // Keep the home launcher invisible until we decide to land there.
-        mLauncherAlpha.value = mRunningOverHome ? 1 : 0;
-        if (mSwipeUpOverHome) {
-            mTransformParams.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
-        } else {
-            mTransformParams.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
-        }
-
-        // Going home has an extra long progress to ensure that it animates into the screen
-        mEndTargetAnimationParams.put(HOME, new EndTargetAnimationParams(3, 100, 1));
-        mEndTargetAnimationParams.put(RECENTS, new EndTargetAnimationParams(1, 300, 0));
-        mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1));
-        mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1));
-
-        initAfterSubclassConstructor();
-        initStateCallbacks();
-    }
-
-    private void initStateCallbacks() {
-        mStateCallback = new MultiStateCallback(STATE_NAMES);
-
-        mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
-                this::onHandlerInvalidated);
-        mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
-                this::onHandlerInvalidatedWithRecents);
-
-        mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
-                this::finishAnimationTargetSetAnimationComplete);
-
-        if (mInQuickSwitchMode) {
-            mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
-                            | STATE_RECENTS_PRESENT,
-                    this::finishAnimationTargetSet);
-        } else {
-            mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
-                    this::finishAnimationTargetSet);
-        }
-    }
-
-    private void onLauncherAlphaChanged() {
-        if (mRecentsAnimationTargets != null && mGestureState.getEndTarget() == null) {
-            applyWindowTransform();
-        }
     }
 
     @Override
-    protected boolean onActivityInit(Boolean alreadyOnHome) {
-        super.onActivityInit(alreadyOnHome);
-        mActivity = mActivityInterface.getCreatedActivity();
-        mRecentsView = mActivity.getOverviewPanel();
-        mRecentsView.setOnPageTransitionEndCallback(null);
-        linkRecentsViewScroll();
-        if (!mContinuingLastGesture) {
-            if (mRunningOverHome) {
-                mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
-            } else {
-                mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
-            }
-        }
-        mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
-        mDeviceState.enableMultipleRegions(false);
-
-        mAnimationFactory = mActivityInterface.prepareRecentsUI(alreadyOnHome,
-                this::onAnimatorPlaybackControllerCreated);
-        mAnimationFactory.createActivityInterface(mTransitionDragLength);
-        return true;
-    }
-
-    @Override
-    protected void initTransitionEndpoints(DeviceProfile dp) {
-        super.initTransitionEndpoints(dp);
-        if (canCreateNewOrUpdateExistingLauncherTransitionController()) {
-            mAnimationFactory.createActivityInterface(mTransitionDragLength);
-        }
-    }
-
-    private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
-        mLauncherTransitionController = anim;
-        mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
-        mLauncherTransitionController.dispatchOnStart();
-        updateLauncherTransitionProgress();
-    }
-
-    private void updateLauncherTransitionProgress() {
-        if (mLauncherTransitionController == null
-                || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
-            return;
-        }
-        // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
-        // anyway. The controller mimics the drag length factor by applying it to its interpolators.
-        float progress = mCurrentShift.value / mDragLengthFactor;
-        mLauncherTransitionController.setPlayFraction(progress);
-    }
-
-    /**
-     * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
-     * (it has its own animation) or if we're already animating the current controller.
-     * @return Whether we can create the launcher controller or update its progress.
-     */
-    private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
-        return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
-    }
-
-    @Override
-    protected boolean moveWindowWithRecentsScroll() {
-        return mInQuickSwitchMode;
-    }
-
-    @Override
-    public void initWhenReady(Intent intent) {
-        if (mInQuickSwitchMode) {
-            // Only init if we are in quickswitch mode
-            super.initWhenReady(intent);
-        }
-    }
-
-    @Override
-    public void updateDisplacement(float displacement) {
-        if (!mInQuickSwitchMode) {
-            super.updateDisplacement(displacement);
-        }
-    }
-
-    @Override
-    protected InputConsumer createNewInputProxyHandler() {
-        // Just consume all input on the active task
-        return new InputConsumer() {
-            @Override
-            public int getType() {
-                return InputConsumer.TYPE_NO_OP;
-            }
-
-            @Override
-            public void onMotionEvent(MotionEvent ev) {
-                mTouchedHomeDuringTransition = true;
-            }
-        };
-    }
-
-    @Override
-    public void onMotionPauseChanged(boolean isPaused) {
-        if (!mInQuickSwitchMode && mDeviceState.isFullyGesturalNavMode()) {
-            updateOverviewThresholdPassed(isPaused);
-        }
-    }
-
-    private void updateOverviewThresholdPassed(boolean passed) {
-        if (passed != mOverviewThresholdPassed) {
-            mOverviewThresholdPassed = passed;
-            if (mSwipeUpOverHome) {
-                mLauncherAlpha.animateToValue(mLauncherAlpha.value, passed ? 0 : 1)
-                        .setDuration(150).start();
-            }
-            performHapticFeedback();
-        }
-    }
-
-    @Override
-    public Intent getLaunchIntent() {
-        if (mInQuickSwitchMode || mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
-            return mGestureState.getOverviewIntent();
-        } else {
-            return mGestureState.getHomeIntent();
-        }
-    }
-
-    @Override
-    public void updateFinalShift() {
-        mTransformParams.setProgress(mCurrentShift.value);
-        if (mRecentsAnimationController != null) {
-            boolean swipeUpThresholdPassed = mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
-            mRecentsAnimationController.setUseLauncherSystemBarFlags(mInQuickSwitchMode
-                    || swipeUpThresholdPassed);
-            mRecentsAnimationController.setSplitScreenMinimized(!mInQuickSwitchMode
-                    && swipeUpThresholdPassed);
-        }
-
-        if (!mInQuickSwitchMode && !mDeviceState.isFullyGesturalNavMode()) {
-            updateOverviewThresholdPassed(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW);
-        }
-
-        applyWindowTransform();
-        updateLauncherTransitionProgress();
-    }
-
-    @Override
-    public void onGestureCancelled() {
-        updateDisplacement(0);
-        mGestureState.setEndTarget(LAST_TASK);
-        mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED);
-    }
-
-    @Override
-    public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
-        mEndVelocityPxPerMs.set(0, velocity.y / 1000);
-        if (mInQuickSwitchMode) {
-            // For now set it to non-null, it will be reset before starting the animation
-            mGestureState.setEndTarget(LAST_TASK);
-        } else {
-            float flingThreshold = mContext.getResources()
-                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
-            boolean isFling = Math.abs(endVelocity) > flingThreshold;
-
-            if (mDeviceState.isFullyGesturalNavMode()) {
-                if (isFling) {
-                    mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
-                } else if (mOverviewThresholdPassed) {
-                    mGestureState.setEndTarget(RECENTS);
-                } else {
-                    mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
-                            ? HOME
-                            : LAST_TASK);
-                }
-            } else {
-                GestureEndTarget startState = mSwipeUpOverHome ? HOME : LAST_TASK;
-                if (isFling) {
-                    mGestureState.setEndTarget(endVelocity < 0 ? RECENTS : startState);
-                } else {
-                    mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
-                            ? RECENTS
-                            : startState);
-                }
-            }
-        }
-        mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
-    }
-
-    @Override
-    public void onConsumerAboutToBeSwitched() {
-        if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) {
-            mGestureState.setEndTarget(NEW_TASK);
-
-            mCanceled = true;
-            mCurrentShift.cancelAnimation();
-            if (mFinishAnimation != null) {
-                mFinishAnimation.cancel();
-            }
-
-            if (mRecentsView != null) {
-                mRecentsView.setOnScrollChangeListener(null);
-            }
-        } else {
-            mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
-        }
-    }
-
-    private void onHandlerInvalidated() {
-        mActivityInitListener.unregister();
-        if (mGestureEndCallback != null) {
-            mGestureEndCallback.run();
-        }
-        if (mFinishAnimation != null) {
-            mFinishAnimation.end();
-        }
-    }
-
-    private void onHandlerInvalidatedWithRecents() {
-        mRecentsView.onGestureAnimationEnd();
-        mRecentsView.setDisallowScrollToClearAll(false);
-        mRecentsView.getClearAllButton().setVisibilityAlpha(1);
-    }
-
-    private void finishAnimationTargetSetAnimationComplete() {
-        switch (mGestureState.getEndTarget()) {
-            case HOME: {
-                if (mSwipeUpOverHome) {
-                    mRecentsAnimationController.finish(false, null, false);
-                    // Send a home intent to clear the task stack
-                    mContext.startActivity(mGestureState.getHomeIntent());
-                } else {
-                    // Notify swipe-to-home (recents animation) is finished
-                    SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
-                    mRecentsAnimationController.finish(true, () -> {
-                        if (!mTouchedHomeDuringTransition) {
-                            // If the user hasn't interacted with the screen during the transition,
-                            // send a home intent so launcher can go to the default home screen.
-                            // (If they are trying to touch something, we don't want to interfere.)
-                            mContext.startActivity(mGestureState.getHomeIntent());
-                        }
-                    }, true);
-                }
-                break;
-            }
-            case LAST_TASK:
-                mRecentsAnimationController.finish(false, null, false);
-                break;
-            case RECENTS: {
-                if (mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
-                    mRecentsAnimationController.finish(true, null, true);
-                    break;
-                }
-
-                final int runningTaskId = mGestureState.getRunningTaskId();
-                ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(runningTaskId);
-                mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
-                        false /* screenshot */);
-
-                ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
-                ActivityOptionsCompat.setFreezeRecentTasksList(options);
-
-                Bundle extras = new Bundle();
-                extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
-                extras.putInt(EXTRA_TASK_ID, runningTaskId);
-
-                Intent intent = new Intent(mGestureState.getOverviewIntent())
-                        .putExtras(extras);
-                mContext.startActivity(intent, options.toBundle());
-                mRecentsAnimationController.cleanupScreenshot();
-                break;
-            }
-            case NEW_TASK: {
-                startNewTask(success -> { });
-                break;
-            }
-        }
-
-        mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
-    }
-
-    private void finishAnimationTargetSet() {
-        if (mInQuickSwitchMode) {
-            // Recalculate the end target, some views might have been initialized after
-            // gesture has ended.
-            if (mRecentsView == null || !hasTargets()) {
-                mGestureState.setEndTarget(LAST_TASK);
-            } else {
-                final int runningTaskIndex = getLastAppearedTaskIndex();
-                final int taskToLaunch = mRecentsView.getNextPage();
-                mGestureState.setEndTarget(
-                        (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
-                                ? NEW_TASK
-                                : LAST_TASK);
-            }
-        }
-
-        EndTargetAnimationParams params = mEndTargetAnimationParams.get(mGestureState.getEndTarget());
-        float endProgress = params.mEndProgress;
-        long duration = (long) (params.mDurationMultiplier *
-                Math.abs(endProgress - mCurrentShift.value));
-        if (mRecentsView != null) {
-            duration = Math.max(duration, mRecentsView.getScroller().getDuration());
-        }
-        if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
-            AnimationSuccessListener endListener = new AnimationSuccessListener() {
-
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    if (mRecentsView != null) {
-                        mRecentsView.setOnPageTransitionEndCallback(FallbackSwipeHandler.this
-                                ::finishAnimationTargetSetAnimationComplete);
-                    } else {
-                        finishAnimationTargetSetAnimationComplete();
-                    }
-                    mFinishAnimation = null;
-                }
-            };
-
-            if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) {
-                mRecentsAnimationController.enableInputProxy(mInputConsumer,
-                        this::createNewInputProxyHandler);
-                RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
-                anim.addAnimatorListener(endListener);
-                anim.start(mContext, mEndVelocityPxPerMs);
-                mFinishAnimation = RunningWindowAnim.wrap(anim);
-            } else {
-
-                AnimatorSet anim = new AnimatorSet();
-                anim.play(mLauncherAlpha.animateToValue(
-                        mLauncherAlpha.value, params.mLauncherAlpha));
-                anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
-
-                anim.setDuration(duration);
-                anim.addListener(endListener);
-                anim.start();
-                mFinishAnimation = RunningWindowAnim.wrap(anim);
-            }
-
-        } else {
-            finishAnimationTargetSetAnimationComplete();
-        }
-    }
-
-    @Override
-    public void onRecentsAnimationStart(RecentsAnimationController controller,
-            RecentsAnimationTargets targets) {
-        super.onRecentsAnimationStart(controller, targets);
-        mRecentsAnimationController.enableInputConsumer();
-        applyWindowTransform();
-
-        mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
-    }
-
-    @Override
-    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
-
-        // Defer clearing the controller and the targets until after we've updated the state
-        super.onRecentsAnimationCanceled(thumbnailData);
+    protected HomeAnimationFactory createHomeAnimationFactory(long duration) {
+        mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
+        ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+        mContext.startActivity(new Intent(mGestureState.getHomeIntent()), options.toBundle());
+        return mActiveAnimationFactory;
     }
 
     @Override
     protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
-        return true;
-    }
+        if (mActiveAnimationFactory != null
+                && mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
+            mActiveAnimationFactory = null;
+            return false;
+        }
 
-    /**
-     * Creates an animation that transforms the current app window into the home app.
-     * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
-     */
-    private RectFSpringAnim createWindowAnimationToHome(float startProgress, long duration) {
-        HomeAnimationFactory factory = new HomeAnimationFactory(null) {
-            @Override
-            public AnimatorPlaybackController createActivityAnimationToHome() {
-                AnimatorSet anim = new AnimatorSet();
-                Animator fadeInLauncher = mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1);
-                fadeInLauncher.setInterpolator(ACCEL_2);
-                anim.play(fadeInLauncher);
-                anim.setDuration(duration);
-                return AnimatorPlaybackController.wrap(anim, duration);
-            }
-        };
-        return createWindowAnimationToHome(startProgress, factory);
+        return super.handleTaskAppeared(appearedTaskTarget);
     }
 
     @Override
-    protected float getWindowAlpha(float progress) {
-        return 1 - ACCEL_1_5.getInterpolation(progress);
+    protected void finishRecentsControllerToHome(Runnable callback) {
+        mRecentsAnimationController.finish(
+                false /* toRecents */, callback, true /* sendUserLeaveHint */);
+    }
+
+    @Override
+    protected void notifyGestureAnimationStartToRecents() {
+        if (mRunningOverHome) {
+            mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
+        } else {
+            super.notifyGestureAnimationStartToRecents();
+        }
+    }
+
+    private class FallbackHomeAnimationFactory extends HomeAnimationFactory
+            implements TransformParams.BuilderProxy {
+
+        private final TransformParams mHomeAlphaParams = new TransformParams();
+        private final AnimatedFloat mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
+
+        private final long mDuration;
+        FallbackHomeAnimationFactory(long duration) {
+            super(null);
+            mDuration = duration;
+        }
+
+        @NonNull
+        @Override
+        public AnimatorPlaybackController createActivityAnimationToHome() {
+            PendingAnimation pa = new PendingAnimation(mDuration);
+            pa.setFloat(mHomeAlpha, AnimatedFloat.VALUE, 1, LINEAR);
+            return pa.createPlaybackController();
+        }
+
+        private void updateHomeAlpha() {
+            mHomeAlphaParams.setProgress(mHomeAlpha.value);
+            if (mHomeAlphaParams.getTargetSet() != null) {
+                mHomeAlphaParams.applySurfaceParams(mHomeAlphaParams.createSurfaceParams(this));
+            }
+        }
+
+        public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+            if (appearedTaskTarget.activityType == ACTIVITY_TYPE_HOME) {
+                RemoteAnimationTargets targets = new RemoteAnimationTargets(
+                        new RemoteAnimationTargetCompat[] {appearedTaskTarget},
+                        new RemoteAnimationTargetCompat[0], appearedTaskTarget.mode);
+                mHomeAlphaParams.setTargetSet(targets);
+                updateHomeAlpha();
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onBuildParams(Builder builder, RemoteAnimationTargetCompat app, int targetMode,
+                TransformParams params) { }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 5dbf199..13b84e0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -15,30 +15,18 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
-import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
 import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_SHELF_ANIM;
-import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION;
 import static com.android.quickstep.SysUINavigationMode.getMode;
-import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
 import static com.android.quickstep.util.LayoutUtils.getDefaultSwipeHeight;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
-import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 
-import android.animation.Animator;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.Log;
-import android.view.MotionEvent;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
@@ -56,14 +44,14 @@
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
+import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
-import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -75,23 +63,24 @@
  * {@link BaseActivityInterface} for the in-launcher recents.
  */
 public final class LauncherActivityInterface extends
-        BaseActivityInterface<LauncherState, Launcher> {
+        BaseActivityInterface<LauncherState, BaseQuickstepLauncher> {
 
     public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface();
 
     private LauncherActivityInterface() {
-        super(true);
+        super(true, OVERVIEW, BACKGROUND_APP);
     }
 
     @Override
-    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
-        calculateTaskSize(context, dp, outRect);
+    public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
+            PagedOrientationHandler orientationHandler) {
+        calculateTaskSize(context, dp, outRect, orientationHandler);
         if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) {
             Rect targetInsets = dp.getInsets();
             int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
             return dp.hotseatBarSizePx + hotseatInset;
         } else {
-            return LayoutUtils.getShelfTrackingDistance(context, dp);
+            return LayoutUtils.getShelfTrackingDistance(context, dp, orientationHandler);
         }
     }
 
@@ -130,119 +119,43 @@
     @Override
     public AnimationFactory prepareRecentsUI(
             boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
-        BaseQuickstepLauncher launcher = getCreatedActivity();
-        final LauncherState startState = launcher.getStateManager().getState();
-
-        LauncherState resetState = startState;
-        if (startState.shouldDisableRestore()) {
-            resetState = launcher.getStateManager().getRestState();
-        }
-        launcher.getStateManager().setRestState(resetState);
-
-        launcher.getStateManager().goToState(BACKGROUND_APP, false);
-        // Since all apps is not visible, we can safely reset the scroll position.
-        // This ensures then the next swipe up to all-apps starts from scroll 0.
-        launcher.getAppsView().reset(false /* animate */);
-
-        return new AnimationFactory() {
-            private final ShelfPeekAnim mShelfAnim = launcher.getShelfPeekAnim();
-            private boolean mIsAttachedToWindow;
-
-            @Override
-            public void createActivityInterface(long transitionLength) {
-                callback.accept(createBackgroundToOverviewAnim(launcher, transitionLength));
-                // Creating the activity controller animation sometimes reapplies the launcher state
-                // (because we set the animation as the current state animation), so we reapply the
-                // attached state here as well to ensure recents is shown/hidden appropriately.
-                if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) {
-                    setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
-                }
-            }
-
-            @Override
-            public void onTransitionCancelled() {
-                launcher.getStateManager().goToState(startState, false /* animate */);
-            }
-
+        DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
             @Override
             public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
                     long duration) {
-                mShelfAnim.setShelfState(shelfState, interpolator, duration);
+                mActivity.getShelfPeekAnim().setShelfState(shelfState, interpolator, duration);
             }
 
             @Override
-            public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
-                if (mIsAttachedToWindow == attached && animate) {
-                    return;
-                }
-                mIsAttachedToWindow = attached;
-                LauncherRecentsView recentsView = launcher.getOverviewPanel();
-                Animator fadeAnim = launcher.getStateManager()
-                        .createStateElementAnimation(
-                        INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
+            protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity,
+                    PendingAnimation pa) {
+                super.createBackgroundToOverviewAnim(activity, pa);
 
-                float fromTranslation = attached ? 1 : 0;
-                float toTranslation = attached ? 0 : 1;
-                launcher.getStateManager()
-                        .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
-                if (!recentsView.isShown() && animate) {
-                    ADJACENT_PAGE_OFFSET.set(recentsView, fromTranslation);
-                } else {
-                    fromTranslation = ADJACENT_PAGE_OFFSET.get(recentsView);
-                }
-                if (!animate) {
-                    ADJACENT_PAGE_OFFSET.set(recentsView, toTranslation);
-                } else {
-                    launcher.getStateManager().createStateElementAnimation(
-                            INDEX_RECENTS_TRANSLATE_X_ANIM,
-                            fromTranslation, toTranslation).start();
+                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.
+                    pa.add(activity.getStateManager().createStateElementAnimation(
+                            INDEX_SHELF_ANIM,
+                            BACKGROUND_APP.getVerticalProgress(activity),
+                            OVERVIEW.getVerticalProgress(activity)));
                 }
 
-                fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
-                fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
+                // Animate the blur and wallpaper zoom
+                float fromDepthRatio = BACKGROUND_APP.getDepth(activity);
+                float toDepthRatio = OVERVIEW.getDepth(activity);
+                pa.addFloat(getDepthController(),
+                        new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
+                        fromDepthRatio, toDepthRatio, LINEAR);
+
             }
         };
-    }
 
-    private AnimatorPlaybackController createBackgroundToOverviewAnim(
-            Launcher activity, long transitionLength) {
-
-        PendingAnimation pa = new PendingAnimation(transitionLength * 2);
-
-        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.
-            pa.add(activity.getStateManager().createStateElementAnimation(
-                    INDEX_SHELF_ANIM,
-                    BACKGROUND_APP.getVerticalProgress(activity),
-                    OVERVIEW.getVerticalProgress(activity)));
-        }
-
-        // Animate the blur and wallpaper zoom
-        float fromDepthRatio = BACKGROUND_APP.getDepth(activity);
-        float toDepthRatio = OVERVIEW.getDepth(activity);
-        pa.addFloat(getDepthController(), new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
-                fromDepthRatio, toDepthRatio, LINEAR);
-
-
-        //  Scale down recents from being full screen to being in overview.
-        RecentsView recentsView = activity.getOverviewPanel();
-        pa.addFloat(recentsView, SCALE_PROPERTY,
-                BACKGROUND_APP.getOverviewScaleAndOffset(activity)[0],
-                OVERVIEW.getOverviewScaleAndOffset(activity)[0],
-                LINEAR);
-        pa.addFloat(recentsView, FULLSCREEN_PROGRESS,
-                BACKGROUND_APP.getOverviewFullscreenProgress(),
-                OVERVIEW.getOverviewFullscreenProgress(),
-                LINEAR);
-
-        AnimatorPlaybackController controller = pa.createPlaybackController();
-        activity.getStateManager().setCurrentUserControlledAnimation(controller);
-
-        // Since we are changing the start position of the UI, reapply the state, at the end
-        controller.setEndAction(() -> activity.getStateManager().goToState(
-                controller.getInterpolatedProgress() > 0.5 ? OVERVIEW : BACKGROUND_APP, false));
-        return controller;
+        BaseQuickstepLauncher launcher = factory.initUI();
+        // Since all apps is not visible, we can safely reset the scroll position.
+        // This ensures then the next swipe up to all-apps starts from scroll 0.
+        launcher.getAppsView().reset(false /* animate */);
+        return factory;
     }
 
     @Override
@@ -251,6 +164,15 @@
                 onInitListener.test(alreadyOnHome));
     }
 
+    @Override
+    public void setOnDeferredActivityLaunchCallback(Runnable r) {
+        Launcher launcher = getCreatedActivity();
+        if (launcher == null) {
+            return;
+        }
+        launcher.setOnDeferredActivityLaunchCallback(r);
+    }
+
     @Nullable
     @Override
     public BaseQuickstepLauncher getCreatedActivity() {
@@ -258,11 +180,13 @@
     }
 
     @Nullable
-    @UiThread
-    private Launcher getVisibleLauncher() {
-        Launcher launcher = getCreatedActivity();
-        return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
-                launcher : null;
+    @Override
+    public DepthController getDepthController() {
+        BaseQuickstepLauncher launcher = getCreatedActivity();
+        if (launcher == null) {
+            return null;
+        }
+        return launcher.getDepthController();
     }
 
     @Nullable
@@ -273,6 +197,14 @@
                 ? launcher.getOverviewPanel() : null;
     }
 
+    @Nullable
+    @UiThread
+    private Launcher getVisibleLauncher() {
+        Launcher launcher = getCreatedActivity();
+        return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus()
+                ? launcher : null;
+    }
+
     @Override
     public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
         if (TestProtocol.sDebugTracing) {
@@ -292,14 +224,26 @@
         return true;
     }
 
-    @Override
-    public void setHintUserWillBeActive() {
-        getCreatedActivity().setHintUserWillBeActive();
-    }
 
     @Override
-    public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
-        return deviceState.isInDeferredGestureRegion(ev);
+    public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+        final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager();
+        stateManager.addStateListener(
+                new StateManager.StateListener<LauncherState>() {
+                    @Override
+                    public void onStateTransitionComplete(LauncherState toState) {
+                        // Are we going from Recents to Workspace?
+                        if (toState == LauncherState.NORMAL) {
+                            exitRunnable.run();
+
+                            // reset layout on swipe to home
+                            RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+                            recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(),
+                                    deviceState.getDisplayRotation());
+                            stateManager.removeStateListener(this);
+                        }
+                    }
+                });
     }
 
     @Override
@@ -313,6 +257,16 @@
     }
 
     @Override
+    public void updateOverviewPredictionState() {
+        Launcher launcher = getCreatedActivity();
+        if (launcher == null) {
+            return;
+        }
+        PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
+                PredictionUiStateManager.Client.OVERVIEW);
+    }
+
+    @Override
     public int getContainerType() {
         final Launcher launcher = getVisibleLauncher();
         return launcher != null ? launcher.getStateManager().getState().containerType
@@ -350,54 +304,11 @@
     }
 
     @Override
-    public void setOnDeferredActivityLaunchCallback(Runnable r) {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        launcher.setOnDeferredActivityLaunchCallback(r);
-    }
-
-    @Override
-    public void updateOverviewPredictionState() {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
-                PredictionUiStateManager.Client.OVERVIEW);
-    }
-
-    @Nullable
-    @Override
-    public DepthController getDepthController() {
-        BaseQuickstepLauncher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return null;
-        }
-        return launcher.getDepthController();
-    }
-
-    @Override
-    public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) {
-        DeviceProfile fullDp = dp.getFullScreenProfile();
-        // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
-        // account for system insets
-        out.set(fullDp.availableWidthPx, fullDp.availableHeightPx);
-        float halfDividerSize = context.getResources()
-                .getDimension(R.dimen.multi_window_task_divider_size) / 2;
-
-        if (fullDp.isLandscape) {
-            out.x = out.x / 2 - halfDividerSize;
-        } else {
-            out.y = out.y / 2 - halfDividerSize;
-        }
-    }
-
-    @Override
-    protected float getExtraSpace(Context context, DeviceProfile dp) {
-        if (dp.isVerticalBarLayout()) {
-            return  0;
+    protected float getExtraSpace(Context context, DeviceProfile dp,
+            PagedOrientationHandler orientationHandler) {
+        if (dp.isVerticalBarLayout() ||
+                hideShelfInTwoButtonLandscape(context, orientationHandler)) {
+            return 0;
         } else {
             Resources res = context.getResources();
             if (showOverviewActions(context)) {
@@ -423,4 +334,5 @@
             }
         }
     }
+
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
new file mode 100644
index 0000000..fa7d268
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.graphics.RectF;
+import android.os.UserHandle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.InputConsumerController;
+
+/**
+ * Temporary class to allow easier refactoring
+ */
+public class LauncherSwipeHandlerV2 extends
+        BaseSwipeUpHandlerV2<BaseQuickstepLauncher, RecentsView> {
+
+    public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
+            TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
+            boolean continuingLastGesture, InputConsumerController inputConsumer) {
+        super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
+                continuingLastGesture, inputConsumer);
+    }
+
+
+    @Override
+    protected HomeAnimationFactory createHomeAnimationFactory(long duration) {
+        HomeAnimationFactory homeAnimFactory;
+        if (mActivity != null) {
+            final TaskView runningTaskView = mRecentsView.getRunningTaskView();
+            final View workspaceView;
+            if (runningTaskView != null
+                    && runningTaskView.getTask().key.getComponent() != null) {
+                workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
+                        runningTaskView.getTask().key.getComponent().getPackageName(),
+                        UserHandle.of(runningTaskView.getTask().key.userId));
+            } else {
+                workspaceView = null;
+            }
+            final RectF iconLocation = new RectF();
+            boolean canUseWorkspaceView =
+                    workspaceView != null && workspaceView.isAttachedToWindow();
+            FloatingIconView floatingIconView = canUseWorkspaceView
+                    ? FloatingIconView.getFloatingIconView(mActivity, workspaceView,
+                    true /* hideOriginal */, iconLocation, false /* isOpening */)
+                    : null;
+
+            mActivity.getRootView().setForceHideBackArrow(true);
+            mActivity.setHintUserWillBeActive();
+
+            homeAnimFactory = new HomeAnimationFactory(floatingIconView) {
+
+                @Override
+                public RectF getWindowTargetRect() {
+                    if (canUseWorkspaceView) {
+                        return iconLocation;
+                    } else {
+                        return super.getWindowTargetRect();
+                    }
+                }
+
+                @NonNull
+                @Override
+                public AnimatorPlaybackController createActivityAnimationToHome() {
+                    // Return an empty APC here since we have an non-user controlled animation
+                    // to home.
+                    long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
+                    return mActivity.getStateManager().createAnimationToNewWorkspace(
+                            NORMAL, accuracy, 0 /* animComponents */);
+                }
+
+                @Override
+                public void playAtomicAnimation(float velocity) {
+                    new StaggeredWorkspaceAnim(mActivity, velocity,
+                            true /* animateOverviewScrim */).start();
+                }
+            };
+
+        } else {
+            homeAnimFactory = new HomeAnimationFactory(null) {
+                @Override
+                public AnimatorPlaybackController createActivityAnimationToHome() {
+                    return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
+                }
+            };
+            mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+                    isPresent -> mRecentsView.startHome());
+        }
+        return homeAnimFactory;
+    }
+
+    @Override
+    protected void finishRecentsControllerToHome(Runnable callback) {
+        mRecentsAnimationController.finish(
+                true /* toRecents */, callback, true /* sendUserLeaveHint */);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index d4c746f..0d49b2b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -7,6 +7,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.systemui.shared.recents.model.Task;
@@ -35,7 +36,8 @@
 
             case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
                 final float swipeHeight =
-                        LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile);
+                        LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile,
+                                PagedOrientationHandler.HOME_ROTATED);
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
                 return response;
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index a4670fd..33b7f12 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.util.ActivityTracker;
@@ -57,6 +58,7 @@
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsRootView;
 import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -345,6 +347,11 @@
         dumpMisc(prefix + "\t", writer);
     }
 
+    @Override
+    public AtomicAnimationFactory<RecentsState> createAtomicAnimationFactory() {
+        return new RecentsAtomicAnimationFactory<>(this, 0);
+    }
+
     private AnimatorListenerAdapter resetStateListener() {
         return new AnimatorListenerAdapter() {
             @Override
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 8bffc78..ef3d174 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -85,7 +85,6 @@
 import com.android.quickstep.util.AssistantUtilities;
 import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.SplitScreenBounds;
-import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.OverscrollPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -555,7 +554,6 @@
                 || previousGestureState.isRecentsAnimationRunning()
                         ? newBaseConsumer(previousGestureState, newGestureState, event)
                         : mResetGestureInputConsumer;
-        // TODO(b/149880412): 2 button landscape mode is wrecked. Fixit!
         if (mDeviceState.isGesturalNavMode()) {
             handleOrientationSetup(base);
         }
@@ -833,16 +831,16 @@
         }
     }
 
-    private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState,
-            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+    private BaseSwipeUpHandler createLauncherSwipeHandler(
+            GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+        return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
     }
 
-    private BaseSwipeUpHandler createFallbackSwipeHandler(GestureState gestureState,
-            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        return new FallbackSwipeHandler(this, mDeviceState, gestureState,
-                mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
+    private BaseSwipeUpHandler createFallbackSwipeHandler(
+            GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
+        return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+                gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
     }
 
     protected boolean shouldNotifyBackGesture() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index f958e6d..9242771 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -24,11 +24,13 @@
 import android.os.Build;
 import android.util.AttributeSet;
 
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.quickstep.FallbackActivityInterface;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 
@@ -38,7 +40,7 @@
 public class FallbackRecentsView extends RecentsView<RecentsActivity>
         implements StateListener<RecentsState> {
 
-    private RunningTaskInfo mRunningTaskInfo;
+    private RunningTaskInfo mHomeTaskInfo;
 
     public FallbackRecentsView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -67,16 +69,40 @@
         return false;
     }
 
-    public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
-        mRunningTaskInfo = runningTaskInfo;
-        onGestureAnimationStart(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
+    /**
+     * When starting gesture interaction from home, we add a temporary invisible tile corresponding
+     * to the home task. This allows us to handle quick-switch similarly to a quick-switching
+     * from a foreground task.
+     */
+    public void onGestureAnimationStartOnHome(RunningTaskInfo homeTaskInfo) {
+        mHomeTaskInfo = homeTaskInfo;
+        onGestureAnimationStart(homeTaskInfo == null ? -1 : homeTaskInfo.taskId);
+    }
+
+    /**
+     * When the gesture ends and recents view become interactive, we also remove the temporary
+     * invisible tile added for the home task. This also pushes the remaining tiles back
+     * to the center.
+     */
+    @Override
+    public void onGestureAnimationEnd() {
+        super.onGestureAnimationEnd();
+        if (mHomeTaskInfo != null) {
+            TaskView tv = getTaskView(mHomeTaskInfo.taskId);
+            if (tv != null) {
+                PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
+                pa.addEndListener(e -> setCurrentTask(-1));
+                runDismissAnimation(pa);
+            }
+        }
     }
 
     @Override
     public void setCurrentTask(int runningTaskId) {
         super.setCurrentTask(runningTaskId);
-        if (mRunningTaskInfo != null && mRunningTaskInfo.taskId != runningTaskId) {
-            mRunningTaskInfo = null;
+        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId != runningTaskId) {
+            mHomeTaskInfo = null;
+            setRunningTaskHidden(false);
         }
     }
 
@@ -85,7 +111,7 @@
         // When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
         // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
         // track the index of the next task appropriately, as if we are switching on any other app.
-        if (mRunningTaskInfo != null && mRunningTaskInfo.taskId == mRunningTaskId) {
+        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId) {
             // Check if the task list has running task
             boolean found = false;
             for (Task t : tasks) {
@@ -97,7 +123,7 @@
             if (!found) {
                 ArrayList<Task> newList = new ArrayList<>(tasks.size() + 1);
                 newList.addAll(tasks);
-                newList.add(Task.from(new TaskKey(mRunningTaskInfo), mRunningTaskInfo, false));
+                newList.add(Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false));
                 tasks = newList;
             }
         }
@@ -105,6 +131,15 @@
     }
 
     @Override
+    public void setRunningTaskHidden(boolean isHidden) {
+        if (mHomeTaskInfo != null) {
+            // Always keep the home task hidden
+            isHidden = true;
+        }
+        super.setRunningTaskHidden(isHidden);
+    }
+
+    @Override
     public void setModalStateEnabled(boolean isModalState) {
         super.setModalStateEnabled(isModalState);
         if (isModalState) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index abe4af4..3a97216 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -21,7 +21,7 @@
 
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.BaseSwipeUpHandlerV2.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
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 6b0d7a3..ab9ec21 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
@@ -207,7 +207,7 @@
                 // Start the window animation on down to give more time for launcher to draw if the
                 // user didn't start the gesture over the back button
                 if (!mIsDeferredDownTarget) {
-                    startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
+                    startTouchTrackingForWindowAnimation(ev.getEventTime());
                 }
 
                 TraceHelper.INSTANCE.endSection(traceToken);
@@ -275,8 +275,7 @@
                         if (mIsDeferredDownTarget) {
                             // Deferred gesture, start the animation and gesture tracking once
                             // we pass the actual touch slop
-                            startTouchTrackingForWindowAnimation(
-                                    ev.getEventTime(), isLikelyToStartNewTask);
+                            startTouchTrackingForWindowAnimation(ev.getEventTime());
                         }
                         if (!mPassedWindowMoveSlop) {
                             mPassedWindowMoveSlop = true;
@@ -326,12 +325,11 @@
         mInteractionHandler.onGestureStarted();
     }
 
-    private void startTouchTrackingForWindowAnimation(
-            long touchTimeMs, boolean isLikelyToStartNewTask) {
+    private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
 
         mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
-                mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask);
+                mTaskAnimationManager.isRecentsAnimationRunning());
         mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
         Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index d22755e..b743d3f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -99,9 +99,6 @@
     private void updateSourceStack(RemoteAnimationTargetCompat target) {
         mSourceInsets.set(target.contentInsets);
         mSourceStackBounds.set(target.screenSpaceBounds);
-
-        // TODO: Should sourceContainerBounds already have this offset?
-        mSourceStackBounds.offsetTo(target.position.x, target.position.y);
     }
 
     public void updateTargetRect(Rect targetRect) {
@@ -169,14 +166,14 @@
         crop.offsetTo(0, 0);
         float cornerRadius = 0f;
         float scale = Math.max(mCurrentRect.width(), mTargetRect.width()) / crop.width();
+        mTmpMatrix.setTranslate(0, 0);
+        if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+            mTmpMatrix.setTranslate(app.localBounds.left, app.localBounds.top);
+        }
         if (app.mode == targetMode
                 && app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
             mTmpMatrix.setRectToRect(mSourceRect, mCurrentRect, ScaleToFit.FILL);
-            if (app.localBounds != null) {
-                mTmpMatrix.postTranslate(app.localBounds.left, app.localBounds.top);
-            } else {
-                mTmpMatrix.postTranslate(app.position.x, app.position.y);
-            }
+            mTmpMatrix.postTranslate(app.localBounds.left, app.localBounds.top);
             mCurrentClipRectF.roundOut(crop);
             if (mSupportsRoundedCornersOnWindows) {
                 if (params.getCornerRadius() > -1) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
new file mode 100644
index 0000000..5b0d503
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.quickstep.views.RecentsView;
+
+public class RecentsAtomicAnimationFactory<ACTIVITY_TYPE extends StatefulActivity, STATE_TYPE>
+        extends AtomicAnimationFactory<STATE_TYPE> {
+
+    public static final int INDEX_RECENTS_FADE_ANIM = AtomicAnimationFactory.NEXT_INDEX + 0;
+    public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = AtomicAnimationFactory.NEXT_INDEX + 1;
+
+    private static final int MY_ANIM_COUNT = 2;
+    protected static final int NEXT_INDEX = AtomicAnimationFactory.NEXT_INDEX + MY_ANIM_COUNT;
+
+    protected final ACTIVITY_TYPE mActivity;
+
+    /**
+     * @param extraAnims number of animations supported by the subclass. This should not include
+     *                  the 2 animations supported by this class.
+     */
+    public RecentsAtomicAnimationFactory(ACTIVITY_TYPE activity, int extraAnims) {
+        super(MY_ANIM_COUNT + extraAnims);
+        mActivity = activity;
+    }
+
+    @Override
+    public Animator createStateElementAnimation(int index, float... values) {
+        switch (index) {
+            case INDEX_RECENTS_FADE_ANIM:
+                return ObjectAnimator.ofFloat(mActivity.getOverviewPanel(),
+                        RecentsView.CONTENT_ALPHA, values);
+            case INDEX_RECENTS_TRANSLATE_X_ANIM: {
+                RecentsView rv = mActivity.getOverviewPanel();
+                return new SpringAnimationBuilder(mActivity)
+                        .setMinimumVisibleChange(1f / rv.getPageOffsetScale())
+                        .setDampingRatio(0.8f)
+                        .setStiffness(250)
+                        .setValues(values)
+                        .build(rv, ADJACENT_PAGE_OFFSET);
+            }
+            default:
+                return super.createStateElementAnimation(index, values);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index f4f7e9c..348c22d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -116,7 +116,8 @@
         if (mDp == null) {
             return 1;
         }
-        mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect);
+        mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect,
+                mOrientationState.getOrientationHandler());
         return mOrientationState.getFullScreenScaleAndPivot(mTaskRect, mDp, mPivot);
     }
 
@@ -131,8 +132,6 @@
         mThumbnailData.windowingMode = WINDOWING_MODE_FULLSCREEN;
 
         mThumbnailPosition.set(runningTarget.screenSpaceBounds);
-        // TODO: Should sourceContainerBounds already have this offset?
-        mThumbnailPosition.offset(-mRunningTarget.position.x, -mRunningTarget.position.y);
         mLayoutValid = false;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
new file mode 100644
index 0000000..df89f74
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2020 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.views;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+
+import androidx.core.graphics.ColorUtils;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.Themes;
+import com.android.quickstep.util.MultiValueUpdateListener;
+
+/**
+ * View used to educate the user on how to access All Apps when in No Nav Button navigation mode.
+ */
+public class AllAppsEduView extends AbstractFloatingView {
+
+    private Launcher mLauncher;
+
+    private AnimatorSet mAnimation;
+
+    private GradientDrawable mCircle;
+    private GradientDrawable mGradient;
+
+    private int mCircleSizePx;
+    private int mPaddingPx;
+    private int mWidthPx;
+    private int mMaxHeightPx;
+
+    public AllAppsEduView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mCircle = (GradientDrawable) context.getDrawable(R.drawable.all_apps_edu_circle);
+        mCircleSizePx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_circle_size);
+        mPaddingPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_padding);
+        mWidthPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_width);
+        mMaxHeightPx = getResources().getDimensionPixelSize(R.dimen.swipe_edu_max_height);
+        setWillNotDraw(false);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        mGradient.draw(canvas);
+        mCircle.draw(canvas);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mIsOpen = true;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mIsOpen = false;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        mLauncher.getDragLayer().removeView(this);
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // TODO
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ALL_APPS_EDU) != 0;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        return mAnimation != null && mAnimation.isRunning();
+    }
+
+    private void playAnimation() {
+        if (mAnimation != null) {
+            return;
+        }
+        mAnimation = new AnimatorSet();
+
+        final Rect circleBoundsOg = new Rect(mCircle.getBounds());
+        final Rect gradientBoundsOg = new Rect(mGradient.getBounds());
+        final Rect temp = new Rect();
+        final float transY = mMaxHeightPx - mCircleSizePx - mPaddingPx;
+
+        // 1st: Circle alpha/scale
+        int firstPart = 600;
+        // 2nd: Circle animates upwards, Gradient alpha fades in, Gradient grows, All Apps hint
+        int secondPart = 1200;
+        int introDuration = firstPart + secondPart;
+
+        StateAnimationConfig config = new StateAnimationConfig();
+        config.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
+                0, 0.08f));
+        config.duration = secondPart;
+        config.userControlled = false;
+        AnimatorPlaybackController stateAnimationController =
+                mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, config);
+        float maxAllAppsProgress = 0.15f;
+
+        ValueAnimator intro = ValueAnimator.ofFloat(0, 1f);
+        intro.setInterpolator(LINEAR);
+        intro.setDuration(introDuration);
+        intro.addUpdateListener((new MultiValueUpdateListener() {
+            FloatProp mCircleAlpha = new FloatProp(0, 255, 0, firstPart, LINEAR);
+            FloatProp mCircleScale = new FloatProp(2f, 1f, 0, firstPart, OVERSHOOT_1_7);
+            FloatProp mDeltaY = new FloatProp(0, transY, firstPart, secondPart, FAST_OUT_SLOW_IN);
+            FloatProp mGradientAlpha = new FloatProp(0, 255, firstPart, secondPart * 0.3f, LINEAR);
+
+            @Override
+            public void onUpdate(float progress) {
+                temp.set(circleBoundsOg);
+                temp.offset(0, (int) -mDeltaY.value);
+                Utilities.scaleRectAboutCenter(temp, mCircleScale.value);
+                mCircle.setBounds(temp);
+                mCircle.setAlpha((int) mCircleAlpha.value);
+                mGradient.setAlpha((int) mGradientAlpha.value);
+
+                temp.set(gradientBoundsOg);
+                temp.top -= mDeltaY.value;
+                mGradient.setBounds(temp);
+                invalidate();
+
+                float stateProgress = Utilities.mapToRange(mDeltaY.value, 0, transY, 0,
+                        maxAllAppsProgress, LINEAR);
+                stateAnimationController.setPlayFraction(stateProgress);
+            }
+        }));
+        intro.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mCircle.setAlpha(0);
+                mGradient.setAlpha(0);
+            }
+        });
+        mAnimation.play(intro);
+
+        ValueAnimator closeAllApps = ValueAnimator.ofFloat(maxAllAppsProgress, 0f);
+        closeAllApps.addUpdateListener(valueAnimator -> {
+            stateAnimationController.setPlayFraction((float) valueAnimator.getAnimatedValue());
+        });
+        closeAllApps.setInterpolator(FAST_OUT_SLOW_IN);
+        closeAllApps.setStartDelay(introDuration);
+        closeAllApps.setDuration(250);
+        mAnimation.play(closeAllApps);
+
+        mAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimation = null;
+                stateAnimationController.dispatchOnCancel();
+                handleClose(false);
+            }
+        });
+        mAnimation.start();
+    }
+
+    private void init(Launcher launcher) {
+        mLauncher = launcher;
+
+        int accentColor = Themes.getColorAccent(launcher);
+        mGradient = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
+                Themes.getAttrBoolean(launcher, R.attr.isMainColorDark)
+                        ? new int[] {0xB3FFFFFF, 0x00FFFFFF}
+                        : new int[] {ColorUtils.setAlphaComponent(accentColor, 127),
+                                ColorUtils.setAlphaComponent(accentColor, 0)});
+        float r = mWidthPx / 2f;
+        mGradient.setCornerRadii(new float[] {r, r, r, r, 0, 0, 0, 0});
+
+        int top = mMaxHeightPx - mCircleSizePx + mPaddingPx;
+        mCircle.setBounds(mPaddingPx, top, mPaddingPx + mCircleSizePx, top + mCircleSizePx);
+        mGradient.setBounds(0, mMaxHeightPx - mCircleSizePx, mWidthPx, mMaxHeightPx);
+
+        DeviceProfile grid = launcher.getDeviceProfile();
+        DragLayer.LayoutParams lp = new DragLayer.LayoutParams(mWidthPx, mMaxHeightPx);
+        lp.ignoreInsets = true;
+        lp.leftMargin = (grid.widthPx - mWidthPx) / 2;
+        lp.topMargin = grid.heightPx - grid.hotseatBarSizePx - mMaxHeightPx;
+        setLayoutParams(lp);
+    }
+
+    /**
+     * Shows the All Apps education view and plays the animation.
+     */
+    public static void show(Launcher launcher) {
+        final DragLayer dragLayer = launcher.getDragLayer();
+        ViewGroup parent = (ViewGroup) dragLayer.getParent();
+        AllAppsEduView view = launcher.getViewCache().getView(R.layout.all_apps_edu_view,
+                launcher, parent);
+        view.init(launcher);
+        launcher.getDragLayer().addView(view);
+        view.requestLayout();
+        view.playAnimation();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
index 2c85618..fd74357 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
@@ -47,24 +47,21 @@
     private boolean mIsRtl;
 
     private int mScrollOffset;
-    private RecentsView mParent;
 
     public ClearAllButton(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        PagedOrientationHandler orientationHandler = mParent.getPagedOrientationHandler();
-        mScrollOffset = orientationHandler.getClearAllScrollOffset(mParent, mIsRtl);
+        PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+        mScrollOffset = orientationHandler.getClearAllScrollOffset(getRecentsView(), mIsRtl);
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mParent = (RecentsView) getParent();
-        mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+    private RecentsView getRecentsView() {
+        return (RecentsView) getParent();
     }
 
     @Override
@@ -94,7 +91,7 @@
 
     @Override
     public void onPageScroll(ScrollState scrollState) {
-        PagedOrientationHandler orientationHandler = mParent.getPagedOrientationHandler();
+        PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
         float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
         if (orientationSize == 0) {
             return;
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 3fc235c..3273e85 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
@@ -17,6 +17,8 @@
 package com.android.quickstep.views;
 
 import static android.view.Surface.ROTATION_0;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
 
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
@@ -827,6 +829,10 @@
     @Override
     public void setInsets(Rect insets) {
         mInsets.set(insets);
+        resetPaddingFromTaskSize();
+    }
+
+    private void resetPaddingFromTaskSize() {
         DeviceProfile dp = mActivity.getDeviceProfile();
         mOrientationState.setMultiWindowMode(dp.isMultiWindowMode);
         getTaskSize(mTempRect);
@@ -840,7 +846,8 @@
     }
 
     public void getTaskSize(Rect outRect) {
-        mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
+        mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
+                mOrientationHandler);
     }
 
     /** Gets the task size for modal state. */
@@ -1075,9 +1082,9 @@
      * Called when a gesture from an app has finished.
      */
     public void onGestureAnimationEnd() {
+        setOnScrollChangeListener(null);
         setEnableFreeScroll(true);
         setEnableDrawingLiveTile(true);
-        setOnScrollChangeListener(null);
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             setRunningTaskViewShowScreenshot(true);
         }
@@ -1108,6 +1115,12 @@
                     false, true, false, false, new ActivityManager.TaskDescription(), 0,
                     new ComponentName("", ""), false);
             taskView.bind(mTmpRunningTask, mOrientationState);
+
+            // Measure and layout immediately so that the scroll values is updated instantly
+            // as the user might be quick-switching
+            measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
+                    makeMeasureSpec(getMeasuredHeight(), EXACTLY));
+            layout(getLeft(), getTop(), getRight(), getBottom());
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -1484,7 +1497,7 @@
         return true;
     }
 
-    private void runDismissAnimation(PendingAnimation pendingAnim) {
+    protected void runDismissAnimation(PendingAnimation pendingAnim) {
         AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
         controller.dispatchOnStart();
         controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
@@ -1618,6 +1631,7 @@
             mActivity.getDragLayer().recreateControllers();
             mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
                     touchRotation != 0 || mOrientationState.getLauncherRotation() != ROTATION_0);
+            resetPaddingFromTaskSize();
             requestLayout();
         }
     }
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 a3e360f..e3c1b42 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
@@ -472,7 +472,6 @@
             } else {
                 setThumbnailRotation(deltaRotate, thumbnailInsets, scale, thumbnailPosition);
             }
-            mMatrix.postTranslate(-thumbnailPosition.left, -thumbnailPosition.top);
 
             final float widthWithInsets;
             final float heightWithInsets;
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f1ea6bb..9d70316 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -92,4 +92,10 @@
     <dimen name="gesture_tutorial_subtitle_margin_start_end">16dp</dimen>
     <dimen name="gesture_tutorial_feedback_margin_start_end">24dp</dimen>
     <dimen name="gesture_tutorial_button_margin_start_end">18dp</dimen>
+
+    <!-- All Apps Education tutorial -->
+    <dimen name="swipe_edu_padding">8dp</dimen>
+    <dimen name="swipe_edu_circle_size">64dp</dimen>
+    <dimen name="swipe_edu_width">80dp</dimen>
+    <dimen name="swipe_edu_max_height">184dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index e5c9fc9..8745814 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -32,6 +32,7 @@
 
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
 
@@ -43,6 +44,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.uioverrides.states.OverviewState;
@@ -51,6 +53,7 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.RecentsView;
 
 /**
  * Touch controller for handling various state transitions in portrait UI.
@@ -129,11 +132,22 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "PortraitStatesTouchController.getTargetState");
+        }
         if (fromState == ALL_APPS && !isDragTowardPositive) {
             // Should swipe down go to OVERVIEW instead?
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
+                        "PortraitStatesTouchController.getTargetState 1");
+            }
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
         } else if (fromState == OVERVIEW) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
+                        "PortraitStatesTouchController.getTargetState 2");
+            }
             LauncherState positiveDragTarget = ALL_APPS;
             if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) {
                 // Don't allow swiping up to all apps.
@@ -141,6 +155,10 @@
             }
             return isDragTowardPositive ? positiveDragTarget : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
+                        "PortraitStatesTouchController.getTargetState 3");
+            }
             int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
             return mAllowDragToOverview && TouchInteractionService.isConnected()
                     && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0
@@ -244,8 +262,9 @@
             mCurrentAnimation = mPendingAnimation.createPlaybackController()
                     .setOnCancelRunnable(onCancelRunnable);
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
+            RecentsView recentsView = mLauncher.getOverviewPanel();
             totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
-                    mLauncher.getDeviceProfile());
+                    mLauncher.getDeviceProfile(), recentsView.getPagedOrientationHandler());
         } else {
             mCurrentAnimation = mLauncher.getStateManager()
                     .createAnimationToNewWorkspace(mToState, config)
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index f29f0ff..9124925 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -15,14 +15,24 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION;
 import static com.android.quickstep.SysUINavigationMode.getMode;
+import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
+import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 
+import android.animation.Animator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
 import android.view.MotionEvent;
@@ -34,9 +44,11 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
@@ -56,11 +68,15 @@
 public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
         ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> {
 
-    private final PointF mTempPoint = new PointF();
     public final boolean rotationSupportedByActivity;
 
-    protected BaseActivityInterface(boolean rotationSupportedByActivity) {
+    private final STATE_TYPE mOverviewState, mBackgroundState;
+
+    protected BaseActivityInterface(boolean rotationSupportedByActivity,
+            STATE_TYPE overviewState, STATE_TYPE backgroundState) {
         this.rotationSupportedByActivity = rotationSupportedByActivity;
+        mOverviewState = overviewState;
+        mBackgroundState = backgroundState;
     }
 
     public void onTransitionCancelled(boolean activityVisible) {
@@ -73,7 +89,8 @@
     }
 
     public abstract int getSwipeUpDestinationAndLength(
-            DeviceProfile dp, Context context, Rect outRect);
+            DeviceProfile dp, Context context, Rect outRect,
+            PagedOrientationHandler orientationHandler);
 
     public void onSwipeUpToRecentsComplete() {
         // Re apply state in case we did something funky during the transition.
@@ -84,7 +101,7 @@
         activity.getStateManager().reapplyState();
     }
 
-    public void onSwipeUpToHomeComplete() { }
+    public abstract void onSwipeUpToHomeComplete();
 
     public abstract void onAssistantVisibilityChanged(float visibility);
 
@@ -130,9 +147,12 @@
     public abstract boolean allowMinimizeSplitScreen();
 
     public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
-        return true;
+        return deviceState.isInDeferredGestureRegion(ev);
     }
 
+    public abstract void onExitOverview(RecentsAnimationDeviceState deviceState,
+            Runnable exitRunnable);
+
     /**
      * Updates the prediction state to the overview state.
      */
@@ -174,26 +194,24 @@
         recentsView.switchToScreenshot(thumbnailData, runnable);
     }
 
-    public void setHintUserWillBeActive() {}
-
-    /**
-     * Sets the expected window size in multi-window mode
-     */
-    public abstract void getMultiWindowSize(Context context, DeviceProfile dp, PointF out);
-
     /**
      * Calculates the taskView size for the provided device configuration
      */
-    public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) {
-        calculateTaskSize(context, dp, getExtraSpace(context, dp), outRect);
+    public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
+            PagedOrientationHandler orientedState) {
+        calculateTaskSize(context, dp, getExtraSpace(context, dp, orientedState),
+                outRect, orientedState);
     }
 
-    protected abstract float getExtraSpace(Context context, DeviceProfile dp);
+    protected abstract float getExtraSpace(Context context, DeviceProfile dp,
+            PagedOrientationHandler orientedState);
 
     private void calculateTaskSize(
-            Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect) {
+            Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect,
+            PagedOrientationHandler orientationHandler) {
         Resources res = context.getResources();
-        final boolean showLargeTaskSize = showOverviewActions(context);
+        final boolean showLargeTaskSize = showOverviewActions(context) ||
+                hideShelfInTwoButtonLandscape(context, orientationHandler);
 
         final int paddingResId;
         if (dp.isMultiWindowMode) {
@@ -251,7 +269,7 @@
     /**
      * Calculates the modal taskView size for the provided device configuration
      */
-    public void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+    public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
         float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode
                 ? R.dimen.multi_window_task_card_horz_space
                 : dp.isVerticalBarLayout()
@@ -265,7 +283,7 @@
     }
 
     /** Gets the space that the overview actions will take, including margins. */
-    public float getOverviewActionsHeight(Context context) {
+    public final float getOverviewActionsHeight(Context context) {
         Resources res = context.getResources();
         float actionsBottomMargin = 0;
         if (getMode(context) == Mode.THREE_BUTTONS) {
@@ -282,8 +300,6 @@
 
     public interface AnimationFactory {
 
-        default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
-
         void createActivityInterface(long transitionLength);
 
         default void onTransitionCancelled() { }
@@ -299,6 +315,97 @@
         default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
     }
 
+    class DefaultAnimationFactory implements AnimationFactory {
+
+        protected final ACTIVITY_TYPE mActivity;
+        private final STATE_TYPE mStartState;
+        private final Consumer<AnimatorPlaybackController> mCallback;
+
+        private boolean mIsAttachedToWindow;
+
+        DefaultAnimationFactory(Consumer<AnimatorPlaybackController> callback) {
+            mCallback = callback;
+
+            mActivity = getCreatedActivity();
+            mStartState = mActivity.getStateManager().getState();
+        }
+
+        protected ACTIVITY_TYPE initUI() {
+            STATE_TYPE resetState = mStartState;
+            if (mStartState.shouldDisableRestore()) {
+                resetState = mActivity.getStateManager().getRestState();
+            }
+            mActivity.getStateManager().setRestState(resetState);
+            mActivity.getStateManager().goToState(mBackgroundState, false);
+            return mActivity;
+        }
+
+        @Override
+        public void createActivityInterface(long transitionLength) {
+            PendingAnimation pa = new PendingAnimation(transitionLength * 2);
+            createBackgroundToOverviewAnim(mActivity, pa);
+            AnimatorPlaybackController controller = pa.createPlaybackController();
+            mActivity.getStateManager().setCurrentUserControlledAnimation(controller);
+
+            // Since we are changing the start position of the UI, reapply the state, at the end
+            controller.setEndAction(() -> mActivity.getStateManager().goToState(
+                    controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState,
+                    false));
+            mCallback.accept(controller);
+
+            // Creating the activity controller animation sometimes reapplies the launcher state
+            // (because we set the animation as the current state animation), so we reapply the
+            // attached state here as well to ensure recents is shown/hidden appropriately.
+            if (SysUINavigationMode.getMode(mActivity) == Mode.NO_BUTTON) {
+                setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
+            }
+        }
+
+        @Override
+        public void onTransitionCancelled() {
+            mActivity.getStateManager().goToState(mStartState, false /* animate */);
+        }
+
+        @Override
+        public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
+            if (mIsAttachedToWindow == attached && animate) {
+                return;
+            }
+            mIsAttachedToWindow = attached;
+            RecentsView recentsView = mActivity.getOverviewPanel();
+            Animator fadeAnim = mActivity.getStateManager()
+                    .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
+
+            float fromTranslation = attached ? 1 : 0;
+            float toTranslation = attached ? 0 : 1;
+            mActivity.getStateManager()
+                    .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
+            if (!recentsView.isShown() && animate) {
+                ADJACENT_PAGE_OFFSET.set(recentsView, fromTranslation);
+            } else {
+                fromTranslation = ADJACENT_PAGE_OFFSET.get(recentsView);
+            }
+            if (!animate) {
+                ADJACENT_PAGE_OFFSET.set(recentsView, toTranslation);
+            } else {
+                mActivity.getStateManager().createStateElementAnimation(
+                        INDEX_RECENTS_TRANSLATE_X_ANIM,
+                        fromTranslation, toTranslation).start();
+            }
+
+            fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
+            fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
+        }
+
+        protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
+            //  Scale down recents from being full screen to being in overview.
+            RecentsView recentsView = activity.getOverviewPanel();
+            pa.addFloat(recentsView, SCALE_PROPERTY,
+                    recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
+            pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
+        }
+    }
+
     protected static boolean showOverviewActions(Context context) {
         return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context);
     }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 016ffea..ffa41fd 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -24,6 +24,7 @@
 
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -278,6 +279,7 @@
     public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
         mEndTarget = target;
         mStateCallback.setState(STATE_END_TARGET_SET);
+        ActiveGestureLog.INSTANCE.addLog("setEndTarget " + mEndTarget);
         if (isAtomic) {
             mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED);
         }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index a892ddc..1da7ccf 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -45,7 +45,6 @@
 import android.os.Process;
 import android.os.UserManager;
 import android.text.TextUtils;
-import android.util.Log;
 import android.view.MotionEvent;
 
 import androidx.annotation.BinderThread;
@@ -107,7 +106,7 @@
             if (frozen) {
                 return;
             }
-            mOrientationTouchTransformer.enableMultipleRegions(false, mDefaultDisplay.getInfo());
+            enableMultipleRegions(false);
         }
     };
 
@@ -118,6 +117,13 @@
 
     private final List<ComponentName> mGestureBlockedActivities;
     private Runnable mOnDestroyFrozenTaskRunnable;
+    /**
+     * Set to true when user swipes to recents. In recents, we ignore the state of the recents
+     * task list being frozen or not to allow the user to keep interacting with nav bar rotation
+     * they went into recents with as opposed to defaulting to the default display rotation.
+     * TODO: (b/156984037) For when user rotates after entering overview
+     */
+    private boolean mInOverview;
 
     public RecentsAnimationDeviceState(Context context) {
         mContext = context;
@@ -508,7 +514,18 @@
         mOrientationTouchTransformer.transform(event);
     }
 
+    void onSwipeUpToOverview(BaseActivityInterface activityInterface) {
+        mInOverview = true;
+        activityInterface.onExitOverview(this, () -> {
+            mInOverview = false;
+            enableMultipleRegions(false);
+        });
+    }
+
     void enableMultipleRegions(boolean enable) {
+        if (mInOverview) {
+            return;
+        }
         mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
         UI_HELPER_EXECUTOR.execute(() -> {
             int quickStepStartingRotation =
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index c715c93..05ce2a2 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.MainThreadInitializedObject;
 
 import java.io.PrintWriter;
@@ -134,6 +135,12 @@
         return getMode(context) != Mode.TWO_BUTTONS;
     }
 
+    public static boolean hideShelfInTwoButtonLandscape(Context context,
+            PagedOrientationHandler pagedOrientationHandler) {
+        return  getMode(context) == Mode.TWO_BUTTONS &&
+                !pagedOrientationHandler.isLayoutNaturalToLauncher();
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("SysUINavigationMode:");
         pw.println("  mode=" + mMode.name());
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index c1b276a..3f58e01 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -25,6 +25,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.SysUINavigationMode;
 
@@ -41,11 +42,13 @@
         return swipeHeight;
     }
 
-    public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
+    public static int getShelfTrackingDistance(Context context, DeviceProfile dp,
+            PagedOrientationHandler orientationHandler) {
         // Track the bottom of the window.
         if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
             Rect taskSize = new Rect();
-            LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize);
+            LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
+                    orientationHandler);
             return (dp.heightPx - taskSize.height()) / 2;
         }
         int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 2d8bba2..7e8222c 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -15,20 +15,27 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.AbstractFloatingView.getOpenView;
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.SharedPreferences;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.views.AllAppsEduView;
 
 /**
  * Extends {@link OnboardingPrefs} for quickstep-specific onboarding data.
@@ -92,5 +99,51 @@
                 }
             });
         }
+
+        if (SysUINavigationMode.getMode(launcher) == NO_BUTTON
+                && FeatureFlags.ENABLE_ALL_APPS_EDU.get()) {
+            stateManager.addStateListener(new StateListener<LauncherState>() {
+                private static final int MAX_NUM_SWIPES_TO_TRIGGER_EDU = 3;
+
+                // Counts the number of consecutive swipes on nav bar without moving screens.
+                private int mCount = 0;
+                private boolean mShouldIncreaseCount;
+
+                @Override
+                public void onStateTransitionStart(LauncherState toState) {
+                    if (toState == NORMAL) {
+                        return;
+                    }
+                    mShouldIncreaseCount = toState == HINT_STATE
+                            && launcher.getWorkspace().getNextPage() == Workspace.DEFAULT_PAGE;
+                }
+
+                @Override
+                public void onStateTransitionComplete(LauncherState finalState) {
+                    if (finalState == NORMAL) {
+                        if (mCount == MAX_NUM_SWIPES_TO_TRIGGER_EDU) {
+                            if (getOpenView(mLauncher, TYPE_ALL_APPS_EDU) == null) {
+                                AllAppsEduView.show(launcher);
+                            }
+                            mCount = 0;
+                        }
+                        return;
+                    }
+
+                    if (mShouldIncreaseCount && finalState == HINT_STATE) {
+                        mCount++;
+                    } else {
+                        mCount = 0;
+                    }
+
+                    if (finalState == ALL_APPS) {
+                        AllAppsEduView view = getOpenView(mLauncher, TYPE_ALL_APPS_EDU);
+                        if (view != null) {
+                            view.close(false);
+                        }
+                    }
+                }
+            });
+        }
     }
 }
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index bed8278..1aa3144 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -59,6 +59,7 @@
             TYPE_DISCOVERY_BOUNCE,
             TYPE_SNACKBAR,
             TYPE_LISTENER,
+            TYPE_ALL_APPS_EDU,
 
             TYPE_TASK_MENU,
             TYPE_OPTIONS_POPUP
@@ -74,25 +75,28 @@
     public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
     public static final int TYPE_SNACKBAR = 1 << 7;
     public static final int TYPE_LISTENER = 1 << 8;
+    public static final int TYPE_ALL_APPS_EDU = 1 << 9;
 
     // Popups related to quickstep UI
-    public static final int TYPE_TASK_MENU = 1 << 9;
-    public static final int TYPE_OPTIONS_POPUP = 1 << 10;
+    public static final int TYPE_TASK_MENU = 1 << 10;
+    public static final int TYPE_OPTIONS_POPUP = 1 << 11;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
             | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
-            | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER;
+            | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
-            | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
+            | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
+            | TYPE_ALL_APPS_EDU;
 
     // Usually we show the back button when a floating view is open. Instead, hide for these types.
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER;
 
-    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER;
+    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
+            & ~TYPE_ALL_APPS_EDU;
 
     // These view all have particular operation associated with swipe down interaction.
     public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index be941f2..1c157c2 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -38,6 +38,8 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
+    private Workspace mWorkspace;
+    private boolean mSendTouchToWorkspace;
 
     public Hotseat(Context context) {
         this(context, null);
@@ -112,8 +114,35 @@
         InsettableFrameLayout.dispatchInsets(this, insets);
     }
 
+    public void setWorkspace(Workspace w) {
+        mWorkspace = w;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // We allow horizontal workspace scrolling from within the Hotseat. We do this by delegating
+        // touch intercept the Workspace, and if it intercepts, delegating touch to the Workspace
+        // for the remainder of the this input stream.
+        int yThreshold = getMeasuredHeight() - getPaddingBottom();
+        if (mWorkspace != null && ev.getY() <= yThreshold) {
+            mSendTouchToWorkspace = mWorkspace.onInterceptTouchEvent(ev);
+            return mSendTouchToWorkspace;
+        }
+        return false;
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        // See comment in #onInterceptTouchEvent
+        if (mSendTouchToWorkspace) {
+            final int action = event.getAction();
+            switch (action & MotionEvent.ACTION_MASK) {
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mSendTouchToWorkspace = false;
+            }
+            return mWorkspace.onTouchEvent(event);
+        }
         return event.getY() > getCellHeight();
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 59476dd..1f84c42 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1116,6 +1116,7 @@
         mWorkspace.initParentViews(mDragLayer);
         mOverviewPanel = findViewById(R.id.overview_panel);
         mHotseat = findViewById(R.id.hotseat);
+        mHotseat.setWorkspace(mWorkspace);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 87ead9e..535c9e6 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -153,12 +153,16 @@
         public static final int CONTAINER_PREDICTION = -102;
         public static final int CONTAINER_HOTSEAT_PREDICTION = -103;
         public static final int CONTAINER_ALL_APPS = -104;
+        public static final int CONTAINER_WIDGETS_TRAY = -105;
+
 
         public static final String containerToString(int container) {
             switch (container) {
                 case CONTAINER_DESKTOP: return "desktop";
                 case CONTAINER_HOTSEAT: return "hotseat";
                 case CONTAINER_PREDICTION: return "prediction";
+                case CONTAINER_ALL_APPS: return "all_apps";
+                case CONTAINER_WIDGETS_TRAY: return "widgets_tray";
                 default: return String.valueOf(container);
             }
         }
diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
index 29c9d93..be994ee 100644
--- a/src/com/android/launcher3/PendingAddItemInfo.java
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -18,12 +18,15 @@
 
 import android.content.ComponentName;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.model.data.ItemInfo;
 
+import java.util.Optional;
+
 /**
- * Meta data that is used for deferred binding.
- * e.g., this object is used to pass information on draggable targets when they are dropped onto
- * the workspace from another container.
+ * Meta data that is used for deferred binding. e.g., this object is used to pass information on
+ * draggable targets when they are dropped onto the workspace from another container.
  */
 public class PendingAddItemInfo extends ItemInfo {
 
@@ -36,4 +39,22 @@
     protected String dumpProperties() {
         return super.dumpProperties() + " componentName=" + componentName;
     }
+
+    /**
+     * Returns shallow copy of the object.
+     */
+    @Override
+    public ItemInfo makeShallowCopy() {
+        PendingAddItemInfo itemInfo = new PendingAddItemInfo();
+        itemInfo.copyFrom(this);
+        itemInfo.componentName = this.componentName;
+        return itemInfo;
+    }
+
+    @Nullable
+    @Override
+    public ComponentName getTargetComponent() {
+        return Optional.ofNullable(super.getTargetComponent()).orElse(componentName);
+    }
+
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 286b522..6b660c1 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2438,6 +2438,10 @@
                     // widgets/shortcuts/folders in a slightly different way
                     mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
                             item.spanX, item.spanY);
+                    mStatsLogManager.log(
+                            LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
+                            d.logInstanceId,
+                            d.dragInfo.buildProto(null));
                 }
             };
             boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
@@ -2526,11 +2530,12 @@
                 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this);
                 resetTransitionTransform();
             }
+            mStatsLogManager.log(
+                    LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
+                    d.logInstanceId,
+                    d.dragInfo.buildProto(null));
         }
-        mStatsLogManager.log(
-                LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
-                d.logInstanceId,
-                d.dragInfo.buildProto(null));
+
     }
 
     public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 6919339..869dbbc 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -164,6 +164,10 @@
             "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
             "Always use hardware optimization for folder animations.");
 
+    public static final BooleanFlag ENABLE_ALL_APPS_EDU = getDebugFlag(
+            "ENABLE_ALL_APPS_EDU", true,
+            "Shows user a tutorial on how to get to All Apps after X amount of attempts.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index f2b7e54..a97d529 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
@@ -342,6 +343,11 @@
                         .setAllAppsContainer(
                                 AllAppsContainer.getDefaultInstance())
                         .build();
+            case CONTAINER_WIDGETS_TRAY:
+                return ContainerInfo.newBuilder()
+                        .setWidgetsContainer(
+                                LauncherAtom.WidgetsContainer.getDefaultInstance())
+                        .build();
         }
         return ContainerInfo.getDefaultInstance();
     }
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 97dc052..44f7db9 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -554,6 +554,8 @@
      */
     public static class AtomicAnimationFactory<STATE_TYPE> {
 
+        protected static final int NEXT_INDEX = 0;
+
         private final Animator[] mStateElementAnimators;
 
         /**
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 52e2ab8..2e63ccf 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -201,7 +201,7 @@
         mToState = newToState;
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "reinitCurrentAnimation: "
-                    + newToState.ordinal);
+                    + newToState.ordinal + " " + getClass().getSimpleName());
         }
 
         mStartProgress = 0;
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index 1ec0690..91cf835 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -34,7 +34,7 @@
      * Returns true when we should show depp shortcuts in shortcut menu for the item.
      */
     public static boolean supportsDeepShortcuts(ItemInfo info) {
-        return isActive(info) && isApp(info);
+        return isActive(info) && isApp(info) && !WidgetsModel.GO_DISABLE_WIDGETS;
     }
 
     /**
@@ -64,7 +64,7 @@
     private static boolean isActive(ItemInfo info) {
         boolean isLoading = info instanceof WorkspaceItemInfo
                 && ((WorkspaceItemInfo) info).hasPromiseIconUi();
-        return !isLoading && !info.isDisabled() && !WidgetsModel.GO_DISABLE_WIDGETS;
+        return !isLoading && !info.isDisabled();
     }
 
     private static boolean isApp(ItemInfo info) {
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index 6e21a41..9601652 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
+
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 
@@ -32,5 +34,6 @@
         componentName = activityInfo.getComponent();
         user = activityInfo.getUser();
         itemType = activityInfo.getItemType();
+        this.container = CONTAINER_WIDGETS_TRAY;
     }
 }
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index bc40484..bef9a08 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
+
 import android.appwidget.AppWidgetHostView;
 import android.os.Bundle;
 
@@ -50,6 +52,7 @@
         spanY = i.spanY;
         minSpanX = i.minSpanX;
         minSpanY = i.minSpanY;
+        this.container = CONTAINER_WIDGETS_TRAY;
     }
 
     public WidgetAddFlowHandler getHandler() {
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 69afcc4..223ae29 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -106,21 +106,33 @@
      */
     @NonNull
     public OverviewTask getCurrentTask() {
+        final List<UiObject2> taskViews = getTasks();
+        mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
+
+        // taskViews contains up to 3 task views: the 'main' (having the widest visible part) one
+        // in the center, and parts of its right and left siblings. Find the main task view by
+        // its width.
+        final UiObject2 widestTask = Collections.max(taskViews,
+                (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(),
+                        mLauncher.getVisibleBounds(t2).width()));
+
+        return new OverviewTask(mLauncher, widestTask, this);
+    }
+
+    @NonNull
+    private List<UiObject2> getTasks() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                "want to get current task")) {
+                "want to get overview tasks")) {
             verifyActiveContainer();
-            final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
+            return mLauncher.getDevice().findObjects(
                     mLauncher.getOverviewObjectSelector("snapshot"));
-            mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
-
-            // taskViews contains up to 3 task views: the 'main' (having the widest visible
-            // part) one in the center, and parts of its right and left siblings. Find the
-            // main task view by its width.
-            final UiObject2 widestTask = Collections.max(taskViews,
-                    (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(),
-                            mLauncher.getVisibleBounds(t2).width()));
-
-            return new OverviewTask(mLauncher, widestTask, this);
         }
     }
+
+    /**
+     * Returns whether Overview has tasks.
+     */
+    public boolean hasTasks() {
+        return getTasks().size() > 0;
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 546ff0b..9c92a26 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -440,6 +440,7 @@
         try {
             Log.e("b/156287114", "Input:");
             for (String line : mDevice.executeShellCommand("dumpsys input").split("\\n")) {
+                SystemClock.sleep(10);
                 Log.d("b/156287114", line);
             }
         } catch (IOException e) {
@@ -833,27 +834,42 @@
 
     @NonNull
     List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
-        return container.findObjects(getLauncherObjectSelector(resName));
+        try {
+            return container.findObjects(getLauncherObjectSelector(resName));
+        } catch (StaleObjectException e) {
+            fail("The container disappeared from screen");
+            return null;
+        }
     }
 
     @NonNull
     UiObject2 waitForObjectInContainer(UiObject2 container, String resName) {
-        final UiObject2 object = container.wait(
-                Until.findObject(getLauncherObjectSelector(resName)),
-                WAIT_TIME_MS);
-        assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: "
-                + container.getResourceName(), object);
-        return object;
+        try {
+            final UiObject2 object = container.wait(
+                    Until.findObject(getLauncherObjectSelector(resName)),
+                    WAIT_TIME_MS);
+            assertNotNull("Can't find a view in Launcher, id: " + resName + " in container: "
+                    + container.getResourceName(), object);
+            return object;
+        } catch (StaleObjectException e) {
+            fail("The container disappeared from screen");
+            return null;
+        }
     }
 
     @NonNull
     UiObject2 waitForObjectInContainer(UiObject2 container, BySelector selector) {
-        final UiObject2 object = container.wait(
-                Until.findObject(selector),
-                WAIT_TIME_MS);
-        assertNotNull("Can't find a view in Launcher, id: " + selector + " in container: "
-                + container.getResourceName(), object);
-        return object;
+        try {
+            final UiObject2 object = container.wait(
+                    Until.findObject(selector),
+                    WAIT_TIME_MS);
+            assertNotNull("Can't find a view in Launcher, id: " + selector + " in container: "
+                    + container.getResourceName(), object);
+            return object;
+        } catch (StaleObjectException e) {
+            fail("The container disappeared from screen");
+            return null;
+        }
     }
 
     private boolean hasLauncherObject(String resId) {
@@ -1144,7 +1160,8 @@
         }
 
         final MotionEvent event = getMotionEvent(downTime, currentTime, action, point.x, point.y);
-        mInstrumentation.getUiAutomation().injectInputEvent(event, true);
+        assertTrue("injectInputEvent failed",
+                mInstrumentation.getUiAutomation().injectInputEvent(event, true));
         event.recycle();
     }
 
@@ -1322,6 +1339,7 @@
                             Log.e("b/156287114", "Input:");
                             for (String line : mDevice.executeShellCommand("dumpsys input").split(
                                     "\\n")) {
+                                SystemClock.sleep(10);
                                 Log.d("b/156287114", line);
                             }
                         } catch (IOException e) {