Merge "Only set spring end value if animation wasn't canceled" into ub-launcher3-rvc-dev
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index cac2d8f..f1b71e8 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -95,7 +95,18 @@
 
 // Represents folder in a closed state.
 message FolderIcon {
+  // Number of items inside folder.
   optional int32 cardinality = 1;
+
+  // State of the folder label before the event.
+  optional FromState from_label_state = 2;
+
+  // State of the folder label after the event.
+  optional ToState to_label_state = 3;
+
+  // Details about actual folder label.
+  // Populated when folder label is not a PII.
+  optional string label_info = 4;
 }
 
 //////////////////////////////////////////////
@@ -120,3 +131,78 @@
     HotseatContainer hotseat = 5;
   }
 }
+
+// Represents state of EditText field before update.
+enum FromState {
+  // Default value.
+  // Used when a FromState is not applicable, for example, during folder creation.
+  FROM_STATE_UNSPECIFIED = 0;
+
+  // EditText was empty.
+  // Eg: When a folder label is updated from empty string.
+  FROM_EMPTY = 1;
+
+  // EditText was non-empty and manually entered by the user.
+  // Eg: When a folder label is updated from a user-entered value.
+  FROM_CUSTOM = 2;
+
+  // EditText was non-empty and one of the suggestions.
+  // Eg: When a folder label is updated from a suggested value.
+  FROM_SUGGESTED = 3;
+}
+
+// Represents state of EditText field after update.
+enum ToState {
+  // Default value.
+  // Used when ToState is not applicable, for example, when folder label is updated to a different
+  // value when folder label suggestion feature is disabled.
+  TO_STATE_UNSPECIFIED = 0;
+
+  // User attempted to change the EditText, but was not changed.
+  UNCHANGED = 1;
+
+  // New label matches with primary(aka top) suggestion.
+  TO_SUGGESTION0 = 2;
+
+  // New value matches with second top suggestion even though the top suggestion was non-empty.
+  TO_SUGGESTION1_WITH_VALID_PRIMARY = 3;
+
+  // New value matches with second top suggestion given that top suggestion was empty.
+  TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 4;
+
+  // New value matches with third top suggestion even though the top suggestion was non-empty.
+  TO_SUGGESTION2_WITH_VALID_PRIMARY = 5;
+
+  // New value matches with third top suggestion given that top suggestion was empty.
+  TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 6;
+
+  // New value matches with 4th top suggestion even though the top suggestion was non-empty.
+  TO_SUGGESTION3_WITH_VALID_PRIMARY = 7;
+
+  // New value matches with 4th top suggestion given that top suggestion was empty.
+  TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 8;
+
+  // New value is empty even though the top suggestion was non-empty.
+  TO_EMPTY_WITH_VALID_PRIMARY = 9;
+
+  // New value is empty given that top suggestion was empty.
+  TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 10;
+
+  // New value is empty given that no suggestions were provided.
+  TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 11;
+
+  // New value is empty given that suggestions feature was disabled.
+  TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 12;
+
+  // New value is non-empty and does not match with any of the suggestions even though the top suggestion was non-empty.
+  TO_CUSTOM_WITH_VALID_PRIMARY = 13;
+
+  // New value is non-empty and not match with any suggestions given that top suggestion was empty.
+  TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 14;
+
+  // New value is non-empty and also no suggestions were provided.
+  TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 15;
+
+  // New value is non-empty and also suggestions feature was disable.
+  TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 16;
+}
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 04506b5..b2286f1 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -95,12 +95,6 @@
             android:clearTaskOnLaunch="true"
             android:exported="false" />
 
-        <activity android:name="com.android.quickstep.LockScreenRecentsActivity"
-                  android:theme="@android:style/Theme.NoDisplay"
-                  android:showOnLockScreen="true"
-                  android:taskAffinity="${packageName}.locktask"
-                  android:directBootAware="true" />
-
         <activity
             android:name="com.android.quickstep.interaction.GestureSandboxActivity"
             android:autoRemoveFromRecents="true"
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 7f8f0a0..e4d0adf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -121,7 +121,7 @@
         if (!putIntoFolder.isEmpty()) {
             ItemInfo firstItem = putIntoFolder.get(0);
             FolderInfo folderInfo = new FolderInfo();
-            folderInfo.title = "";
+            folderInfo.setTitle("");
             mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
                     firstItem.screenId, firstItem.cellX, firstItem.cellY);
             folderInfo.contents.addAll(putIntoFolder);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 7520688..6761148 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.views.DoubleShadowBubbleTextView;
 
 /**
@@ -65,6 +66,7 @@
     private final int mNormalizedIconRadius;
     private final BlurMaskFilter mShadowFilter;
     private int mPlateColor;
+    boolean mDrawForDrag = false;
 
 
     public PredictedAppIcon(Context context) {
@@ -188,6 +190,10 @@
     }
 
     private void drawEffect(Canvas canvas, boolean isBadged) {
+        // Don't draw ring effect if item is about to be dragged.
+        if (mDrawForDrag) {
+            return;
+        }
         mRingPath.reset();
         getShape().addToPath(mRingPath, getOutlineOffsetX(), getOutlineOffsetY(),
                 mNormalizedIconRadius);
@@ -208,6 +214,26 @@
         canvas.drawPath(mRingPath, mIconRingPaint);
     }
 
+    @Override
+    public void getSourceVisualDragBounds(Rect bounds) {
+        super.getSourceVisualDragBounds(bounds);
+        if (!mIsPinned) {
+            int internalSize = (int) (bounds.width() * RING_EFFECT_RATIO);
+            bounds.inset(internalSize, internalSize);
+        }
+    }
+
+    @Override
+    public SafeCloseable prepareDrawDragView() {
+        mDrawForDrag = true;
+        invalidate();
+        SafeCloseable r = super.prepareDrawDragView();
+        return () -> {
+            r.close();
+            mDrawForDrag = false;
+        };
+    }
+
     /**
      * Creates and returns a new instance of PredictedAppIcon from WorkspaceItemInfo
      */
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 357e9ec..fa0d3f3 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
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -54,11 +55,7 @@
 
     @Override
     public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        return new float[] {getOverviewScale(launcher), NO_OFFSET};
-    }
-
-    private float getOverviewScale(Launcher launcher) {
-        return ((RecentsView) launcher.getOverviewPanel()).getMaxScaleForFullScreen();
+        return getOverviewScaleAndOffsetForBackgroundState(launcher);
     }
 
     @Override
@@ -88,4 +85,11 @@
     protected float getDepthUnchecked(Context context) {
         return 1f;
     }
+
+    public static float[] getOverviewScaleAndOffsetForBackgroundState(
+            BaseDraggingActivity activity) {
+        return new float[] {
+                ((RecentsView) activity.getOverviewPanel()).getMaxScaleForFullScreen(),
+                NO_OFFSET};
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index a7e7d3a..d5b0687 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -16,11 +16,10 @@
 package com.android.launcher3.uioverrides.states;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Rect;
 
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.views.RecentsView;
 
@@ -49,22 +48,23 @@
 
     @Override
     public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        Resources res = launcher.getBaseContext().getResources();
-
-        Rect out = new Rect();
-        launcher.<RecentsView>getOverviewPanel().getTaskSize(out);
-        int taskHeight = out.height();
-
-        float topMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
-        float bottomMargin = res.getDimension(R.dimen.overview_actions_top_margin);
-        float newHeight = taskHeight + topMargin + bottomMargin;
-        float scale = newHeight / taskHeight;
-
-        return new float[] {scale, 0};
+        return getOverviewScaleAndOffsetForModalState(launcher);
     }
 
     @Override
     public float getOverviewModalness() {
         return 1.0f;
     }
+
+    public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
+        Rect out = new Rect();
+        activity.<RecentsView>getOverviewPanel().getTaskSize(out);
+        int taskHeight = out.height();
+        activity.<RecentsView>getOverviewPanel().getModalTaskSize(out);
+        int newHeight = out.height();
+
+        float scale = (float) newHeight / taskHeight;
+
+        return new float[] {scale, NO_OFFSET};
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 1f3b82c..0ee5d04 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -58,6 +58,7 @@
     private final SingleAxisSwipeDetector mDetector;
     private final RecentsView mRecentsView;
     private final int[] mTempCords = new int[2];
+    private final boolean mIsRtl;
 
     private PendingAnimation mPendingAnimation;
     private AnimatorPlaybackController mCurrentAnimation;
@@ -75,6 +76,7 @@
     public TaskViewTouchController(T activity) {
         mActivity = activity;
         mRecentsView = activity.getOverviewPanel();
+        mIsRtl = Utilities.isRtl(activity.getResources());
         SingleAxisSwipeDetector.Direction dir =
             mRecentsView.getPagedOrientationHandler().getOppositeSwipeDirection();
         mDetector = new SingleAxisSwipeDetector(activity, this, dir);
@@ -201,8 +203,8 @@
         mCurrentAnimationIsGoingUp = goingUp;
         BaseDragLayer dl = mActivity.getDragLayer();
         final int secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl);
-        long maxDuration = (long) (2 * secondaryLayerDimension);
-        int verticalFactor = -orientationHandler.getTaskDismissDirectionFactor();
+        long maxDuration = 2 * secondaryLayerDimension;
+        int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl);
         int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
         if (goingUp) {
             mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
@@ -236,7 +238,7 @@
     public void onDragStart(boolean start, float startDisplacement) {
         PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
         if (mCurrentAnimation == null) {
-            reInitAnimationController(orientationHandler.isGoingUp(startDisplacement));
+            reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, mIsRtl));
             mDisplacementShift = 0;
         } else {
             mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
@@ -250,7 +252,7 @@
         PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
         float totalDisplacement = displacement + mDisplacementShift;
         boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp :
-                orientationHandler.isGoingUp(totalDisplacement);
+                orientationHandler.isGoingUp(totalDisplacement, mIsRtl);
         if (isGoingUp != mCurrentAnimationIsGoingUp) {
             reInitAnimationController(isGoingUp);
             mFlingBlockCheck.blockFling();
@@ -282,7 +284,7 @@
         float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
         if (fling) {
             logAction = Touch.FLING;
-            boolean goingUp = orientationHandler.isGoingUp(velocity);
+            boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
             goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
         } else {
             logAction = Touch.SWIPE;
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 1dd5fb7..f38ff10 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -31,9 +31,9 @@
 import android.view.View;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.TransformParams;
@@ -47,20 +47,21 @@
  *
  * @param <T> activity that contains the overview
  */
-final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> extends
+final class AppToOverviewAnimationProvider<T extends StatefulActivity<?>> extends
         RemoteAnimationProvider {
 
     private static final long RECENTS_LAUNCH_DURATION = 250;
     private static final String TAG = "AppToOverviewAnimationProvider";
 
-    private final BaseActivityInterface<T> mActivityInterface;
+    private final BaseActivityInterface<?, T> mActivityInterface;
     // The id of the currently running task that is transitioning to overview.
     private final int mTargetTaskId;
 
     private T mActivity;
     private RecentsView mRecentsView;
 
-    AppToOverviewAnimationProvider(BaseActivityInterface<T> activityInterface, int targetTaskId) {
+    AppToOverviewAnimationProvider(
+            BaseActivityInterface<?, T> activityInterface, int targetTaskId) {
         mActivityInterface = activityInterface;
         mTargetTaskId = targetTaskId;
     }
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 76c6060..bbee67c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -44,12 +44,12 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.views.FloatingIconView;
@@ -60,7 +60,6 @@
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.TransformParams.BuilderProxy;
-import com.android.quickstep.util.WindowSizeStrategy;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -76,7 +75,7 @@
  * Base class for swipe up handler with some utility methods
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q extends RecentsView>
+public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
         implements RecentsAnimationListener {
 
     private static final String TAG = "BaseSwipeUpHandler";
@@ -97,7 +96,7 @@
     protected final Context mContext;
     protected final RecentsAnimationDeviceState mDeviceState;
     protected final GestureState mGestureState;
-    protected final BaseActivityInterface<T> mActivityInterface;
+    protected final BaseActivityInterface<?, T> mActivityInterface;
     protected final InputConsumerController mInputConsumer;
 
     protected final TaskViewSimulator mTaskViewSimulator;
@@ -132,15 +131,14 @@
     private boolean mRecentsViewScrollLinked = false;
 
     protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, InputConsumerController inputConsumer,
-            WindowSizeStrategy windowSizeStrategy) {
+            GestureState gestureState, InputConsumerController inputConsumer) {
         mContext = context;
         mDeviceState = deviceState;
         mGestureState = gestureState;
         mActivityInterface = gestureState.getActivityInterface();
         mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
         mInputConsumer = inputConsumer;
-        mTaskViewSimulator = new TaskViewSimulator(context, windowSizeStrategy);
+        mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
     }
 
     /**
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 324c390..4b3af31 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -15,23 +15,27 @@
  */
 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.FallbackRecentsView.ZOOM_PROGRESS;
-import static com.android.quickstep.util.WindowSizeStrategy.FALLBACK_RECENTS_SIZE_STRATEGY;
+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.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.graphics.PointF;
 import android.graphics.Rect;
 
 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.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;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -44,19 +48,18 @@
  * currently running one and apps should interact with the {@link RecentsActivity} as opposed
  * to the in-launcher one.
  */
-public final class FallbackActivityInterface implements
-        BaseActivityInterface<RecentsActivity> {
+public final class FallbackActivityInterface extends
+        BaseActivityInterface<RecentsState, RecentsActivity> {
 
-    public FallbackActivityInterface() { }
+    public static final FallbackActivityInterface INSTANCE = new FallbackActivityInterface();
 
-    @Override
-    public void onTransitionCancelled(boolean activityVisible) {
-        // TODO:
+    private FallbackActivityInterface() {
+        super(false);
     }
 
     @Override
     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
-        FALLBACK_RECENTS_SIZE_STRATEGY.calculateTaskSize(context, dp, outRect);
+        calculateTaskSize(context, dp, outRect);
         if (dp.isVerticalBarLayout()
                 && SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) {
             Rect targetInsets = dp.getInsets();
@@ -68,17 +71,6 @@
     }
 
     @Override
-    public void onSwipeUpToRecentsComplete() {
-        RecentsActivity activity = getCreatedActivity();
-        if (activity == null) {
-            return;
-        }
-        RecentsView recentsView = activity.getOverviewPanel();
-        recentsView.getClearAllButton().setVisibilityAlpha(1);
-        recentsView.setDisallowScrollToClearAll(false);
-    }
-
-    @Override
     public void onAssistantVisibilityChanged(float visibility) {
         // This class becomes active when the screen is locked.
         // Rather than having it handle assistant visibility changes, the assistant visibility is
@@ -89,15 +81,13 @@
     public AnimationFactory prepareRecentsUI(
             boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
         RecentsActivity activity = getCreatedActivity();
-        if (activityVisible) {
+        if (activity == null) {
             return (transitionLength) -> { };
         }
 
+        activity.getStateManager().goToState(BACKGROUND_APP);
         FallbackRecentsView rv = activity.getOverviewPanel();
         rv.setContentAlpha(0);
-        rv.getClearAllButton().setVisibilityAlpha(0);
-        rv.setDisallowScrollToClearAll(true);
-        rv.setInOverviewState(false);
 
         return new AnimationFactory() {
 
@@ -115,23 +105,19 @@
 
             @Override
             public void createActivityInterface(long transitionLength) {
-                AnimatorSet animatorSet = new AnimatorSet();
+                PendingAnimation pa = new PendingAnimation(transitionLength * 2);
+
                 if (isAnimatingToRecents) {
-                    ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
-                    anim.setDuration(transitionLength).setInterpolator(LINEAR);
-                    animatorSet.play(anim);
+                    pa.addFloat(rv, CONTENT_ALPHA, 0, 1, LINEAR);
                 }
 
-                ObjectAnimator anim = ObjectAnimator.ofFloat(rv, ZOOM_PROGRESS, 1, 0);
-                anim.setDuration(transitionLength).setInterpolator(LINEAR);
-                animatorSet.play(anim);
-
-                AnimatorPlaybackController controller =
-                        AnimatorPlaybackController.wrap(animatorSet, transitionLength);
+                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(() ->
-                        rv.setInOverviewState(controller.getInterpolatedProgress() > 0.5));
+                controller.setEndAction(() -> activity.getStateManager().goToState(
+                        controller.getInterpolatedProgress() > 0.5 ? DEFAULT : BACKGROUND_APP));
                 callback.accept(controller);
             }
         };
@@ -147,7 +133,7 @@
     @Nullable
     @Override
     public RecentsActivity getCreatedActivity() {
-        return BaseRecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
+        return RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     @Nullable
@@ -202,11 +188,14 @@
     }
 
     @Override
-    public void onLaunchTaskSuccess() {
-        RecentsActivity activity = getCreatedActivity();
-        if (activity == null) {
-            return;
-        }
-        activity.onTaskLaunched();
+    public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) {
+        out.set(dp.widthPx, dp.heightPx);
+    }
+
+    @Override
+    protected float getExtraSpace(Context context, DeviceProfile dp) {
+        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 8ce6bbc..db41bd6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -24,7 +24,6 @@
 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.util.WindowSizeStrategy.FALLBACK_RECENTS_SIZE_STRATEGY;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
@@ -37,10 +36,12 @@
 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 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.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.RectFSpringAnim;
@@ -103,10 +104,16 @@
     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, FALLBACK_RECENTS_SIZE_STRATEGY);
+        super(context, deviceState, gestureState, inputConsumer);
 
         mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
         mContinuingLastGesture = continuingLastGesture;
@@ -165,10 +172,6 @@
         mRecentsView = mActivity.getOverviewPanel();
         mRecentsView.setOnPageTransitionEndCallback(null);
         linkRecentsViewScroll();
-        mRecentsView.setDisallowScrollToClearAll(true);
-        mRecentsView.getClearAllButton().setVisibilityAlpha(0);
-        mRecentsView.setZoomProgress(1);
-
         if (!mContinuingLastGesture) {
             if (mRunningOverHome) {
                 mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
@@ -178,10 +181,49 @@
         }
         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;
     }
@@ -260,6 +302,7 @@
         }
 
         applyWindowTransform();
+        updateLauncherTransitionProgress();
     }
 
     @Override
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 9ff5d942..d466296 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -21,16 +21,21 @@
 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.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY;
+import static com.android.quickstep.SysUINavigationMode.getMode;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+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;
@@ -44,6 +49,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
@@ -60,7 +66,6 @@
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
-import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.function.Consumer;
@@ -69,11 +74,18 @@
 /**
  * {@link BaseActivityInterface} for the in-launcher recents.
  */
-public final class LauncherActivityInterface implements BaseActivityInterface<Launcher> {
+public final class LauncherActivityInterface extends
+        BaseActivityInterface<LauncherState, Launcher> {
+
+    public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface();
+
+    private LauncherActivityInterface() {
+        super(true);
+    }
 
     @Override
     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
-        LAUNCHER_ACTIVITY_SIZE_STRATEGY.calculateTaskSize(context, dp, outRect);
+        calculateTaskSize(context, dp, outRect);
         if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) {
             Rect targetInsets = dp.getInsets();
             int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
@@ -84,24 +96,12 @@
     }
 
     @Override
-    public void onTransitionCancelled(boolean activityVisible) {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        LauncherState startState = launcher.getStateManager().getRestState();
-        launcher.getStateManager().goToState(startState, activityVisible);
-    }
-
-    @Override
     public void onSwipeUpToRecentsComplete() {
-        // Re apply state in case we did something funky during the transition.
+        super.onSwipeUpToRecentsComplete();
         Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
+        if (launcher != null) {
+            DiscoveryBounce.showForOverviewIfNeeded(launcher);
         }
-        launcher.getStateManager().reapplyState();
-        DiscoveryBounce.showForOverviewIfNeeded(launcher);
     }
 
     @Override
@@ -113,15 +113,7 @@
         // Ensure recents is at the correct position for NORMAL state. For example, when we detach
         // recents, we assume the first task is invisible, making translation off by one task.
         launcher.getStateManager().reapplyState();
-        setLauncherHideBackArrow(false);
-    }
-
-    private void setLauncherHideBackArrow(boolean hideBackArrow) {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        launcher.getRootView().setForceHideBackArrow(hideBackArrow);
+        launcher.getRootView().setForceHideBackArrow(false);
     }
 
     @Override
@@ -337,15 +329,6 @@
     }
 
     @Override
-    public void onLaunchTaskSuccess() {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        launcher.getStateManager().moveToRestState();
-    }
-
-    @Override
     public void closeOverlay() {
         Launcher launcher = getCreatedActivity();
         if (launcher == null) {
@@ -360,23 +343,6 @@
     }
 
     @Override
-    public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
-            Runnable onFinishRunnable) {
-        Launcher launcher = getCreatedActivity();
-        if (launcher == null) {
-            return;
-        }
-        RecentsView recentsView = launcher.getOverviewPanel();
-        if (recentsView == null) {
-            if (onFinishRunnable != null) {
-                onFinishRunnable.run();
-            }
-            return;
-        }
-        recentsView.switchToScreenshot(thumbnailData, onFinishRunnable);
-    }
-
-    @Override
     public void setOnDeferredActivityLaunchCallback(Runnable r) {
         Launcher launcher = getCreatedActivity();
         if (launcher == null) {
@@ -404,4 +370,50 @@
         }
         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;
+        } else {
+            Resources res = context.getResources();
+            if (showOverviewActions(context)) {
+                //TODO: this needs to account for the swipe gesture height and accessibility
+                // UI when shown.
+                float actionsBottomMargin = 0;
+                if (getMode(context) == Mode.THREE_BUTTONS) {
+                    actionsBottomMargin = res.getDimensionPixelSize(
+                            R.dimen.overview_actions_bottom_margin_three_button);
+                } else {
+                    actionsBottomMargin = res.getDimensionPixelSize(
+                            R.dimen.overview_actions_bottom_margin_gesture);
+                }
+                float actionsHeight = actionsBottomMargin
+                        + res.getDimensionPixelSize(R.dimen.overview_actions_height);
+                return actionsHeight;
+            } else {
+                return getDefaultSwipeHeight(context, dp) + dp.workspacePageIndicatorHeight
+                        + res.getDimensionPixelSize(
+                        R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+                        + res.getDimensionPixelSize(
+                        R.dimen.dynamic_grid_hotseat_bottom_padding);
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index e7fe142..1830ccb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -35,7 +35,6 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
-import static com.android.quickstep.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
@@ -60,7 +59,6 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -69,6 +67,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -198,7 +197,7 @@
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
-        super(context, deviceState, gestureState, inputConsumer, LAUNCHER_ACTIVITY_SIZE_STRATEGY);
+        super(context, deviceState, gestureState, inputConsumer);
         mTaskAnimationManager = taskAnimationManager;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
@@ -684,7 +683,7 @@
             setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha);
         }
 
-        BaseDraggingActivity activity = mActivityInterface.getCreatedActivity();
+        StatefulActivity activity = mActivityInterface.getCreatedActivity();
         return activity == null ? InputConsumer.NO_OP
                 : new OverviewInputConsumer(mGestureState, activity, null, true);
     }
@@ -724,7 +723,7 @@
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return false;
         }
-        if (mGestureState.getEndTarget() == NEW_TASK
+        if (mStateCallback.hasStates(STATE_START_NEW_TASK)
                 && appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
             reset();
             return true;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
deleted file mode 100644
index 65f323c..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/**
- * Empty activity to start a recents transition
- */
-public class LockScreenRecentsActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        finish();
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index c6b719a..8846727 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -28,9 +28,10 @@
 import android.view.ViewConfiguration;
 
 import androidx.annotation.BinderThread;
-import com.android.launcher3.BaseDraggingActivity;
+
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
@@ -111,7 +112,7 @@
         protected void onTransitionComplete() {
             // TODO(b/138729100) This doesn't execute first time launcher is run
             if (mTriggeredFromAltTab) {
-                RecentsView rv = (RecentsView) mActivityInterface.getVisibleRecentsView();
+                RecentsView rv =  mActivityInterface.getVisibleRecentsView();
                 if (rv == null) {
                     return;
                 }
@@ -136,7 +137,7 @@
 
         @Override
         protected boolean handleCommand(long elapsedTime) {
-            RecentsView recents = (RecentsView) mActivityInterface.getVisibleRecentsView();
+            RecentsView recents = mActivityInterface.getVisibleRecentsView();
             if (recents == null) {
                 return false;
             }
@@ -150,9 +151,9 @@
         }
     }
 
-    private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
+    private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable {
 
-        protected final BaseActivityInterface<T> mActivityInterface;
+        protected final BaseActivityInterface<?, T> mActivityInterface;
         private final long mCreateTime;
         private final AppToOverviewAnimationProvider<T> mAnimationProvider;
 
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 86cfbdf..a4670fd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -15,6 +15,9 @@
  */
 package com.android.quickstep;
 
+import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
@@ -29,21 +32,32 @@
 import android.app.ActivityOptions;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.view.View;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAnimationRunner;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.ObjectWrapper;
+import com.android.launcher3.util.SystemUiController;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.fallback.FallbackRecentsStateController;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsRootView;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -51,26 +65,40 @@
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 /**
  * A recents activity that shows the recently launched tasks as swipable task cards.
  * See {@link com.android.quickstep.views.RecentsView}.
  */
-public final class RecentsActivity extends BaseRecentsActivity {
+public final class RecentsActivity extends StatefulActivity<RecentsState> {
 
     public static final String EXTRA_THUMBNAIL = "thumbnailData";
     public static final String EXTRA_TASK_ID = "taskID";
+    public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER =
+            new ActivityTracker<>();
 
     private Handler mUiHandler = new Handler(Looper.getMainLooper());
     private RecentsRootView mRecentsRootView;
     private FallbackRecentsView mFallbackRecentsView;
+    private OverviewActionsView mActionsView;
 
-    @Override
+    private Configuration mOldConfig;
+
+    private StateManager<RecentsState> mStateManager;
+
+    /**
+     * Init drag layer and overview panel views.
+     */
     protected void initViews() {
         setContentView(R.layout.fallback_recents_activity);
         mRecentsRootView = findViewById(R.id.drag_layer);
         mFallbackRecentsView = findViewById(R.id.overview_panel);
+        mActionsView = findViewById(R.id.overview_actions_view);
+
         mRecentsRootView.recreateControllers();
-        mFallbackRecentsView.init(findViewById(R.id.overview_actions_view));
+        mFallbackRecentsView.init(mActionsView);
     }
 
     @Override
@@ -103,25 +131,38 @@
         intent.removeExtra(EXTRA_TASK_ID);
         intent.removeExtra(EXTRA_THUMBNAIL);
         super.onNewIntent(intent);
+        ACTIVITY_TRACKER.handleNewIntent(this, intent);
     }
 
-    @Override
+    /**
+         * Logic for when device configuration changes (rotation, screen size change, multi-window,
+         * etc.)
+         */
     protected void onHandleConfigChanged() {
-        super.onHandleConfigChanged();
+        mUserEventDispatcher = null;
+        initDeviceProfile();
+
+        AbstractFloatingView.closeOpenViews(this, true,
+                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+        dispatchDeviceProfileChanged();
+
+        reapplyUi();
         mRecentsRootView.recreateControllers();
     }
 
-    @Override
-    protected void reapplyUi() {
-        mRecentsRootView.dispatchInsets();
-    }
-
-    @Override
+    /**
+     * Generate the device profile to use in this activity.
+     * @return device profile
+     */
     protected DeviceProfile createDeviceProfile() {
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
+        DeviceProfile dp1 = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
+
+        // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
+        // activity.
         return (mRecentsRootView != null) && isInMultiWindowMode()
                 ? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize())
-                : super.createDeviceProfile();
+                : dp1.copy(this);
     }
 
     @Override
@@ -139,6 +180,10 @@
         return (T) mFallbackRecentsView;
     }
 
+    public OverviewActionsView getActionsView() {
+        return mActionsView;
+    }
+
     @Override
     public void returnToHomescreen() {
         super.returnToHomescreen();
@@ -160,12 +205,7 @@
                     RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
                 AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
                         wallpaperTargets);
-                anim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mFallbackRecentsView.resetViewUI();
-                    }
-                });
+                anim.addListener(resetStateListener());
                 result.setAnimation(anim, RecentsActivity.this);
             }
         };
@@ -193,12 +233,7 @@
                     .createAdjacentPageAnimForTaskLaunch(taskView);
             adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
             adjacentAnimation.setDuration(RECENTS_LAUNCH_DURATION);
-            adjacentAnimation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mFallbackRecentsView.resetTaskVisuals();
-                }
-            });
+            adjacentAnimation.addListener(resetStateListener());
             target.play(adjacentAnimation);
         }
         return target;
@@ -210,13 +245,14 @@
         // onActivityStart callback.
         mFallbackRecentsView.setContentAlpha(1);
         super.onStart();
-        mFallbackRecentsView.resetTaskVisuals();
     }
 
     @Override
     protected void onStop() {
         super.onStop();
-        mFallbackRecentsView.reset();
+
+        // Workaround for b/78520668, explicitly trim memory once UI is hidden
+        onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
     }
 
     @Override
@@ -225,7 +261,97 @@
         AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL);
     }
 
-    public void onTaskLaunched() {
-        mFallbackRecentsView.resetTaskVisuals();
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mStateManager = new StateManager<>(this, RecentsState.DEFAULT);
+
+        mOldConfig = new Configuration(getResources().getConfiguration());
+        initDeviceProfile();
+        initViews();
+
+        getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
+                Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
+        ACTIVITY_TRACKER.handleCreate(this);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        int diff = newConfig.diff(mOldConfig);
+        if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
+            onHandleConfigChanged();
+        }
+        mOldConfig.setTo(newConfig);
+        super.onConfigurationChanged(newConfig);
+    }
+
+    /**
+     * Initialize/update the device profile.
+     */
+    private void initDeviceProfile() {
+        mDeviceProfile = createDeviceProfile();
+        onDeviceProfileInitiated();
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
+        // as a part of quickstep, so that high-res thumbnails can load the next time we enter
+        // overview
+        RecentsModel.INSTANCE.get(this).getThumbnailCache()
+                .getHighResLoadingState().setVisible(true);
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        super.onTrimMemory(level);
+        RecentsModel.INSTANCE.get(this).onTrimMemory(level);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        ACTIVITY_TRACKER.onActivityDestroyed(this);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // TODO: Launch the task we came from
+        startHome();
+    }
+
+    public void startHome() {
+        startActivity(new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    @Override
+    protected StateHandler<RecentsState>[] createStateHandlers() {
+        return new StateHandler[] { new FallbackRecentsStateController(this) };
+    }
+
+    @Override
+    public StateManager<RecentsState> getStateManager() {
+        return mStateManager;
+    }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        super.dump(prefix, fd, writer, args);
+        writer.println(prefix + "Misc:");
+        dumpMisc(prefix + "\t", writer);
+    }
+
+    private AnimatorListenerAdapter resetStateListener() {
+        return new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mFallbackRecentsView.resetTaskVisuals();
+                mStateManager.reapplyState();
+            }
+        };
     }
 }
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 4b2fc75..acc7794 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -38,6 +38,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.Icon;
 import android.os.Build;
@@ -62,6 +63,7 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.nano.LauncherTraceProto;
@@ -69,6 +71,7 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
@@ -81,6 +84,7 @@
 import com.android.quickstep.util.ActiveGestureLog;
 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;
@@ -118,7 +122,7 @@
 /**
  * Service connected by system-UI for handling touch interaction.
  */
-@TargetApi(Build.VERSION_CODES.Q)
+@TargetApi(Build.VERSION_CODES.R)
 public class TouchInteractionService extends Service implements PluginListener<OverscrollPlugin>,
         ProtoTraceable<LauncherTraceProto> {
 
@@ -229,6 +233,11 @@
             MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
         }
 
+        public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets)  {
+            WindowBounds wb = new WindowBounds(bounds, insets);
+            MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
+        }
+
         /** Deprecated methods **/
         public void onQuickStep(MotionEvent motionEvent) { }
 
@@ -697,7 +706,7 @@
     public InputConsumer createOverviewInputConsumer(GestureState previousGestureState,
             GestureState gestureState, MotionEvent event,
             boolean forceOverviewInputConsumer) {
-        BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity();
+        StatefulActivity activity = gestureState.getActivityInterface().getCreatedActivity();
         if (activity == null) {
             return mResetGestureInputConsumer;
         }
@@ -746,7 +755,7 @@
             return;
         }
 
-        final BaseActivityInterface<BaseDraggingActivity> activityInterface =
+        final BaseActivityInterface activityInterface =
                 mOverviewComponentObserver.getActivityInterface();
         final Intent overviewIntent = new Intent(
                 mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
new file mode 100644
index 0000000..3f1e7ba
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -0,0 +1,94 @@
+/*
+ * 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.fallback;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
+
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.views.ClearAllButton;
+
+/**
+ * State controller for fallback recents activity
+ */
+public class FallbackRecentsStateController implements StateHandler<RecentsState> {
+
+    private final StateAnimationConfig mNoConfig = new StateAnimationConfig();
+    private final RecentsActivity mActivity;
+    private final FallbackRecentsView mRecentsView;
+
+    public FallbackRecentsStateController(RecentsActivity activity) {
+        mActivity = activity;
+        mRecentsView = activity.getOverviewPanel();
+    }
+
+    @Override
+    public void setState(RecentsState state) {
+        mRecentsView.updateEmptyMessage();
+        mRecentsView.resetTaskVisuals();
+        setProperties(state, mNoConfig, PropertySetter.NO_ANIM_PROPERTY_SETTER);
+    }
+
+    @Override
+    public void setStateWithAnimation(RecentsState toState, StateAnimationConfig config,
+            PendingAnimation setter) {
+        if (!config.hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_PEEK | PLAY_ATOMIC_OVERVIEW_SCALE)) {
+            // The entire recents animation is played atomically.
+            return;
+        }
+        if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
+            return;
+        }
+        // While animating into recents, update the visible task data as needed
+        setter.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
+        mRecentsView.updateEmptyMessage();
+
+        setProperties(toState, config, setter);
+    }
+
+    private void setProperties(RecentsState state, StateAnimationConfig config,
+            PropertySetter setter) {
+        float buttonAlpha = state.hasButtons() ? 1 : 0;
+        setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
+                buttonAlpha, LINEAR);
+        setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
+                MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+
+        float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
+        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
+                config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
+        setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
+
+        setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
+                config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
+        setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
+    }
+}
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 559004c..f958e6d 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
@@ -15,17 +15,17 @@
  */
 package com.android.quickstep.fallback;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.quickstep.util.WindowSizeStrategy.FALLBACK_RECENTS_SIZE_STRATEGY;
+import static com.android.quickstep.fallback.RecentsState.DEFAULT;
+import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
 
+import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
-import android.graphics.Canvas;
+import android.os.Build;
 import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.view.View;
 
-import com.android.launcher3.Utilities;
+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;
@@ -34,26 +34,9 @@
 
 import java.util.ArrayList;
 
-public class FallbackRecentsView extends RecentsView<RecentsActivity> {
-
-    public static final FloatProperty<FallbackRecentsView> ZOOM_PROGRESS =
-            new FloatProperty<FallbackRecentsView> ("zoomInProgress") {
-
-                @Override
-                public void setValue(FallbackRecentsView view, float value) {
-                    view.setZoomProgress(value);
-                }
-
-                @Override
-                public Float get(FallbackRecentsView view) {
-                    return view.mZoomInProgress;
-                }
-            };
-
-    private float mZoomInProgress = 0;
-    private boolean mInOverviewState = true;
-
-    private float mZoomScale = 1f;
+@TargetApi(Build.VERSION_CODES.R)
+public class FallbackRecentsView extends RecentsView<RecentsActivity>
+        implements StateListener<RecentsState> {
 
     private RunningTaskInfo mRunningTaskInfo;
 
@@ -62,7 +45,8 @@
     }
 
     public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, FALLBACK_RECENTS_SIZE_STRATEGY);
+        super(context, attrs, defStyleAttr, FallbackActivityInterface.INSTANCE);
+        mActivity.getStateManager().addStateListener(this);
     }
 
     @Override
@@ -78,70 +62,11 @@
     }
 
     @Override
-    public void onViewAdded(View child) {
-        super.onViewAdded(child);
-        updateEmptyMessage();
-    }
-
-    @Override
-    public void onViewRemoved(View child) {
-        super.onViewRemoved(child);
-        updateEmptyMessage();
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        maybeDrawEmptyMessage(canvas);
-        super.draw(canvas);
-    }
-
-    @Override
-    public void reset() {
-        super.reset();
-        resetViewUI();
-    }
-
-    @Override
     public boolean shouldUseMultiWindowTaskSizeStrategy() {
         // Just use the activity task size for multi-window as well.
         return false;
     }
 
-    public void resetViewUI() {
-        setZoomProgress(0);
-        resetTaskVisuals();
-    }
-
-    public void setInOverviewState(boolean inOverviewState) {
-        if (mInOverviewState != inOverviewState) {
-            mInOverviewState = inOverviewState;
-            if (mInOverviewState) {
-                resetTaskVisuals();
-            } else {
-                setZoomProgress(1);
-            }
-        }
-    }
-
-    @Override
-    public void resetTaskVisuals() {
-        super.resetTaskVisuals();
-        setFullscreenProgress(mFullscreenProgress);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        mZoomScale = getMaxScaleForFullScreen();
-        setZoomProgress(mZoomInProgress);
-    }
-
-    public void setZoomProgress(float progress) {
-        mZoomInProgress = progress;
-        SCALE_PROPERTY.set(this, Utilities.mapRange(mZoomInProgress, 1, mZoomScale));
-        FULLSCREEN_PROGRESS.set(this, mZoomInProgress);
-    }
-
     public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
         mRunningTaskInfo = runningTaskInfo;
         onGestureAnimationStart(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
@@ -178,4 +103,37 @@
         }
         super.applyLoadPlan(tasks);
     }
+
+    @Override
+    public void setModalStateEnabled(boolean isModalState) {
+        super.setModalStateEnabled(isModalState);
+        if (isModalState) {
+            mActivity.getStateManager().goToState(RecentsState.MODAL_TASK);
+        } else {
+            if (mActivity.isInState(RecentsState.MODAL_TASK)) {
+                mActivity.getStateManager().goToState(DEFAULT);
+            }
+        }
+    }
+
+    @Override
+    public void onStateTransitionStart(RecentsState toState) {
+        setOverviewStateEnabled(true);
+        setFreezeViewVisibility(true);
+    }
+
+    @Override
+    public void onStateTransitionComplete(RecentsState finalState) {
+        setOverlayEnabled(finalState == DEFAULT || finalState == MODAL_TASK);
+        setFreezeViewVisibility(false);
+    }
+
+    @Override
+    public void setOverviewStateEnabled(boolean enabled) {
+        super.setOverviewStateEnabled(enabled);
+        if (enabled) {
+            RecentsState state = mActivity.getStateManager().getState();
+            setDisallowScrollToClearAll(!state.hasButtons());
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java
new file mode 100644
index 0000000..211a30c
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java
@@ -0,0 +1,116 @@
+/*
+ * 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.fallback;
+
+import static com.android.launcher3.uioverrides.states.BackgroundAppState.getOverviewScaleAndOffsetForBackgroundState;
+import static com.android.launcher3.uioverrides.states.OverviewModalTaskState.getOverviewScaleAndOffsetForModalState;
+
+import android.content.Context;
+
+import com.android.launcher3.statemanager.BaseState;
+import com.android.quickstep.RecentsActivity;
+
+/**
+ * State definition for Fallback recents
+ */
+public class RecentsState implements BaseState<RecentsState> {
+
+    private static final int FLAG_MODAL = BaseState.getFlag(0);
+    private static final int FLAG_HAS_BUTTONS = BaseState.getFlag(1);
+    private static final int FLAG_FULL_SCREEN = BaseState.getFlag(2);
+
+    public static final RecentsState DEFAULT = new RecentsState(0, FLAG_HAS_BUTTONS);
+    public static final RecentsState MODAL_TASK = new ModalState(1,
+            FLAG_DISABLE_RESTORE | FLAG_HAS_BUTTONS | FLAG_MODAL);
+    public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
+            FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN);
+
+    public final int ordinal;
+    private final int mFlags;
+
+    private static final float NO_OFFSET = 0;
+    private static final float NO_SCALE = 1;
+
+    public RecentsState(int id, int flags) {
+        this.ordinal = id;
+        this.mFlags = flags;
+    }
+
+
+    @Override
+    public String toString() {
+        return "Ordinal-" + ordinal;
+    }
+
+    @Override
+    public final boolean hasFlag(int mask) {
+        return (mFlags & mask) != 0;
+    }
+
+    @Override
+    public int getTransitionDuration(Context context) {
+        return 250;
+    }
+
+    @Override
+    public RecentsState getHistoryForState(RecentsState previousState) {
+        return DEFAULT;
+    }
+
+    /**
+     * For this state, how modal should over view been shown. 0 modalness means all tasks drawn,
+     * 1 modalness means the current task is show on its own.
+     */
+    public float getOverviewModalness() {
+        return hasFlag(FLAG_MODAL) ? 1 : 0;
+    }
+
+    public boolean isFullScreen() {
+        return hasFlag(FLAG_FULL_SCREEN);
+    }
+
+    public boolean hasButtons() {
+        return hasFlag(FLAG_HAS_BUTTONS);
+    }
+
+    public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
+        return new float[] { NO_SCALE, NO_OFFSET };
+    }
+
+
+    private static class ModalState extends RecentsState {
+
+        public ModalState(int id, int flags) {
+            super(id, flags);
+        }
+
+        @Override
+        public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
+            return getOverviewScaleAndOffsetForModalState(activity);
+        }
+    }
+
+    private static class BackgroundAppState extends RecentsState {
+        public BackgroundAppState(int id, int flags) {
+            super(id, flags);
+        }
+
+        @Override
+        public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
+            return getOverviewScaleAndOffsetForBackgroundState(activity);
+        }
+    }
+}
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 adf19df..2dc7f5f 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
@@ -18,14 +18,15 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
-
 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.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
-import android.content.ComponentName;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Point;
@@ -34,15 +35,14 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
-
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
-import com.android.quickstep.LockScreenRecentsActivity;
 import com.android.quickstep.MultiStateCallback;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationController;
@@ -52,6 +52,7 @@
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -61,8 +62,6 @@
 public class DeviceLockedInputConsumer implements InputConsumer,
         RecentsAnimationCallbacks.RecentsAnimationListener {
 
-    private static final float SCALE_DOWN = 0.75f;
-
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null;
     private static int getFlagForIndex(int index, String name) {
         if (DEBUG_STATES) {
@@ -93,6 +92,7 @@
     private float mProgress;
 
     private boolean mThresholdCrossed = false;
+    private boolean mHomeLaunched = false;
 
     private RecentsAnimationController mRecentsAnimationController;
     private RecentsAnimationTargets mRecentsAnimationTargets;
@@ -176,7 +176,6 @@
      * the animation can still be running.
      */
     private void finishTouchTracking(MotionEvent ev) {
-        mStateCallback.setState(STATE_HANDLER_INVALIDATED);
         if (mThresholdCrossed && ev.getAction() == ACTION_UP) {
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
@@ -192,12 +191,34 @@
             } else {
                 dismissTask = mProgress >= (1 - MIN_PROGRESS_FOR_OVERVIEW);
             }
-            if (dismissTask) {
-                // For now, just start the home intent so user is prompted to unlock the device.
-                mContext.startActivity(new Intent(Intent.ACTION_MAIN)
-                        .addCategory(Intent.CATEGORY_HOME)
-                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-            }
+
+            // Animate back to fullscreen before finishing
+            ValueAnimator animator = ValueAnimator.ofFloat(mTransformParams.getProgress(), 0f);
+            animator.setDuration(100);
+            animator.setInterpolator(Interpolators.ACCEL);
+            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                    mTransformParams.setProgress((float) valueAnimator.getAnimatedValue());
+                    mAppWindowAnimationHelper.applyTransform(mTransformParams);
+                }
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (dismissTask) {
+                        // For now, just start the home intent so user is prompted to unlock the device.
+                        mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+                                .addCategory(Intent.CATEGORY_HOME)
+                                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+                        mHomeLaunched = true;
+                    }
+                    mStateCallback.setState(STATE_HANDLER_INVALIDATED);
+                }
+            });
+            animator.start();
+        } else {
+            mStateCallback.setState(STATE_HANDLER_INVALIDATED);
         }
         mVelocityTracker.recycle();
         mVelocityTracker = null;
@@ -205,13 +226,11 @@
 
     private void startRecentsTransition() {
         mThresholdCrossed = true;
+        mHomeLaunched = false;
         TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
-        Intent intent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_DEFAULT)
-                .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+        Intent intent = mGestureState.getHomeIntent()
                 .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
         mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this);
     }
@@ -229,8 +248,9 @@
             mAppWindowAnimationHelper.updateSource(displaySize, targetCompat);
         }
 
-        Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN);
-        displaySize.offsetTo(displaySize.left, 0);
+        // Offset the surface slightly
+        displaySize.offset(0, mContext.getResources().getDimensionPixelSize(
+                R.dimen.device_locked_y_offset));
         mTransformParams.setTargetSet(mRecentsAnimationTargets);
         mAppWindowAnimationHelper.updateTargetRect(displaySize);
         mAppWindowAnimationHelper.applyTransform(mTransformParams);
@@ -245,7 +265,9 @@
     }
 
     private void endRemoteAnimation() {
-        if (mRecentsAnimationController != null) {
+        if (mHomeLaunched) {
+            ActivityManagerWrapper.getInstance().cancelRecentsAnimation(false);
+        } else if (mRecentsAnimationController != null) {
             mRecentsAnimationController.finishController(
                     false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index c82d4b5..11fee2f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -24,8 +24,8 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
@@ -41,11 +41,11 @@
 /**
  * Input consumer for handling touch on the recents/Launcher activity.
  */
-public class OverviewInputConsumer<T extends BaseDraggingActivity>
+public class OverviewInputConsumer<T extends StatefulActivity<?>>
         implements InputConsumer {
 
     private final T mActivity;
-    private final BaseActivityInterface<T> mActivityInterface;
+    private final BaseActivityInterface<?, T> mActivityInterface;
     private final BaseDragLayer mTarget;
     private final InputMonitorCompat mInputMonitor;
 
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 b8f0f4d..a3db940 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
@@ -15,8 +15,6 @@
  */
 package com.android.quickstep.util;
 
-import static android.view.Surface.ROTATION_0;
-
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
@@ -33,6 +31,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.views.RecentsView.ScrollState;
 import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper;
 import com.android.quickstep.views.TaskView;
@@ -52,7 +51,7 @@
 
     private final RecentsOrientedState mOrientationState;
     private final Context mContext;
-    private final WindowSizeStrategy mSizeStrategy;
+    private final BaseActivityInterface mSizeStrategy;
 
     private final Rect mTaskRect = new Rect();
     private final PointF mPivot = new PointF();
@@ -81,7 +80,7 @@
     private boolean mLayoutValid = false;
     private boolean mScrollValid = false;
 
-    public TaskViewSimulator(Context context, WindowSizeStrategy sizeStrategy) {
+    public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
         mContext = context;
         mSizeStrategy = sizeStrategy;
 
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 1018211..2c85618 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
@@ -64,7 +64,13 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mParent = (RecentsView) getParent();
-        mIsRtl = !mParent.getPagedOrientationHandler().getRecentsRtlSetting(getResources());
+        mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+        mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 9d7efc4..3d89403 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -25,17 +25,14 @@
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.quickstep.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY;
 
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.graphics.Canvas;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
-import android.view.View;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.BaseQuickstepLauncher;
@@ -49,6 +46,7 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.ScrimView;
+import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.plugins.PluginListener;
@@ -91,7 +89,7 @@
     }
 
     public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, LAUNCHER_ACTIVITY_SIZE_STRATEGY);
+        super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE);
         mActivity.getStateManager().addStateListener(this);
     }
 
@@ -123,24 +121,6 @@
         }
     }
 
-    @Override
-    public void draw(Canvas canvas) {
-        maybeDrawEmptyMessage(canvas);
-        super.draw(canvas);
-    }
-
-    @Override
-    public void onViewAdded(View child) {
-        super.onViewAdded(child);
-        updateEmptyMessage();
-    }
-
-    @Override
-    protected void onTaskStackUpdated() {
-        // Lazily update the empty message only when the task stack is reapplied
-        updateEmptyMessage();
-    }
-
     /**
      * Animates adjacent tasks and translate hotseat off screen as well.
      */
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index 7201b02..f06a6a4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -145,8 +145,6 @@
 
     /** Updates vertical margins for different navigation mode. */
     public void updateVerticalMarginForNavModeChange(Mode mode) {
-        int topMargin = getResources()
-                .getDimensionPixelSize(R.dimen.overview_actions_top_margin);
         int bottomMargin = 0;
         if (mode == Mode.THREE_BUTTONS) {
             bottomMargin = getResources()
@@ -157,6 +155,6 @@
         }
         LayoutParams params = (LayoutParams) getLayoutParams();
         params.setMargins(
-                params.leftMargin, topMargin, params.rightMargin, bottomMargin);
+                params.leftMargin, params.topMargin, params.rightMargin, bottomMargin);
     }
 }
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 700af97..253e83c 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
@@ -115,6 +115,7 @@
 import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
+import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
@@ -126,8 +127,8 @@
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.util.WindowSizeStrategy;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.model.Task;
@@ -147,7 +148,8 @@
 @TargetApi(Build.VERSION_CODES.P)
 public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable,
         TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
-        InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener {
+        InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
+        SplitScreenBounds.OnChangeListener {
 
     private static final String TAG = RecentsView.class.getSimpleName();
 
@@ -207,7 +209,7 @@
             };
 
     protected final RecentsOrientedState mOrientationState;
-    protected final WindowSizeStrategy mSizeStrategy;
+    protected final BaseActivityInterface mSizeStrategy;
     protected RecentsAnimationController mRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
     protected AppWindowAnimationHelper mAppWindowAnimationHelper;
@@ -379,7 +381,7 @@
     };
 
     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr,
-            WindowSizeStrategy sizeStrategy) {
+            BaseActivityInterface sizeStrategy) {
         super(context, attrs, defStyleAttr);
         setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
         setEnableFreeScroll(true);
@@ -510,6 +512,7 @@
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
                 mIPinnedStackAnimationListener);
         mOrientationState.initListeners();
+        SplitScreenBounds.INSTANCE.addOnChangeListener(this);
     }
 
     @Override
@@ -523,6 +526,7 @@
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
         mIdp.removeOnChangeListener(this);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
+        SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
         mIPinnedStackAnimationListener.setActivity(null);
         mOrientationState.destroyListeners();
     }
@@ -550,6 +554,13 @@
         child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL);
         updateTaskStartIndex(child);
         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false);
+        updateEmptyMessage();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        maybeDrawEmptyMessage(canvas);
+        super.draw(canvas);
     }
 
     private void updateTaskStartIndex(View affectingView) {
@@ -762,7 +773,10 @@
         return taskViewCount;
     }
 
-    protected void onTaskStackUpdated() { }
+    protected void onTaskStackUpdated() {
+        // Lazily update the empty message only when the task stack is reapplied
+        updateEmptyMessage();
+    }
 
     public void resetTaskVisuals() {
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
@@ -829,6 +843,11 @@
         mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
     }
 
+    /** Gets the task size for modal state. */
+    public void getModalTaskSize(Rect outRect) {
+        mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect);
+    }
+
     @Override
     protected boolean computeScrollHelper() {
         boolean scrolling = super.computeScrollHelper();
@@ -1589,7 +1608,12 @@
         if (mOrientationState.update(touchRotation, displayRotation)) {
             mOrientationHandler = mOrientationState.getOrientationHandler();
             mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
-            setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+            setLayoutDirection(mIsRtl
+                    ? View.LAYOUT_DIRECTION_RTL
+                    : View.LAYOUT_DIRECTION_LTR);
+            mClearAllButton.setLayoutDirection(mIsRtl
+                    ? View.LAYOUT_DIRECTION_LTR
+                    : View.LAYOUT_DIRECTION_RTL);
             mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
             mActivity.getDragLayer().recreateControllers();
             mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
@@ -2145,18 +2169,6 @@
         updatePageOffsets();
         if (getCurrentPageTaskView() != null) {
             getCurrentPageTaskView().setModalness(modalness);
-            TaskView tv = getCurrentPageTaskView();
-
-            // Move the task view up as it scales...
-            // ...the icon on taskview is hidden in modal state, so consider the top of the task
-            mTempFloatPoint[0] = 0;
-            mTempFloatPoint[1] = tv.getTop() + mTaskTopMargin;
-            // ...find the top after the transformation
-            getMatrix().mapPoints(mTempFloatPoint);
-
-            // ...make it match the top inset
-            float calcOffset = (mInsets.top - mTempFloatPoint[1]) * mTaskModalness;
-            tv.setTranslationY(calcOffset);
         }
     }
 
@@ -2165,6 +2177,13 @@
         return null;
     }
 
+    @Override
+    public void onSecondaryWindowBoundsChanged() {
+        // Invalidate the task view size
+        setInsets(mInsets);
+        requestLayout();
+    }
+
     /**
      * Enables or disables modal state for RecentsView
      * @param isModalState
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index da9468e..6b759ba 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -449,13 +449,13 @@
 
     public void setOrientationState(RecentsOrientedState orientationState) {
         PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
-        boolean isRtl = orientationHandler.getRecentsRtlSetting(getResources());
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
         int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
         switch (orientationHandler.getRotation()) {
             case Surface.ROTATION_90:
-                iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
+                iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
                 iconParams.rightMargin = -thumbnailPadding;
                 iconParams.leftMargin = 0;
                 iconParams.topMargin = snapshotParams.topMargin / 2;
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 5c30651..ee55e61 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -24,7 +24,6 @@
 
     <!-- Overrideable in overlay that provides the Overview Actions. -->
     <dimen name="overview_actions_height">66dp</dimen>
-    <dimen name="overview_actions_top_margin">44dp</dimen>
     <dimen name="overview_actions_bottom_margin_gesture">16dp</dimen>
     <dimen name="overview_actions_bottom_margin_three_button">8dp</dimen>
     <dimen name="overview_actions_horizontal_margin">16dp</dimen>
@@ -63,7 +62,8 @@
     <dimen name="task_card_menu_shadow_height">3dp</dimen>
     <dimen name="task_card_menu_horizontal_padding">0dp</dimen>
     <dimen name="portrait_task_card_horz_space">136dp</dimen>
-    <dimen name="portrait_task_card_horz_space_big_overview">24dp</dimen>
+    <dimen name="portrait_task_card_horz_space_big_overview">96dp</dimen>
+    <dimen name="portrait_modal_task_card_horz_space">60dp</dimen>
     <dimen name="landscape_task_card_horz_space">200dp</dimen>
     <dimen name="multi_window_task_card_horz_space">100dp</dimen>
     <!-- Copied from framework resource:
@@ -79,6 +79,9 @@
     <!-- Distance to move elements when swiping up to go home from launcher -->
     <dimen name="home_pullback_distance">28dp</dimen>
 
+    <!-- Distance to move the tasks when swiping up while the device is locked -->
+    <dimen name="device_locked_y_offset">-80dp</dimen>
+
     <!-- Overscroll Gesture -->
     <dimen name="gestures_overscroll_fling_threshold">40dp</dimen>
 
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 8368817..c841170 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -129,12 +129,12 @@
     <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
     <string name="home_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up</string>
 
-  <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
-  <string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
-    <!-- Button text shown on a button on the confirm screen. [CHAR LIMIT=14] -->
-    <string name="gesture_tutorial_action_button_label" translatable="false">Done</string>
-    <!-- Button text shown on a text button on the confirm screen. [CHAR LIMIT=14] -->
-    <string name="gesture_tutorial_action_text_button_label" translatable="false">Settings</string>
+    <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
+    <string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
+    <!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_done" translatable="false">Done</string>
+    <!-- Button text shown on a button to go to Settings. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_settings" translatable="false">Settings</string>
 
     <!-- ******* Overview ******* -->
     <!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 43328b6..2699a91 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -15,23 +15,34 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.getMode;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
 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;
-import android.view.View;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.util.WindowBounds;
+import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.SplitScreenBounds;
+import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -42,84 +53,232 @@
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
  */
 @TargetApi(Build.VERSION_CODES.P)
-public interface BaseActivityInterface<T extends BaseDraggingActivity> {
+public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
+        ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> {
 
-    void onTransitionCancelled(boolean activityVisible);
+    private final PointF mTempPoint = new PointF();
+    public final boolean rotationSupportedByActivity;
 
-    int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
+    protected BaseActivityInterface(boolean rotationSupportedByActivity) {
+        this.rotationSupportedByActivity = rotationSupportedByActivity;
+    }
 
-    void onSwipeUpToRecentsComplete();
+    public void onTransitionCancelled(boolean activityVisible) {
+        ACTIVITY_TYPE activity = getCreatedActivity();
+        if (activity == null) {
+            return;
+        }
+        STATE_TYPE startState = activity.getStateManager().getRestState();
+        activity.getStateManager().goToState(startState, activityVisible);
+    }
 
-    default void onSwipeUpToHomeComplete() { }
-    void onAssistantVisibilityChanged(float visibility);
+    public abstract int getSwipeUpDestinationAndLength(
+            DeviceProfile dp, Context context, Rect outRect);
 
-    AnimationFactory prepareRecentsUI(
+    public void onSwipeUpToRecentsComplete() {
+        // Re apply state in case we did something funky during the transition.
+        ACTIVITY_TYPE activity = getCreatedActivity();
+        if (activity == null) {
+            return;
+        }
+        activity.getStateManager().reapplyState();
+    }
+
+    public void onSwipeUpToHomeComplete() { }
+
+    public abstract void onAssistantVisibilityChanged(float visibility);
+
+    public abstract AnimationFactory prepareRecentsUI(
             boolean activityVisible, Consumer<AnimatorPlaybackController> callback);
 
-    ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener);
+    public abstract ActivityInitListener createActivityInitListener(
+            Predicate<Boolean> onInitListener);
 
     /**
      * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
      */
-    default void setOnDeferredActivityLaunchCallback(Runnable r) {}
+    public void setOnDeferredActivityLaunchCallback(Runnable r) {}
 
     @Nullable
-    T getCreatedActivity();
+    public abstract ACTIVITY_TYPE getCreatedActivity();
 
     @Nullable
-    default DepthController getDepthController() {
+    public DepthController getDepthController() {
         return null;
     }
 
-    default boolean isResumed() {
-        BaseDraggingActivity activity = getCreatedActivity();
+    public final boolean isResumed() {
+        ACTIVITY_TYPE activity = getCreatedActivity();
         return activity != null && activity.hasBeenResumed();
     }
 
-    default boolean isStarted() {
-        BaseDraggingActivity activity = getCreatedActivity();
+    public final boolean isStarted() {
+        ACTIVITY_TYPE activity = getCreatedActivity();
         return activity != null && activity.isStarted();
     }
 
     @UiThread
     @Nullable
-    <T extends View> T getVisibleRecentsView();
+    public abstract <T extends RecentsView> T getVisibleRecentsView();
 
     @UiThread
-    boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
+    public abstract boolean switchToRecentsIfVisible(Runnable onCompleteCallback);
 
-    Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
+    public abstract Rect getOverviewWindowBounds(
+            Rect homeBounds, RemoteAnimationTargetCompat target);
 
-    boolean allowMinimizeSplitScreen();
+    public abstract boolean allowMinimizeSplitScreen();
 
-    default boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+    public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
         return true;
     }
 
     /**
      * Updates the prediction state to the overview state.
      */
-    default void updateOverviewPredictionState() {
-        // By default overview predictions are not supported
+    public void updateOverviewPredictionState() {
+        // By public overview predictions are not supported
     }
 
     /**
      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
      */
-    int getContainerType();
+    public abstract int getContainerType();
 
-    boolean isInLiveTileMode();
+    public abstract boolean isInLiveTileMode();
 
-    void onLaunchTaskFailed();
+    public abstract void onLaunchTaskFailed();
 
-    void onLaunchTaskSuccess();
+    public void onLaunchTaskSuccess() {
+        ACTIVITY_TYPE activity = getCreatedActivity();
+        if (activity == null) {
+            return;
+        }
+        activity.getStateManager().moveToRestState();
+    }
 
-    default void closeOverlay() { }
+    public void closeOverlay() { }
 
-    default void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
-            Runnable runnable) {}
+    public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {
+        ACTIVITY_TYPE activity = getCreatedActivity();
+        if (activity == null) {
+            return;
+        }
+        RecentsView recentsView = activity.getOverviewPanel();
+        if (recentsView == null) {
+            if (runnable != null) {
+                runnable.run();
+            }
+            return;
+        }
+        recentsView.switchToScreenshot(thumbnailData, runnable);
+    }
 
-    interface AnimationFactory {
+    /**
+     * 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);
+    }
+
+    protected abstract float getExtraSpace(Context context, DeviceProfile dp);
+
+    private void calculateTaskSize(
+            Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect) {
+        Resources res = context.getResources();
+        final boolean showLargeTaskSize = showOverviewActions(context);
+
+        final int paddingResId;
+        if (dp.isMultiWindowMode) {
+            paddingResId = R.dimen.multi_window_task_card_horz_space;
+        } else if (dp.isVerticalBarLayout()) {
+            paddingResId = R.dimen.landscape_task_card_horz_space;
+        } else if (showLargeTaskSize) {
+            paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
+        } else {
+            paddingResId = R.dimen.portrait_task_card_horz_space;
+        }
+        float paddingHorz = res.getDimension(paddingResId);
+        float paddingVert = showLargeTaskSize
+                ? 0 : res.getDimension(R.dimen.task_card_vert_space);
+
+        calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
+                res.getDimension(R.dimen.task_thumbnail_top_margin), outRect);
+    }
+
+    private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
+            float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin,
+            Rect outRect) {
+        float taskWidth, taskHeight;
+        Rect insets = dp.getInsets();
+        if (dp.isMultiWindowMode) {
+            WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
+            taskWidth = bounds.availableSize.x;
+            taskHeight = bounds.availableSize.y;
+        } else {
+            taskWidth = dp.availableWidthPx;
+            taskHeight = dp.availableHeightPx;
+        }
+
+        // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
+        // we override the insets ourselves.
+        int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
+        int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
+
+        float availableHeight = launcherVisibleHeight
+                - topIconMargin - extraVerticalSpace - paddingVert;
+        float availableWidth = launcherVisibleWidth - paddingHorz;
+
+        float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
+        float outWidth = scale * taskWidth;
+        float outHeight = scale * taskHeight;
+
+        // Center in the visible space
+        float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
+        float y = insets.top + Math.max(topIconMargin,
+                (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
+        outRect.set(Math.round(x), Math.round(y),
+                Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
+    }
+
+    /**
+     * Calculates the modal taskView size for the provided device configuration
+     */
+    public 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()
+                        ? R.dimen.landscape_task_card_horz_space
+                        : R.dimen.portrait_modal_task_card_horz_space);
+        float extraVerticalSpace = getOverviewActionsHeight(context);
+        float paddingVert = 0;
+        float topIconMargin = 0;
+        calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert,
+                topIconMargin, outRect);
+    }
+
+    /** Gets the space that the overview actions will take, including margins. */
+    public float getOverviewActionsHeight(Context context) {
+        Resources res = context.getResources();
+        float actionsBottomMargin = 0;
+        if (getMode(context) == Mode.THREE_BUTTONS) {
+            actionsBottomMargin = res.getDimensionPixelSize(
+                    R.dimen.overview_actions_bottom_margin_three_button);
+        } else {
+            actionsBottomMargin = res.getDimensionPixelSize(
+                    R.dimen.overview_actions_bottom_margin_gesture);
+        }
+        float overviewActionsHeight = actionsBottomMargin
+                + res.getDimensionPixelSize(R.dimen.overview_actions_height);
+        return overviewActionsHeight;
+    }
+
+    public interface AnimationFactory {
 
         default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
 
@@ -137,4 +296,8 @@
          */
         default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
     }
+
+    protected static boolean showOverviewActions(Context context) {
+        return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
deleted file mode 100644
index 1b9158b..0000000
--- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.util.ActivityTracker;
-import com.android.launcher3.util.SystemUiController;
-import com.android.launcher3.util.Themes;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * A base fallback recents activity that provides support for device profile changes, activity
- * lifecycle tracking, and basic input handling from recents.
- *
- * This class is only used as a fallback in case the default launcher does not have a recents
- * implementation.
- */
-public abstract class BaseRecentsActivity extends BaseDraggingActivity {
-
-    public static final ActivityTracker<BaseRecentsActivity> ACTIVITY_TRACKER =
-            new ActivityTracker<>();
-    private Configuration mOldConfig;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mOldConfig = new Configuration(getResources().getConfiguration());
-        initDeviceProfile();
-        initViews();
-
-        getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
-                Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
-        ACTIVITY_TRACKER.handleCreate(this);
-    }
-
-    /**
-     * Init drag layer and overview panel views.
-     */
-    abstract protected void initViews();
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        int diff = newConfig.diff(mOldConfig);
-        if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
-            onHandleConfigChanged();
-        }
-        mOldConfig.setTo(newConfig);
-        super.onConfigurationChanged(newConfig);
-    }
-
-    /**
-     * Logic for when device configuration changes (rotation, screen size change, multi-window,
-     * etc.)
-     */
-    protected void onHandleConfigChanged() {
-        mUserEventDispatcher = null;
-        initDeviceProfile();
-
-        AbstractFloatingView.closeOpenViews(this, true,
-                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
-        dispatchDeviceProfileChanged();
-
-        reapplyUi();
-    }
-
-    /**
-     * Initialize/update the device profile.
-     */
-    private void initDeviceProfile() {
-        mDeviceProfile = createDeviceProfile();
-        onDeviceProfileInitiated();
-    }
-
-    /**
-     * Generate the device profile to use in this activity.
-     * @return device profile
-     */
-    protected DeviceProfile createDeviceProfile() {
-        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
-
-        // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
-        // activity.
-        return dp.copy(this);
-    }
-
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        // Workaround for b/78520668, explicitly trim memory once UI is hidden
-        onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
-    }
-
-    @Override
-    public void onEnterAnimationComplete() {
-        super.onEnterAnimationComplete();
-        // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled
-        // as a part of quickstep, so that high-res thumbnails can load the next time we enter
-        // overview
-        RecentsModel.INSTANCE.get(this).getThumbnailCache()
-                .getHighResLoadingState().setVisible(true);
-    }
-
-    @Override
-    public void onTrimMemory(int level) {
-        super.onTrimMemory(level);
-        RecentsModel.INSTANCE.get(this).onTrimMemory(level);
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        ACTIVITY_TRACKER.handleNewIntent(this, intent);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        ACTIVITY_TRACKER.onActivityDestroyed(this);
-    }
-
-    @Override
-    public void onBackPressed() {
-        // TODO: Launch the task we came from
-        startHome();
-    }
-
-    public void startHome() {
-        startActivity(new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-    }
-
-    @Override
-    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        super.dump(prefix, fd, writer, args);
-        writer.println(prefix + "Misc:");
-        dumpMisc(prefix + "\t", writer);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 9b515ae..295b465 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -17,10 +17,12 @@
 
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 
+import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.Intent;
+import android.os.Build;
 
-import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -32,6 +34,7 @@
  * Manages the state for an active system gesture, listens for events from the system and Launcher,
  * and fires events when the states change.
  */
+@TargetApi(Build.VERSION_CODES.R)
 public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener {
 
     /**
@@ -189,7 +192,7 @@
     /**
      * @return the interface to the activity handing the UI updates for this gesture.
      */
-    public <T extends BaseDraggingActivity> BaseActivityInterface<T> getActivityInterface() {
+    public <T extends StatefulActivity<?>> BaseActivityInterface<?, T> getActivityInterface() {
         return mActivityInterface;
     }
 
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 231ee72..0449d0c 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -140,7 +140,7 @@
 
         if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
-            mActivityInterface = new LauncherActivityInterface();
+            mActivityInterface = LauncherActivityInterface.INSTANCE;
             mIsHomeAndOverviewSame = true;
             mOverviewIntent = mMyHomeIntent;
             mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
@@ -150,7 +150,7 @@
         } else {
             // The default home app is a different launcher. Use the fallback Overview instead.
 
-            mActivityInterface = new FallbackActivityInterface();
+            mActivityInterface = FallbackActivityInterface.INSTANCE;
             mIsHomeAndOverviewSame = false;
             mOverviewIntent = mFallbackIntent;
             mCurrentHomeIntent.setComponent(defaultHome);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index b0ce8e6..2d9c56f 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -154,8 +154,7 @@
      * Loads and creates a list of all the recent tasks.
      */
     @VisibleForTesting
-    ArrayList<Task> loadTasksInBackground(int numTasks,
-            boolean loadKeysOnly) {
+    ArrayList<Task> loadTasksInBackground(int numTasks, boolean loadKeysOnly) {
         int currentUserId = Process.myUserHandle().getIdentifier();
         ArrayList<Task> allTasks = new ArrayList<>();
         List<ActivityManager.RecentTaskInfo> rawTasks =
@@ -174,9 +173,7 @@
             }
         };
 
-        int taskCount = rawTasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i);
+        for (ActivityManager.RecentTaskInfo rawTask : rawTasks) {
             Task.TaskKey taskKey = new Task.TaskKey(rawTask);
             Task task;
             if (!loadKeysOnly) {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index f3cefb9..58870ed 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -68,13 +68,16 @@
     @Override
     Integer getActionButtonStringId() {
         if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
-            return R.string.gesture_tutorial_action_button_label;
+            return R.string.gesture_tutorial_action_button_label_done;
         }
         return null;
     }
 
     @Override
     Integer getActionTextButtonStringId() {
+        if (mTutorialType == BACK_NAVIGATION_COMPLETE) {
+            return R.string.gesture_tutorial_action_button_label_settings;
+        }
         return null;
     }
 
@@ -86,7 +89,6 @@
     @Override
     void onActionTextButtonClicked(View button) {
         mTutorialFragment.startSystemNavigationSetting();
-        mTutorialFragment.closeTutorial();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 0e45376..524cbaf 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -60,7 +60,7 @@
     @Override
     Integer getActionButtonStringId() {
         if (mTutorialType == HOME_NAVIGATION_COMPLETE) {
-            return R.string.gesture_tutorial_action_button_label;
+            return R.string.gesture_tutorial_action_button_label_done;
         }
         return null;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 3a56b0e..44c1a5d 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.interaction;
 
-import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.graphics.Insets;
 import android.os.Bundle;
@@ -35,8 +34,6 @@
 import com.android.launcher3.R;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 
-import java.net.URISyntaxException;
-
 abstract class TutorialFragment extends Fragment implements OnTouchListener {
 
     private static final String LOG_TAG = "TutorialFragment";
@@ -182,14 +179,6 @@
     }
 
     void startSystemNavigationSetting() {
-        try {
-            startActivityForResult(
-                    Intent.parseUri(SYSTEM_NAVIGATION_SETTING_INTENT, /* flags= */ 0),
-                    /* requestCode= */ 0);
-        } catch (URISyntaxException e) {
-            Log.e(LOG_TAG, "The launch Intent Uri is wrong syntax: " + e);
-        } catch (ActivityNotFoundException e) {
-            Log.e(LOG_TAG, "The launch Activity not found: " + e);
-        }
+        startActivity(new Intent("com.android.settings.GESTURE_NAVIGATION_SETTINGS"));
     }
 }
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index a98aad1..8889560 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -64,24 +64,46 @@
     }
 
     /**
-     * Logs an event and accompanying {@link ItemInfo}
+     * Logs a {@link LauncherEvent}.
      */
+    @Override
+    public void log(LauncherEvent event) {
+        log(event, DEFAULT_INSTANCE_ID, LauncherAtom.ItemInfo.getDefaultInstance());
+    }
+
+    /**
+     * Logs an event and accompanying {@link InstanceId}.
+     */
+    @Override
+    public void log(LauncherEvent event, InstanceId instanceId) {
+        log(event, instanceId, LauncherAtom.ItemInfo.getDefaultInstance());
+    }
+
+    /**
+     * Logs an event and accompanying {@link ItemInfo}.
+     */
+    @Override
     public void log(LauncherEvent event, LauncherAtom.ItemInfo itemInfo) {
         log(event, DEFAULT_INSTANCE_ID, itemInfo);
     }
 
     /**
-     * Logs an event and accompanying {@link LauncherAtom.ItemInfo}
+     * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}.
      */
     @Override
     public void log(LauncherEvent event, InstanceId instanceId, LauncherAtom.ItemInfo itemInfo) {
         if (IS_VERBOSE) {
-            Log.d(TAG, String.format("\n%s\n%s", event.name(), itemInfo));
+            Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID
+                    ? String.format("\n%s\n%s", event.name(), itemInfo)
+                    : String.format("%s(InstanceId:%s)\n%s", event.name(), instanceId, itemInfo));
         }
+
         if (!Utilities.ATLEAST_R) {
             return;
         }
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT,
+
+        SysUiStatsLog.write(
+                SysUiStatsLog.LAUNCHER_EVENT,
                 SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */,
                 SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME /* TODO */,
                 SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND /* TODO */,
@@ -118,6 +140,7 @@
     }
 
     private class SnapshotWorker extends BaseModelUpdateTask {
+
         @Override
         public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
             IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
@@ -140,6 +163,7 @@
             }
         }
     }
+
     private static void writeSnapshot(LauncherAtom.ItemInfo itemInfo) {
         if (IS_VERBOSE) {
             Log.d(TAG, "\nwriteSnapshot:" + itemInfo);
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index fa53be2..c1b276a 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
-import static com.android.quickstep.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -26,6 +25,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
+import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.SysUINavigationMode;
 
 public class LayoutUtils {
@@ -45,7 +45,7 @@
         // Track the bottom of the window.
         if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
             Rect taskSize = new Rect();
-            LAUNCHER_ACTIVITY_SIZE_STRATEGY.calculateTaskSize(context, dp, taskSize);
+            LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize);
             return (dp.heightPx - taskSize.height()) / 2;
         }
         int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index fffbb34..e03f4b8 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -51,6 +51,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.WindowBounds;
+import com.android.quickstep.BaseActivityInterface;
 
 import java.lang.annotation.Retention;
 import java.util.function.IntConsumer;
@@ -120,7 +122,7 @@
     private final ContentResolver mContentResolver;
     private final SharedPreferences mSharedPrefs;
     private final OrientationEventListener mOrientationListener;
-    private final WindowSizeStrategy mSizeStrategy;
+    private final BaseActivityInterface mSizeStrategy;
 
     private final Matrix mTmpMatrix = new Matrix();
 
@@ -132,7 +134,7 @@
      *                              is enabled
      * @see #setRotationWatcherEnabled(boolean)
      */
-    public RecentsOrientedState(Context context, WindowSizeStrategy sizeStrategy,
+    public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
             IntConsumer rotationChangeListener) {
         mContext = context;
         mContentResolver = context.getContentResolver();
@@ -361,7 +363,8 @@
         float fullHeight = dp.heightPx - insets.top - insets.bottom;
 
         if (dp.isMultiWindowMode) {
-            mSizeStrategy.getMultiWindowSize(mContext, dp, outPivot);
+            WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(mContext);
+            outPivot.set(bounds.availableSize.x, bounds.availableSize.y);
         } else {
             outPivot.set(fullWidth, fullHeight);
         }
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
new file mode 100644
index 0000000..a770e8e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Build;
+import android.view.WindowInsets.Type;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.WindowBounds;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to hold the information abound a window bounds for split screen
+ */
+@TargetApi(Build.VERSION_CODES.R)
+public class SplitScreenBounds {
+
+    public static final SplitScreenBounds INSTANCE = new SplitScreenBounds();
+    private final ArrayList<OnChangeListener> mListeners = new ArrayList<>();
+
+    @Nullable
+    private WindowBounds mBounds;
+
+    private SplitScreenBounds() { }
+
+    @UiThread
+    public void setSecondaryWindowBounds(@NonNull WindowBounds bounds) {
+        if (!bounds.equals(mBounds)) {
+            mBounds = bounds;
+            for (OnChangeListener listener : mListeners) {
+                listener.onSecondaryWindowBoundsChanged();
+            }
+        }
+    }
+
+    public @NonNull WindowBounds getSecondaryWindowBounds(Context context) {
+        if (mBounds == null) {
+            mBounds = createDefaultWindowBounds(context);
+        }
+        return mBounds;
+    }
+
+    /**
+     * Creates window bounds as 50% of device size
+     */
+    private static WindowBounds createDefaultWindowBounds(Context context) {
+        WindowMetrics wm = context.getSystemService(WindowManager.class).getMaximumWindowMetrics();
+        Insets insets = wm.getWindowInsets().getInsets(Type.systemBars());
+
+        WindowBounds bounds = new WindowBounds(wm.getBounds(),
+                new Rect(insets.left, insets.top, insets.right, insets.bottom));
+        int rotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
+        int halfDividerSize = context.getResources()
+                .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
+
+        if (rotation == ROTATION_0 || rotation == ROTATION_180) {
+            bounds.bounds.top = bounds.insets.top + bounds.availableSize.y / 2 + halfDividerSize;
+            bounds.insets.top = 0;
+        } else {
+            bounds.bounds.left = bounds.insets.left + bounds.availableSize.x / 2 + halfDividerSize;
+            bounds.insets.left = 0;
+        }
+        return new WindowBounds(bounds.bounds, bounds.insets);
+    }
+
+    public void addOnChangeListener(OnChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void removeOnChangeListener(OnChangeListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Interface to receive window bounds changes
+     */
+    public interface OnChangeListener {
+
+        /**
+         * Called when window bounds for secondary window changes
+         */
+        void onSecondaryWindowBoundsChanged();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java b/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java
deleted file mode 100644
index 81a1924..0000000
--- a/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * 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.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.SysUINavigationMode.getMode;
-import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
-import static com.android.quickstep.util.LayoutUtils.getDefaultSwipeHeight;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PointF;
-import android.graphics.Rect;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.quickstep.SysUINavigationMode.Mode;
-
-/**
- * Utility class to wrap different layout behavior for Launcher and RecentsView
- * TODO: Merge is with {@link com.android.quickstep.BaseActivityInterface} once we remove the
- * state dependent members from {@link com.android.quickstep.LauncherActivityInterface}
- */
-public abstract class WindowSizeStrategy {
-
-    private final PointF mTempPoint = new PointF();
-    public final boolean rotationSupportedByActivity;
-
-    private WindowSizeStrategy(boolean rotationSupportedByActivity) {
-        this.rotationSupportedByActivity = rotationSupportedByActivity;
-    }
-
-    /**
-     * 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);
-    }
-
-    abstract float getExtraSpace(Context context, DeviceProfile dp);
-
-    private void calculateTaskSize(
-            Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect) {
-        float taskWidth, taskHeight, paddingHorz;
-        Resources res = context.getResources();
-        Rect insets = dp.getInsets();
-        final boolean showLargeTaskSize = showOverviewActions(context);
-
-        if (dp.isMultiWindowMode) {
-            getMultiWindowSize(context, dp, mTempPoint);
-            taskWidth = mTempPoint.x;
-            taskHeight = mTempPoint.y;
-            paddingHorz = res.getDimension(R.dimen.multi_window_task_card_horz_space);
-        } else {
-            taskWidth = dp.availableWidthPx;
-            taskHeight = dp.availableHeightPx;
-
-            final int paddingResId;
-            if (dp.isVerticalBarLayout()) {
-                paddingResId = R.dimen.landscape_task_card_horz_space;
-            } else if (showLargeTaskSize) {
-                paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
-            } else {
-                paddingResId = R.dimen.portrait_task_card_horz_space;
-            }
-            paddingHorz = res.getDimension(paddingResId);
-        }
-
-        float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
-        float paddingVert = showLargeTaskSize
-                ? 0 : res.getDimension(R.dimen.task_card_vert_space);
-
-        // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
-        // we override the insets ourselves.
-        int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
-        int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
-
-        float availableHeight = launcherVisibleHeight
-                - topIconMargin - extraVerticalSpace - paddingVert;
-        float availableWidth = launcherVisibleWidth - paddingHorz;
-
-        float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
-        float outWidth = scale * taskWidth;
-        float outHeight = scale * taskHeight;
-
-        // Center in the visible space
-        float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
-        float y = insets.top + Math.max(topIconMargin,
-                (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
-        outRect.set(Math.round(x), Math.round(y),
-                Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
-    }
-
-
-    public static final WindowSizeStrategy LAUNCHER_ACTIVITY_SIZE_STRATEGY =
-            new WindowSizeStrategy(true) {
-
-        @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
-        float getExtraSpace(Context context, DeviceProfile dp) {
-            if (dp.isVerticalBarLayout()) {
-                return  0;
-            } else {
-                Resources res = context.getResources();
-                if (showOverviewActions(context)) {
-                    //TODO: this needs to account for the swipe gesture height and accessibility
-                    // UI when shown.
-                    float actionsBottomMargin = 0;
-                    if (getMode(context) == Mode.THREE_BUTTONS) {
-                        actionsBottomMargin = res.getDimensionPixelSize(
-                                R.dimen.overview_actions_bottom_margin_three_button);
-                    } else {
-                        actionsBottomMargin = res.getDimensionPixelSize(
-                                R.dimen.overview_actions_bottom_margin_gesture);
-                    }
-                    float actionsTopMargin = res.getDimensionPixelSize(
-                            R.dimen.overview_actions_top_margin);
-                    float actionsHeight = actionsTopMargin + actionsBottomMargin
-                            + res.getDimensionPixelSize(R.dimen.overview_actions_height);
-                    return actionsHeight;
-                } else {
-                    return getDefaultSwipeHeight(context, dp) + dp.workspacePageIndicatorHeight
-                            + res.getDimensionPixelSize(
-                                    R.dimen.dynamic_grid_hotseat_extra_vertical_size)
-                            + res.getDimensionPixelSize(
-                                    R.dimen.dynamic_grid_hotseat_bottom_padding);
-                }
-            }
-        }
-    };
-
-    public static final WindowSizeStrategy FALLBACK_RECENTS_SIZE_STRATEGY =
-            new WindowSizeStrategy(false) {
-        @Override
-        public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) {
-            out.set(dp.widthPx, dp.heightPx);
-        }
-
-        @Override
-        float getExtraSpace(Context context, DeviceProfile dp) {
-            return showOverviewActions(context)
-                    ? context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height)
-                    : 0;
-        }
-    };
-
-    static boolean showOverviewActions(Context context) {
-        return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context);
-    }
-}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 79ed2b8..60b6da6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.IconLabelDotView;
 
@@ -744,11 +745,12 @@
     }
 
     @Override
-    public void prepareDrawDragView() {
+    public SafeCloseable prepareDrawDragView() {
         if (getIcon() instanceof FastBitmapDrawable) {
             FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
             icon.setScale(1f);
         }
         setForceHideDot(true);
+        return () -> { };
     }
 }
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index b4c5f96..d75d712 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_CANCEL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.UNDO;
 
@@ -27,6 +29,7 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -38,6 +41,8 @@
 
 public class DeleteDropTarget extends ButtonDropTarget {
 
+    private final StatsLogManager mStatsLogManager;
+
     private int mControlType = ControlType.DEFAULT_CONTROLTYPE;
 
     public DeleteDropTarget(Context context, AttributeSet attrs) {
@@ -46,6 +51,7 @@
 
     public DeleteDropTarget(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        this.mStatsLogManager = StatsLogManager.newInstance(context);
     }
 
     @Override
@@ -120,6 +126,11 @@
             d.dragInfo.container = NO_ID;
         }
         super.onDrop(d, options);
+        mStatsLogManager.log(
+                mControlType == ControlType.REMOVE_TARGET
+                        ? LAUNCHER_ITEM_DROPPED_ON_REMOVE
+                        : LAUNCHER_ITEM_DROPPED_ON_CANCEL,
+                d.logInstanceId);
     }
 
     @Override
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 9dbb5fc..fbac0bd 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -7,6 +7,10 @@
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DISMISS_PREDICTION;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_UNINSTALL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_UNINSTALL_CANCELLED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_UNINSTALL_COMPLETED;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SYSTEM_NO;
 
@@ -34,6 +38,7 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -58,7 +63,7 @@
 
     private static final long CACHE_EXPIRE_TIMEOUT = 5000;
     private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
-
+    private final StatsLogManager mStatsLogManager;
     private final Alarm mCacheExpireAlarm;
     private boolean mHadPendingAlarm;
 
@@ -69,8 +74,8 @@
 
     public SecondaryDropTarget(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-
         mCacheExpireAlarm = new Alarm();
+        mStatsLogManager = StatsLogManager.newInstance(context);
     }
 
     @Override
@@ -214,6 +219,11 @@
         // Defer onComplete
         d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
         super.onDrop(d, options);
+        if (mCurrentAccessibilityAction == UNINSTALL) {
+            mStatsLogManager.log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL, d.logInstanceId);
+        } else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+            mStatsLogManager.log(LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST, d.logInstanceId);
+        }
     }
 
     @Override
@@ -338,8 +348,10 @@
                     mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) {
                 mDragObject.dragSource = mOriginal;
                 mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
+                mStatsLogManager.log(LAUNCHER_ITEM_UNINSTALL_COMPLETED, mDragObject.logInstanceId);
             } else {
                 sendFailure();
+                mStatsLogManager.log(LAUNCHER_ITEM_UNINSTALL_CANCELLED, mDragObject.logInstanceId);
             }
         }
 
diff --git a/src/com/android/launcher3/dragndrop/DraggableView.java b/src/com/android/launcher3/dragndrop/DraggableView.java
index 287c781..f7dcf6b 100644
--- a/src/com/android/launcher3/dragndrop/DraggableView.java
+++ b/src/com/android/launcher3/dragndrop/DraggableView.java
@@ -18,6 +18,10 @@
 
 import android.graphics.Rect;
 
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.util.SafeCloseable;
+
 /**
  * Interface defining methods required for drawing and previewing DragViews, drag previews, and
  * related animations
@@ -42,9 +46,12 @@
     int getViewType();
 
     /**
-     * Before rendering as a DragView bitmap, some views need a preparation step.
+     * Before rendering as a DragView bitmap, some views need a preparation step. Returns a
+     * callback to clear any preparation work
      */
-    default void prepareDrawDragView() { }
+    @NonNull default SafeCloseable prepareDrawDragView() {
+        return () -> { };
+    }
 
     /**
      * If an actual View subclass, this method returns the rectangle (within the View's coordinates)
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 9a36b3e..f7fe535 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,23 +18,17 @@
 
 import static android.text.TextUtils.isEmpty;
 
-import static androidx.core.util.Preconditions.checkNotNull;
-
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
 import static com.android.launcher3.model.data.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
-import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
-import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
-import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
-import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
 
 import static java.util.Arrays.asList;
-import static java.util.Arrays.stream;
 import static java.util.Optional.ofNullable;
 
 import android.animation.Animator;
@@ -94,12 +88,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
-import com.android.launcher3.userevent.LauncherLogProto.Action;
-import com.android.launcher3.userevent.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.LauncherLogProto.ItemType;
-import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent;
-import com.android.launcher3.userevent.LauncherLogProto.Target;
-import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Thunk;
@@ -111,10 +99,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
-import java.util.Optional;
-import java.util.OptionalInt;
 import java.util.stream.Collectors;
-import java.util.stream.IntStream;
 
 /**
  * Represents a set of icons chosen by the user or generated by the system.
@@ -213,8 +198,7 @@
     @Thunk int mScrollHintDir = SCROLL_NONE;
     @Thunk int mCurrentScrollDir = SCROLL_NONE;
 
-    private String mPreviousLabel;
-    private boolean mIsPreviousLabelSuggested;
+    private StatsLogManager mStatsLogManager;
 
     /**
      * Used to inflate the Workspace from XML.
@@ -227,10 +211,12 @@
         setAlwaysDrawnWithCacheEnabled(false);
 
         mLauncher = Launcher.getLauncher(context);
+        mStatsLogManager = StatsLogManager.newInstance(context);
         // We need this view to be focusable in touch mode so that when text editing of the folder
         // name is complete, we have something to focus on, thus hiding the cursor and giving
         // reliable behavior when clicking the text field (since it will always gain focus on click).
         setFocusableInTouchMode(true);
+
     }
 
     @Override
@@ -348,9 +334,9 @@
         if (DEBUG) {
             Log.d(TAG, "onBackKey newTitle=" + newTitle);
         }
-
-        mInfo.title = newTitle;
-        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !getAcceptedSuggestionIndex().isPresent(),
+        mInfo.setTitle(newTitle);
+        mInfo.fromCustom = mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
+        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(),
                 mLauncher.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
         mLauncher.getModelWriter().updateItemInDatabase(mInfo);
@@ -441,8 +427,6 @@
         }
         mItemsInvalidated = true;
         mInfo.addListener(this);
-        Optional.ofNullable(mInfo.title).ifPresent(title -> mPreviousLabel = title.toString());
-        mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
 
         if (!isEmpty(mInfo.title)) {
             mFolderName.setText(mInfo.title);
@@ -1347,10 +1331,8 @@
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
-        StatsLogManager.newInstance(getContext())
-                .log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED,
-                    d.logInstanceId,
-                    d.dragInfo.buildProto(mInfo));
+        mStatsLogManager
+                .log(LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, d.dragInfo.buildProto(mInfo));
     }
 
     // This is used so the item doesn't immediately appear in the folder when added. In one case
@@ -1455,7 +1437,8 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
-                logCurrentFolderLabelState();
+                mStatsLogManager.log(LAUNCHER_FOLDER_LABEL_UPDATED, mInfo.buildProto());
+                logFolderLabelState();
                 mFolderName.dispatchBackKey();
             }
         }
@@ -1654,147 +1637,14 @@
         return mContent;
     }
 
-    protected void logCurrentFolderLabelState() {
-        LauncherEvent launcherEvent = LauncherEvent.newBuilder()
-                .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
-                .addSrcTarget(newEditTextTargetBuilder()
-                        .setFromFolderLabelState(getFromFolderLabelState())
-                        .setToFolderLabelState(getToFolderLabelState()))
-                .addSrcTarget(newFolderTargetBuilder())
-                .addSrcTarget(newParentContainerTarget())
-                .build();
-        mLauncher.getUserEventDispatcher().logLauncherEvent(launcherEvent);
-        mPreviousLabel = mFolderName.getText().toString();
-        mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
-    }
-
-    private Target.FromFolderLabelState getFromFolderLabelState() {
-        return mPreviousLabel == null
-                ? FROM_FOLDER_LABEL_STATE_UNSPECIFIED
-                : mPreviousLabel.isEmpty()
-                ? FROM_EMPTY
-                : mIsPreviousLabelSuggested
-                ? FROM_SUGGESTED
-                : FROM_CUSTOM;
-    }
-
-    private Target.ToFolderLabelState getToFolderLabelState() {
-        String newLabel =
-                checkNotNull(mFolderName.getText().toString(),
-                "Expected valid folder label, but found null");
-        if (newLabel.equals(mPreviousLabel)) {
-            return Target.ToFolderLabelState.UNCHANGED;
-        }
-
-        if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            return newLabel.isEmpty()
-                ? ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED
-                : ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED;
-        }
-
-        Optional<String[]> suggestedLabels = getSuggestedLabels();
-        boolean isEmptySuggestions = suggestedLabels
-                .map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
-                .orElse(true);
-        if (isEmptySuggestions) {
-            return newLabel.isEmpty()
-                ? ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS
-                : ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS;
-        }
-
-        boolean hasValidPrimary = suggestedLabels
-                .map(labels -> !isEmpty(labels[0]))
-                .orElse(false);
-        if (newLabel.isEmpty()) {
-            return hasValidPrimary ? ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY
-                : ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
-        }
-
-        OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex();
-        if (!accepted_suggestion_index.isPresent()) {
-            return hasValidPrimary ? ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY
-                : ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
-        }
-
-        switch (accepted_suggestion_index.getAsInt()) {
-            case 0:
-                return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY;
-            case 1:
-                return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY
-                    : ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY;
-            case 2:
-                return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY
-                    : ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY;
-            case 3:
-                return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY
-                    : ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY;
-            default:
-                // fall through
-        }
-        return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED;
-
-    }
-
-    private Optional<String[]> getSuggestedLabels() {
-        return ofNullable(mInfo)
-            .map(info -> info.suggestedFolderNames)
-            .map(
-                folderNames ->
-                    (FolderNameInfo[])
-                        folderNames.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
-            .map(
-                folderNameInfoArray ->
-                    stream(folderNameInfoArray)
-                        .filter(Objects::nonNull)
-                        .map(FolderNameInfo::getLabel)
-                        .filter(Objects::nonNull)
-                        .map(CharSequence::toString)
-                        .toArray(String[]::new));
-    }
-
-    private OptionalInt getAcceptedSuggestionIndex() {
-        String newLabel = checkNotNull(mFolderName.getText().toString(),
-                "Expected valid folder label, but found null");
-        return getSuggestedLabels()
-                .map(suggestionsArray ->
-                    IntStream.range(0, suggestionsArray.length)
-                        .filter(
-                            index -> !isEmpty(suggestionsArray[index])
-                                && newLabel.equalsIgnoreCase(suggestionsArray[index]))
-                        .sequential()
-                        .findFirst()
-                ).orElse(OptionalInt.empty());
-
-    }
-
-
-    private Target.Builder newEditTextTargetBuilder() {
-        return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT);
-    }
-
-    private Target.Builder newFolderTargetBuilder() {
-        return Target.newBuilder()
-                .setType(Target.Type.CONTAINER)
-                .setContainerType(ContainerType.FOLDER)
-                .setPageIndex(mInfo.screenId)
-                .setGridX(mInfo.cellX)
-                .setGridY(mInfo.cellY)
-                .setCardinality(mInfo.contents.size());
-    }
-
-    private Target.Builder newParentContainerTarget() {
-        Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
-        switch (mInfo.container) {
-            case CONTAINER_HOTSEAT:
-                return builder.setContainerType(ContainerType.HOTSEAT);
-            case CONTAINER_DESKTOP:
-                return builder.setContainerType(ContainerType.WORKSPACE);
-            default:
-                throw new AssertionError(String
-                        .format("Expected container to be either %s or %s but found %s.",
-                                CONTAINER_HOTSEAT,
-                                CONTAINER_DESKTOP,
-                                mInfo.container));
-        }
+    /**
+     * Logs current folder label info.
+     *
+     * @deprecated This method is only used for log validation and soon will be removed.
+     */
+    @Deprecated
+    public void logFolderLabelState() {
+        mLauncher.getUserEventDispatcher()
+                .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent());
     }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 93208d4..153d6bc 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -20,6 +20,7 @@
 
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -62,6 +63,8 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.FolderInfo.FolderListener;
@@ -410,10 +413,10 @@
                 Executors.UI_HELPER_EXECUTOR.post(() -> {
                     d.folderNameProvider.getSuggestedFolderName(
                             getContext(), mInfo.contents, nameInfos);
-                    showFinalView(finalIndex, item, nameInfos);
+                    showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
                 });
             } else {
-                showFinalView(finalIndex, item, nameInfos);
+                showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
             }
         } else {
             addItem(item);
@@ -421,12 +424,12 @@
     }
 
     private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
-            FolderNameInfo[] nameInfos) {
+            FolderNameInfo[] nameInfos, InstanceId instanceId) {
         postDelayed(() -> {
             mPreviewItemManager.hidePreviewItem(finalIndex, false);
             mFolder.showItem(item);
-            setLabelSuggestion(nameInfos);
-            mFolder.logCurrentFolderLabelState();
+            setLabelSuggestion(nameInfos, instanceId);
+            mFolder.logFolderLabelState();
             invalidate();
         }, DROP_IN_ANIMATION_DURATION);
     }
@@ -434,7 +437,7 @@
     /**
      * Set the suggested folder name.
      */
-    public void setLabelSuggestion(FolderNameInfo[] nameInfos) {
+    public void setLabelSuggestion(FolderNameInfo[] nameInfos, InstanceId instanceId) {
         if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
             return;
         }
@@ -445,7 +448,9 @@
         if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) {
             return;
         }
-        mInfo.title = nameInfos[0].getLabel();
+        mInfo.setTitle(nameInfos[0].getLabel());
+        StatsLogManager.newInstance(getContext())
+                .log(LAUNCHER_FOLDER_LABEL_UPDATED, instanceId, mInfo.buildProto());
         onTitleChanged(mInfo.title);
         mFolder.mFolderName.setText(mInfo.title);
         mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 634d07e..21822a3 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.nio.ByteBuffer;
@@ -76,11 +77,12 @@
 
         if (mView instanceof DraggableView) {
             DraggableView dv = (DraggableView) mView;
-            dv.prepareDrawDragView();
-            dv.getSourceVisualDragBounds(mTempRect);
-            destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
-                    blurSizeOutline / 2 - mTempRect.top);
-            mView.draw(destCanvas);
+            try (SafeCloseable t = dv.prepareDrawDragView()) {
+                dv.getSourceVisualDragBounds(mTempRect);
+                destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
+                        blurSizeOutline / 2 - mTempRect.top);
+                mView.draw(destCanvas);
+            }
         }
         destCanvas.restoreToCount(saveCount);
     }
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 20eec9a..350f221 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -29,6 +29,7 @@
 import android.view.Display;
 import android.view.SurfaceControlViewHost;
 import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
 
 import com.android.launcher3.InvariantDeviceProfile;
 
@@ -37,6 +38,8 @@
 /** Render preview using surface view. */
 public class PreviewSurfaceRenderer implements IBinder.DeathRecipient {
 
+    private static final int FADE_IN_ANIMATION_DURATION = 200;
+
     private static final String KEY_HOST_TOKEN = "host_token";
     private static final String KEY_VIEW_WIDTH = "width";
     private static final String KEY_VIEW_HEIGHT = "height";
@@ -78,10 +81,12 @@
             binderDied();
         }
 
+        SurfaceControlViewHost.SurfacePackage surfacePackage;
         try {
             mSurfaceControlViewHost = MAIN_EXECUTOR
                     .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
                     .get(5, TimeUnit.SECONDS);
+            surfacePackage = mSurfaceControlViewHost.getSurfacePackage();
             mHostToken.linkToDeath(this, 0);
         } catch (Exception e) {
             e.printStackTrace();
@@ -89,6 +94,14 @@
         }
 
         MAIN_EXECUTOR.execute(() -> {
+            // If mSurfaceControlViewHost is null due to any reason (e.g. binder died,
+            // happening when user leaves the preview screen before preview rendering finishes),
+            // we should return here.
+            SurfaceControlViewHost host = mSurfaceControlViewHost;
+            if (host == null) {
+                return;
+            }
+
             View view = new LauncherPreviewRenderer(mContext, mIdp).getRenderedView();
             // This aspect scales the view to fit in the surface and centers it
             final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
@@ -99,14 +112,19 @@
             view.setPivotY(0);
             view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
             view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
-            mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(),
+            view.setAlpha(0);
+            view.animate().alpha(1)
+                    .setInterpolator(new AccelerateDecelerateInterpolator())
+                    .setDuration(FADE_IN_ANIMATION_DURATION)
+                    .start();
+            host.setView(view, view.getMeasuredWidth(),
                     view.getMeasuredHeight());
         });
 
         Bundle result = new Bundle();
-        result.putParcelable(KEY_SURFACE_PACKAGE, mSurfaceControlViewHost.getSurfacePackage());
+        result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage);
 
-        Handler handler = new Handler(Looper.getMainLooper(), Loopermessage -> {
+        Handler handler = new Handler(Looper.getMainLooper(), message -> {
             binderDied();
             return true;
         });
@@ -120,8 +138,10 @@
     @Override
     public void binderDied() {
         if (mSurfaceControlViewHost != null) {
-            mSurfaceControlViewHost.release();
-            mSurfaceControlViewHost = null;
+            MAIN_EXECUTOR.execute(() -> {
+                mSurfaceControlViewHost.release();
+                mSurfaceControlViewHost = null;
+            });
         }
         mHostToken.unlinkToDeath(this, 0);
     }
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index fdd32b8..b240f0b 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -18,13 +18,13 @@
 import android.content.Context;
 
 import com.android.launcher3.R;
-import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.ItemInfo;
 import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 /**
  * Handles the user event logging in R+.
- * All of the event id is defined here.
+ * All of the event ids are defined here.
  * Most of the methods are dummy methods for Launcher3
  * Actual call happens only for Launcher variant that implements QuickStep.
  */
@@ -37,25 +37,58 @@
     public enum LauncherEvent implements EventEnum {
         @LauncherUiEvent(doc = "App launched from workspace, hotseat or folder in launcher")
         LAUNCHER_APP_LAUNCH_TAP(338),
+
         @LauncherUiEvent(doc = "Task launched from overview using TAP")
         LAUNCHER_TASK_LAUNCH_TAP(339),
+
         @LauncherUiEvent(doc = "Task launched from overview using SWIPE DOWN")
         LAUNCHER_TASK_LAUNCH_SWIPE_DOWN(340),
+
         @LauncherUiEvent(doc = "TASK dismissed from overview using SWIPE UP")
         LAUNCHER_TASK_DISMISS_SWIPE_UP(341),
+
         @LauncherUiEvent(doc = "User dragged a launcher item")
         LAUNCHER_ITEM_DRAG_STARTED(383),
+
         @LauncherUiEvent(doc = "A dragged launcher item is successfully dropped")
         LAUNCHER_ITEM_DROP_COMPLETED(385),
+
         @LauncherUiEvent(doc = "A dragged launcher item is successfully dropped on another item "
-                + "resulting in new folder creation")
-        LAUNCHER_ITEM_DROP_FOLDER_CREATED(386);
+                + "resulting in a new folder creation")
+        LAUNCHER_ITEM_DROP_FOLDER_CREATED(386),
+
+        @LauncherUiEvent(doc = "User action resulted in or manually updated the folder label to "
+                + "new/same value.")
+        LAUNCHER_FOLDER_LABEL_UPDATED(460),
+
+        @LauncherUiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar")
+        LAUNCHER_ITEM_DROPPED_ON_REMOVE(465),
+
+        @LauncherUiEvent(doc = "A dragged item is dropped on 'Cancel' button in the target bar")
+        LAUNCHER_ITEM_DROPPED_ON_CANCEL(466),
+
+        @LauncherUiEvent(doc = "A predicted item is dragged and dropped on 'Don't suggest app'"
+                + " button in the target bar")
+        LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST(467),
+
+        @LauncherUiEvent(doc = "A dragged item is dropped on 'Uninstall' button in target bar")
+        LAUNCHER_ITEM_DROPPED_ON_UNINSTALL(468),
+
+        @LauncherUiEvent(doc = "User completed uninstalling the package after dropping on "
+                + "the icon onto 'Uninstall' button in the target bar")
+        LAUNCHER_ITEM_UNINSTALL_COMPLETED(469),
+
+        @LauncherUiEvent(doc = "User cancelled uninstalling the package after dropping on "
+                + "the icon onto 'Uninstall' button in the target bar")
+        LAUNCHER_ITEM_UNINSTALL_CANCELLED(470);
         // ADD MORE
 
         private final int mId;
+
         LauncherEvent(int id) {
             mId = id;
         }
+
         public int getId() {
             return mId;
         }
@@ -78,14 +111,32 @@
     }
 
     /**
-     * Logs an event and accompanying {@link LauncherAtom.ItemInfo}
+     * Logs a {@link LauncherEvent}.
      */
-    public void log(LauncherEvent event, InstanceId instanceId, LauncherAtom.ItemInfo itemInfo) { }
-    public void log(LauncherEvent event, LauncherAtom.ItemInfo itemInfo) { }
+    public void log(LauncherEvent event) {
+    }
 
+    /**
+     * Logs an event and accompanying {@link InstanceId}.
+     */
+    public void log(LauncherEvent event, InstanceId instanceId) {
+    }
+
+    /**
+     * Logs an event and accompanying {@link ItemInfo}.
+     */
+    public void log(LauncherEvent event, ItemInfo itemInfo) {
+    }
+
+    /**
+     * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}.
+     */
+    public void log(LauncherEvent event, InstanceId instanceId, ItemInfo itemInfo) {
+    }
 
     /**
      * Logs snapshot, or impression of the current workspace.
      */
-    public void logSnapshot() { }
+    public void logSnapshot() {
+    }
 }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index da081a0..7818ff5 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -145,7 +145,9 @@
         LauncherEvent event = newLauncherEvent(action,  targets);
         ItemInfo info = v == null ? null : (ItemInfo) v.getTag();
         if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
-            FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
+            final String pkg = info.getTargetComponent() != null
+                    ? info.getTargetComponent().getPackageName() : "unknown";
+            FileLog.d(TAG, "appLaunch: packageName:" + pkg
                     + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
                     userHandle)) + ",launchLocation:" + info.container);
         }
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 3ac6a22..096743a 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -16,16 +16,45 @@
 
 package com.android.launcher3.model.data;
 
+import static android.text.TextUtils.isEmpty;
+
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
+
+import static java.util.Arrays.stream;
+import static java.util.Optional.ofNullable;
+
 import android.content.Intent;
 import android.os.Process;
+import android.text.TextUtils;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.FolderNameInfo;
 import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.FromState;
+import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.userevent.LauncherLogProto;
+import com.android.launcher3.userevent.LauncherLogProto.Target;
+import com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState;
+import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.ArrayList;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.StringJoiner;
+import java.util.stream.IntStream;
+
 
 /**
  * Represents a folder containing shortcuts or apps.
@@ -57,6 +86,20 @@
 
     public Intent suggestedFolderNames;
 
+    // Represents the title before current.
+    // Primarily used for logging purpose.
+    private CharSequence mPreviousTitle;
+
+    // True if the title before was manually entered, suggested otherwise.
+    // Primarily used for logging purpose.
+    public boolean fromCustom;
+
+    /**
+     * Used for separating {@link #mPreviousTitle} and {@link #title} when concatenating them
+     * for logging.
+     */
+    private static final CharSequence FOLDER_LABEL_DELIMITER = "=>";
+
     /**
      * The apps and shortcuts
      */
@@ -160,9 +203,20 @@
     @Override
     public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) {
         return getDefaultItemInfoBuilder()
-            .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size()))
-            .setContainerInfo(getContainerInfo())
-            .build();
+                .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size()))
+                .setRank(rank)
+                .setContainerInfo(getContainerInfo())
+                .build();
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mPreviousTitle = this.title;
+        this.title = title;
+    }
+
+    public CharSequence getPreviousTitle() {
+        return mPreviousTitle;
     }
 
     @Override
@@ -172,4 +226,244 @@
         folderInfo.contents = this.contents;
         return folderInfo;
     }
+
+    /**
+     * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging.
+     */
+    @Override
+    public LauncherAtom.ItemInfo buildProto() {
+        FromState fromFolderLabelState = getFromFolderLabelState();
+        ToState toFolderLabelState = getToFolderLabelState();
+        LauncherAtom.FolderIcon.Builder folderIconBuilder = LauncherAtom.FolderIcon.newBuilder()
+                .setCardinality(contents.size())
+                .setFromLabelState(fromFolderLabelState)
+                .setToLabelState(toFolderLabelState);
+
+        // If the folder label is suggested, it is logged to improve prediction model.
+        // When both old and new labels are logged together delimiter is used.
+        StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER);
+        if (fromFolderLabelState.equals(FromState.FROM_SUGGESTED)) {
+            labelInfoBuilder.add(mPreviousTitle);
+        }
+        if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) {
+            labelInfoBuilder.add(title);
+        }
+        if (labelInfoBuilder.length() > 0) {
+            folderIconBuilder.setLabelInfo(labelInfoBuilder.toString());
+        }
+
+        return getDefaultItemInfoBuilder()
+                .setFolderIcon(folderIconBuilder)
+                .setContainerInfo(getContainerInfo())
+                .build();
+    }
+
+    /**
+     * Returns index of the accepted suggestion.
+     */
+    public OptionalInt getAcceptedSuggestionIndex() {
+        String newLabel = checkNotNull(title,
+                "Expected valid folder label, but found null").toString();
+        return getSuggestedLabels()
+                .map(suggestionsArray ->
+                        IntStream.range(0, suggestionsArray.length)
+                                .filter(
+                                        index -> !isEmpty(suggestionsArray[index])
+                                                && newLabel.equalsIgnoreCase(
+                                                suggestionsArray[index]))
+                                .sequential()
+                                .findFirst()
+                ).orElse(OptionalInt.empty());
+
+    }
+
+    private LauncherAtom.ToState getToFolderLabelState() {
+        if (title == null) {
+            return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
+        }
+
+        if (title.equals(mPreviousTitle)) {
+            return LauncherAtom.ToState.UNCHANGED;
+        }
+
+        if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+            return title.length() > 0
+                    ? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED
+                    : LauncherAtom.ToState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
+        }
+
+        Optional<String[]> suggestedLabels = getSuggestedLabels();
+        boolean isEmptySuggestions = suggestedLabels
+                .map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
+                .orElse(true);
+        if (isEmptySuggestions) {
+            return title.length() > 0
+                    ? LauncherAtom.ToState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS
+                    : LauncherAtom.ToState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS;
+        }
+
+        boolean hasValidPrimary = suggestedLabels
+                .map(labels -> !isEmpty(labels[0]))
+                .orElse(false);
+        if (title.length() == 0) {
+            return hasValidPrimary ? LauncherAtom.ToState.TO_EMPTY_WITH_VALID_PRIMARY
+                    : LauncherAtom.ToState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+        }
+
+        OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex();
+        if (!accepted_suggestion_index.isPresent()) {
+            return hasValidPrimary ? LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_PRIMARY
+                    : LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+        }
+
+        switch (accepted_suggestion_index.getAsInt()) {
+            case 0:
+                return LauncherAtom.ToState.TO_SUGGESTION0;
+            case 1:
+                return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION1_WITH_VALID_PRIMARY
+                        : LauncherAtom.ToState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY;
+            case 2:
+                return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION2_WITH_VALID_PRIMARY
+                        : LauncherAtom.ToState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY;
+            case 3:
+                return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION3_WITH_VALID_PRIMARY
+                        : LauncherAtom.ToState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY;
+            default:
+                // fall through
+        }
+        return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
+
+    }
+
+    private LauncherAtom.FromState getFromFolderLabelState() {
+        return mPreviousTitle == null
+                ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED
+                : mPreviousTitle.length() == 0
+                        ? LauncherAtom.FromState.FROM_EMPTY
+                        : fromCustom
+                                ? LauncherAtom.FromState.FROM_CUSTOM
+                                : LauncherAtom.FromState.FROM_SUGGESTED;
+    }
+
+    private Optional<String[]> getSuggestedLabels() {
+        return ofNullable(suggestedFolderNames)
+                .map(folderNames ->
+                        (FolderNameInfo[])
+                                folderNames.getParcelableArrayExtra(EXTRA_FOLDER_SUGGESTIONS))
+                .map(folderNameInfoArray ->
+                        stream(folderNameInfoArray)
+                                .filter(Objects::nonNull)
+                                .map(FolderNameInfo::getLabel)
+                                .filter(Objects::nonNull)
+                                .map(CharSequence::toString)
+                                .toArray(String[]::new));
+    }
+
+    /**
+     * Returns {@link LauncherLogProto.LauncherEvent} to log current folder label info.
+     *
+     * @deprecated This method is used only for validation purpose and soon will be removed.
+     */
+    @Deprecated
+    public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent() {
+        return LauncherLogProto.LauncherEvent.newBuilder()
+                .setAction(LauncherLogProto.Action
+                        .newBuilder()
+                        .setType(LauncherLogProto.Action.Type.SOFT_KEYBOARD))
+                .addSrcTarget(Target
+                        .newBuilder()
+                        .setType(Target.Type.ITEM)
+                        .setItemType(LauncherLogProto.ItemType.EDITTEXT)
+                        .setFromFolderLabelState(convertFolderLabelState(getFromFolderLabelState()))
+                        .setToFolderLabelState(convertFolderLabelState(getToFolderLabelState())))
+                .addSrcTarget(Target.newBuilder()
+                        .setType(Target.Type.CONTAINER)
+                        .setContainerType(LauncherLogProto.ContainerType.FOLDER)
+                        .setPageIndex(screenId)
+                        .setGridX(cellX)
+                        .setGridY(cellY)
+                        .setCardinality(contents.size()))
+                .addSrcTarget(newParentContainerTarget())
+                .build();
+    }
+
+    /**
+     * @deprecated This method is used only for validation purpose and soon will be removed.
+     */
+    @Deprecated
+    private Target.Builder newParentContainerTarget() {
+        Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
+        switch (container) {
+            case CONTAINER_HOTSEAT:
+                return builder.setContainerType(LauncherLogProto.ContainerType.HOTSEAT);
+            case CONTAINER_DESKTOP:
+                return builder.setContainerType(LauncherLogProto.ContainerType.WORKSPACE);
+            default:
+                throw new AssertionError(String
+                        .format("Expected container to be either %s or %s but found %s.",
+                                CONTAINER_HOTSEAT,
+                                CONTAINER_DESKTOP,
+                                container));
+        }
+    }
+
+    /**
+     * @deprecated This method is used only for validation purpose and soon will be removed.
+     */
+    @Deprecated
+    private static FromFolderLabelState convertFolderLabelState(FromState fromState) {
+        switch (fromState) {
+            case FROM_EMPTY:
+                return FROM_EMPTY;
+            case FROM_SUGGESTED:
+                return FROM_SUGGESTED;
+            case FROM_CUSTOM:
+                return FROM_CUSTOM;
+            default:
+                return FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+        }
+    }
+
+    /**
+     * @deprecated This method is used only for validation purpose and soon will be removed.
+     */
+    @Deprecated
+    private static ToFolderLabelState convertFolderLabelState(ToState toState) {
+        switch (toState) {
+            case UNCHANGED:
+                return ToFolderLabelState.UNCHANGED;
+            case TO_SUGGESTION0:
+                return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY;
+            case TO_SUGGESTION1_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY;
+            case TO_SUGGESTION1_WITH_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY;
+            case TO_SUGGESTION2_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY;
+            case TO_SUGGESTION2_WITH_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY;
+            case TO_SUGGESTION3_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY;
+            case TO_SUGGESTION3_WITH_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY;
+            case TO_EMPTY_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY;
+            case TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+            case TO_EMPTY_WITH_EMPTY_SUGGESTIONS:
+                return ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS;
+            case TO_EMPTY_WITH_SUGGESTIONS_DISABLED:
+                return ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
+            case TO_CUSTOM_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY;
+            case TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+            case TO_CUSTOM_WITH_EMPTY_SUGGESTIONS:
+                return ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS;
+            case TO_CUSTOM_WITH_SUGGESTIONS_DISABLED:
+                return ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED;
+            default:
+                return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 4359f25..f2b7e54 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -252,6 +252,13 @@
     /**
      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
      */
+    public LauncherAtom.ItemInfo buildProto() {
+        return buildProto(null);
+    }
+
+    /**
+     * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
+     */
     public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) {
         LauncherAtom.ItemInfo.Builder itemBuilder = getDefaultItemInfoBuilder();
         Optional<ComponentName> nullableComponent = Optional.ofNullable(getTargetComponent());
@@ -303,7 +310,7 @@
                     break;
             }
             itemBuilder.setContainerInfo(ContainerInfo.newBuilder().setFolder(folderBuilder));
-        } else {
+        } else if (getContainerInfo().getContainerCase().getNumber() > 0) {
             itemBuilder.setContainerInfo(getContainerInfo());
         }
         return itemBuilder.build();
@@ -345,4 +352,8 @@
         itemInfo.copyFrom(this);
         return itemInfo;
     }
+
+    public void setTitle(CharSequence title) {
+        this.title = title;
+    }
 }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 5007ca0..d02c731 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -33,7 +33,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
@@ -74,8 +73,8 @@
     }
 
     @Override
-    public boolean isGoingUp(float displacement) {
-        return displacement > 0;
+    public boolean isGoingUp(float displacement, boolean isRtl) {
+        return isRtl ? displacement < 0 : displacement > 0;
     }
 
     @Override
@@ -226,13 +225,13 @@
     }
 
     @Override
-    public int getShortEdgeLength(DeviceProfile dp) {
-        return dp.heightPx;
+    public int getTaskDismissDirectionFactor() {
+        return 1;
     }
 
     @Override
-    public int getTaskDismissDirectionFactor() {
-        return 1;
+    public int getTaskDragDisplacementFactor(boolean isRtl) {
+        return isRtl ? 1 : -1;
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index cdfe6d5..2e0268d 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -75,8 +75,8 @@
     int getScrollOffsetStart(View view, Rect insets);
     int getScrollOffsetEnd(View view, Rect insets);
     SingleAxisSwipeDetector.Direction getOppositeSwipeDirection();
-    int getShortEdgeLength(DeviceProfile dp);
     int getTaskDismissDirectionFactor();
+    int getTaskDragDisplacementFactor(boolean isRtl);
     ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
     void setMaxScroll(AccessibilityEvent event, int maxScroll);
     boolean getRecentsRtlSetting(Resources resources);
@@ -91,7 +91,7 @@
     void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y);
     void scrollerStartScroll(OverScroller scroller, int newPosition);
     void getCurveProperties(PagedView view, Rect insets, CurveProperties out);
-    boolean isGoingUp(float displacement);
+    boolean isGoingUp(float displacement, boolean isRtl);
     boolean isLayoutNaturalToLauncher();
     float getTaskMenuX(float x, View thumbnailView);
     float getTaskMenuY(float y, View thumbnailView);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 25dc1f6..2fc7a9f 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -32,7 +32,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
@@ -73,7 +72,8 @@
     }
 
     @Override
-    public boolean isGoingUp(float displacement) {
+    public boolean isGoingUp(float displacement, boolean isRtl) {
+        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
         return displacement < 0;
     }
 
@@ -223,13 +223,14 @@
     }
 
     @Override
-    public int getShortEdgeLength(DeviceProfile dp) {
-        return dp.widthPx;
+    public int getTaskDismissDirectionFactor() {
+        return -1;
     }
 
     @Override
-    public int getTaskDismissDirectionFactor() {
-        return -1;
+    public int getTaskDragDisplacementFactor(boolean isRtl) {
+        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+        return 1;
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index dde2829..4c1700e 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -32,6 +32,11 @@
     }
 
     @Override
+    public int getTaskDragDisplacementFactor(boolean isRtl) {
+        return isRtl ? -1 : 1;
+    }
+
+    @Override
     public boolean getRecentsRtlSetting(Resources resources) {
         return Utilities.isRtl(resources);
     }
@@ -60,8 +65,8 @@
     }
 
     @Override
-    public boolean isGoingUp(float displacement) {
-        return displacement < 0;
+    public boolean isGoingUp(float displacement, boolean isRtl) {
+        return isRtl ? displacement > 0 : displacement < 0;
     }
 
     @Override
@@ -82,13 +87,8 @@
     }
 
     @Override
-    public int getClearAllScrollOffset(View view, boolean isRtl) {
-        return (isRtl ? view.getPaddingTop() : - view.getPaddingBottom()) / 2;
-    }
-
-    @Override
     public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
         view.setTranslationX(0);
-        view.setTranslationY(-translation);
+        view.setTranslationY(translation);
     }
 }
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index da631bd..e6de06d 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -167,8 +167,8 @@
 
     @Override
     public void onLongPress(MotionEvent event) {
-        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Workspace.longPress");
         if (mLongPressState == STATE_REQUESTED) {
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Workspace.longPress");
             if (canHandleLongPress()) {
                 mLongPressState = STATE_PENDING_PARENT_INFORM;
                 mWorkspace.getParent().requestDisallowInterceptTouchEvent(true);
diff --git a/src/com/android/launcher3/util/WindowBounds.java b/src/com/android/launcher3/util/WindowBounds.java
new file mode 100644
index 0000000..3c2fb62
--- /dev/null
+++ b/src/com/android/launcher3/util/WindowBounds.java
@@ -0,0 +1,47 @@
+/*
+ * 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.launcher3.util;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Utility class to hold information about window position and layout
+ */
+public class WindowBounds {
+
+    public final Rect bounds;
+    public final Rect insets;
+    public final Point availableSize;
+
+    public WindowBounds(Rect bounds, Rect insets) {
+        this.bounds = bounds;
+        this.insets = insets;
+        availableSize = new Point(bounds.width() - insets.left - insets.right,
+                bounds.height() - insets.top - insets.bottom);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (!(obj instanceof WindowBounds)) {
+            return false;
+        }
+        WindowBounds other = (WindowBounds) obj;
+        return other.bounds.equals(bounds) && other.insets.equals(insets);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 94ab780..ce94a3e 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -86,6 +86,11 @@
                         zeroButtonToOverviewGestureStartsInLauncher()
                                 ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
                                 : LauncherInstrumentation.GestureScope.OUTSIDE;
+
+                // b/156044202
+                mLauncher.log("Hierarchy before swiping up to overview:");
+                mLauncher.dumpViewHierarchy();
+
                 mLauncher.sendPointer(
                         downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
                 mLauncher.executeAndWaitForEvent(
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 6e9c5a0..14212be 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -436,6 +436,13 @@
                 sEventChecker.finishNoWait();
             }
         }
+        // b/156287114
+        try {
+            log("Input: " + mDevice.executeShellCommand("dumpsys input"));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
         log("Hierarchy dump for: " + message);
         dumpViewHierarchy();
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index ac90b1b..79d20ac 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -207,7 +207,9 @@
 
     // Workaround for b/154157191
     private static boolean ignoreMistatch(boolean successfulGesture, String sequence) {
-        return TestProtocol.SEQUENCE_TIS.equals(sequence) && successfulGesture;
+        // b/156287114
+        return false;
+//        return TestProtocol.SEQUENCE_TIS.equals(sequence) && successfulGesture;
     }
 
     // If the list of actual events matches the list of expected events, returns -1, otherwise