Merge "Let Launcher able to receive task launching callback(4/N)" into sc-dev
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 5b30143..161c98e 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -35,6 +35,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -83,6 +84,8 @@
 
     private @Nullable TaskbarController mTaskbarController;
     private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
+    // Will be updated when dragging from taskbar.
+    private DragOptions mWorkspaceDragOptions = new DragOptions();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -270,6 +273,15 @@
     }
 
     @Override
+    public DragOptions getDefaultWorkspaceDragOptions() {
+        return mWorkspaceDragOptions;
+    }
+
+    public void setWorkspaceDragOptions(DragOptions dragOptions) {
+        mWorkspaceDragOptions = dragOptions;
+    }
+
+    @Override
     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
         QuickstepAppTransitionManagerImpl appTransitionManager =
                 (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 0156e8f..f297343 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -107,7 +107,8 @@
         WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
         v.setVisibility(View.INVISIBLE);
         mLauncher.getWorkspace().beginDragShared(
-                v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
+                v, null, this, dragItem, new DragPreviewProvider(v),
+                mLauncher.getDefaultWorkspaceDragOptions());
         return true;
     };
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 544bc99..74a82ab 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -170,7 +170,14 @@
 
             @Override
             public View.OnLongClickListener getItemOnLongClickListener() {
-                return mDragController::startDragOnLongClick;
+                return view -> {
+                    if (mLauncher.hasBeenResumed() && view.getTag() instanceof ItemInfo) {
+                        alignRealHotseatWithTaskbar();
+                        return mDragController.startWorkspaceDragOnLongClick(view);
+                    } else {
+                        return mDragController.startSystemDragOnLongClick(view);
+                    }
+                };
             }
 
             @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index baec899..f51e498 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ClipDescriptionCompat;
@@ -57,7 +58,7 @@
      * generate the ClipDescription and Intent.
      * @return Whether {@link View#startDragAndDrop} started successfully.
      */
-    protected boolean startDragOnLongClick(View view) {
+    protected boolean startSystemDragOnLongClick(View view) {
         if (!(view instanceof BubbleTextView)) {
             return false;
         }
@@ -125,6 +126,38 @@
     }
 
     /**
+     * Starts a drag and drop operation that controls a real Workspace (Hotseat) view.
+     * @param view The Taskbar item that was long clicked.
+     * @return Whether {@link View#startDragAndDrop} started successfully.
+     */
+    protected boolean startWorkspaceDragOnLongClick(View view) {
+        View.DragShadowBuilder transparentShadowBuilder = new View.DragShadowBuilder(view) {
+            private static final int ARBITRARY_SHADOW_SIZE = 10;
+
+            @Override
+            public void onDrawShadow(Canvas canvas) {
+            }
+
+            @Override
+            public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
+                outShadowSize.set(ARBITRARY_SHADOW_SIZE, ARBITRARY_SHADOW_SIZE);
+                outShadowTouchPoint.set(ARBITRARY_SHADOW_SIZE / 2, ARBITRARY_SHADOW_SIZE / 2);
+            }
+        };
+
+        TaskbarDragListener taskbarDragListener = new TaskbarDragListener(mLauncher,
+                (ItemInfo) view.getTag());
+        if (view.startDragAndDrop(new ClipData("", new String[] {taskbarDragListener.getMimeType()},
+                        new ClipData.Item("")),
+                transparentShadowBuilder, null /* localState */, View.DRAG_FLAG_GLOBAL)) {
+            view.setOnDragListener(getDraggedViewDragListener());
+            taskbarDragListener.init(mLauncher.getDragLayer());
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Hide the original Taskbar item while it is being dragged.
      */
     private View.OnDragListener getDraggedViewDragListener() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
new file mode 100644
index 0000000..2bd5861
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+import android.content.ClipDescription;
+import android.graphics.Point;
+import android.view.DragEvent;
+import android.view.View;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.data.ItemInfo;
+
+import java.util.UUID;
+
+/**
+ * Listens to system drag and drop events initated by the Taskbar, and forwards them to Launcher's
+ * internal DragController to move Hotseat items.
+ */
+public class TaskbarDragListener implements View.OnDragListener {
+
+    private static final String MIME_TYPE_PREFIX = "com.android.launcher3.taskbar.drag_and_drop/";
+
+    private final BaseQuickstepLauncher mLauncher;
+    private final ItemInfo mDraggedItem;
+    private final DragOptions mDragOptions;
+    // Randomly generated id used to verify the drag event.
+    private final String mId;
+
+    // Initialized in init().
+    DragLayer mDragLayer;
+
+    /**
+     * @param draggedItem The info of the item that was long clicked, which we will use to find
+     *                    the equivalent match on Hotseat to drag internally.
+     */
+    public TaskbarDragListener(BaseQuickstepLauncher launcher, ItemInfo draggedItem) {
+        mLauncher = launcher;
+        mDraggedItem = draggedItem;
+        mDragOptions = new DragOptions();
+        mDragOptions.simulatedDndStartPoint = new Point();
+        mId = UUID.randomUUID().toString();
+    }
+
+    protected void init(DragLayer dragLayer) {
+        mDragLayer = dragLayer;
+        mDragLayer.setOnDragListener(this);
+    }
+
+    private void cleanup() {
+        mDragLayer.setOnDragListener(null);
+        mLauncher.setWorkspaceDragOptions(new DragOptions());
+    }
+
+    /**
+     * Returns a randomly generated id used to verify the drag event.
+     */
+    protected String getMimeType() {
+        return MIME_TYPE_PREFIX + mId;
+    }
+
+    @Override
+    public boolean onDrag(View dragLayer, DragEvent dragEvent) {
+        ClipDescription clipDescription = dragEvent.getClipDescription();
+        if (dragEvent.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+            if (clipDescription == null || !clipDescription.hasMimeType(getMimeType())) {
+                // We didn't initiate this drag, ignore.
+                cleanup();
+                return false;
+            }
+            View hotseatView = mLauncher.getHotseat().getFirstItemMatch(
+                    (info, view) -> info == mDraggedItem);
+            if (hotseatView == null) {
+                cleanup();
+                return false;
+            }
+            mDragOptions.simulatedDndStartPoint.set((int) dragEvent.getX(), (int) dragEvent.getY());
+            mLauncher.setWorkspaceDragOptions(mDragOptions);
+            hotseatView.performLongClick();
+        } else if (dragEvent.getAction() == DragEvent.ACTION_DRAG_ENDED) {
+            cleanup();
+        }
+        return mLauncher.getDragController().onDragEvent(dragEvent);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index ed27f2f..a729e77 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -192,6 +192,7 @@
             } else {
                 hotseatView.setOnClickListener(null);
                 hotseatView.setOnLongClickListener(null);
+                hotseatView.setTag(null);
             }
             updateHotseatItemVisibility(hotseatView);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index c60e257..facfb9d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -126,26 +126,11 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "PortraitStatesTouchController.getTargetState");
-        }
         if (fromState == ALL_APPS && !isDragTowardPositive) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
-                        "PortraitStatesTouchController.getTargetState 1");
-            }
             return NORMAL;
         } else if (fromState == OVERVIEW) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
-                        "PortraitStatesTouchController.getTargetState 2");
-            }
             return isDragTowardPositive ? OVERVIEW : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
-                        "PortraitStatesTouchController.getTargetState 3");
-            }
             return ALL_APPS;
         }
         return fromState;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 615d0fe..7f2af6b 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -79,7 +80,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
@@ -104,7 +104,6 @@
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -353,7 +352,6 @@
 
         mRecentsView = activity.getOverviewPanel();
         mRecentsView.setOnPageTransitionEndCallback(null);
-        addLiveTileOverlay();
 
         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
         if (alreadyOnHome) {
@@ -918,26 +916,15 @@
                 isFling, isCancel);
         float endShift = endTarget.isLauncher ? 1 : 0;
         final float startShift;
-        Interpolator interpolator = DEACCEL;
         if (!isFling) {
             long expectedDuration = Math.abs(Math.round((endShift - currentShift)
                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
             startShift = currentShift;
-            interpolator = endTarget == RECENTS ? ACCEL_DEACCEL : DEACCEL;
         } else {
             startShift = Utilities.boundToRange(currentShift - velocity.y
                     * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
             if (mTransitionDragLength > 0) {
-                if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
-                    Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
-                            startShift, endShift, endShift, endVelocity,
-                            mTransitionDragLength, mContext);
-                    endShift = overshoot.end;
-                    interpolator = overshoot.interpolator;
-                    duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
-                            MAX_SWIPE_DURATION);
-                } else {
                     float distanceToTravel = (endShift - currentShift) * mTransitionDragLength;
 
                     // we want the page's snap velocity to approximately match the velocity at
@@ -945,13 +932,9 @@
                     // derivative of the scroll interpolator at zero, ie. 2.
                     long baseDuration = Math.round(Math.abs(distanceToTravel / velocity.y));
                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
-
-                    if (endTarget == RECENTS) {
-                        interpolator = ACCEL_DEACCEL;
-                    }
-                }
             }
         }
+        Interpolator interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
 
         if (endTarget.isLauncher) {
             mInputConsumerProxy.enable();
@@ -1241,13 +1224,6 @@
         });
         anim.addAnimatorListener(new AnimationSuccessListener() {
             @Override
-            public void onAnimationStart(Animator animation) {
-                if (mActivity != null) {
-                    removeLiveTileOverlay();
-                }
-            }
-
-            @Override
             public void onAnimationSuccess(Animator animator) {
                 if (mRecentsView != null) {
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
@@ -1268,7 +1244,7 @@
             // In the off chance that the gesture ends before Launcher is started, we should clear
             // the callback here so that it doesn't update with the wrong state
             mActivity.clearRunOnceOnStartCallback();
-            resetLauncherListenersAndOverlays();
+            resetLauncherListeners();
         }
         if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
             cancelCurrentAnimation();
@@ -1351,7 +1327,7 @@
         endLauncherTransitionController();
 
         mRecentsView.onGestureAnimationEnd();
-        resetLauncherListenersAndOverlays();
+        resetLauncherListeners();
     }
 
     private void endLauncherTransitionController() {
@@ -1364,13 +1340,12 @@
         }
     }
 
-    private void resetLauncherListenersAndOverlays() {
+    private void resetLauncherListeners() {
         // Reset the callback for deferred activity launches
         if (!LIVE_TILE.get()) {
             mActivityInterface.setOnDeferredActivityLaunchCallback(null);
         }
         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
-        removeLiveTileOverlay();
     }
 
     private void notifyTransitionCancelled() {
@@ -1594,17 +1569,6 @@
         anim.start();
     }
 
-    private void addLiveTileOverlay() {
-        if (LiveTileOverlay.INSTANCE.attach(mActivity.getRootView().getOverlay())) {
-            mRecentsView.setLiveTileOverlayAttached(true);
-        }
-    }
-
-    private void removeLiveTileOverlay() {
-        LiveTileOverlay.INSTANCE.detach(mActivity.getRootView().getOverlay());
-        mRecentsView.setLiveTileOverlayAttached(false);
-    }
-
     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
         return app.isNotInRecents
                 || app.activityType == ACTIVITY_TYPE_HOME;
@@ -1765,13 +1729,6 @@
             }
             mTaskViewSimulator.apply(mTransformParams);
         }
-        if (LIVE_TILE.get() && mRecentsAnimationTargets != null) {
-            LiveTileOverlay.INSTANCE.update(
-                    mTaskViewSimulator.getCurrentRect(),
-                    mTaskViewSimulator.getCurrentCornerRadius());
-            LiveTileOverlay.INSTANCE.setRotation(
-                    mTaskViewSimulator.getOrientationState().getDisplayRotation());
-        }
         ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 8b0d782..3f3e5ad 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -187,9 +187,6 @@
 
     @Override
     public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "switchToRecentsIfVisible");
-        }
         Launcher launcher = getVisibleLauncher();
         if (launcher == null) {
             return false;
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 1c5dc4c..2f1538b 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -22,10 +22,12 @@
 import android.app.ActivityManager;
 import android.os.Build;
 import android.os.Process;
+import android.util.Log;
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -51,6 +53,9 @@
 
     // The list change id, increments as the task list changes in the system
     private int mChangeId;
+    // Whether we are currently updating the tasks in the background (up to when the result is
+    // posted back on the main thread)
+    private boolean mLoadingTasksInBackground;
 
     private TaskLoadResult mResultsBg = INVALID_RESULT;
     private TaskLoadResult mResultsUi = INVALID_RESULT;
@@ -64,6 +69,11 @@
         mActivityManagerWrapper.registerTaskStackListener(this);
     }
 
+    @VisibleForTesting
+    public boolean isLoadingTasksInBackground() {
+        return mLoadingTasksInBackground;
+    }
+
     /**
      * Fetches the task keys skipping any local cache.
      */
@@ -83,6 +93,10 @@
      * @return The change id of the current task list
      */
     public synchronized int getTasks(boolean loadKeysOnly, Consumer<ArrayList<Task>> callback) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: keysOnly=" + loadKeysOnly
+                    + " callback=" + callback);
+        }
         final int requestLoadId = mChangeId;
         if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
             // The list is up to date, send the callback on the next frame,
@@ -90,22 +104,38 @@
             if (callback != null) {
                 // Copy synchronously as the changeId might change by next frame
                 ArrayList<Task> result = copyOf(mResultsUi);
-                mMainThreadExecutor.post(() -> callback.accept(result));
+                mMainThreadExecutor.post(() -> {
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: no new tasks");
+                    }
+                    callback.accept(result);
+                });
             }
 
             return requestLoadId;
         }
 
         // Kick off task loading in the background
+        mLoadingTasksInBackground = true;
         UI_HELPER_EXECUTOR.execute(() -> {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: loading in bg start");
+            }
             if (!mResultsBg.isValidForRequest(requestLoadId, loadKeysOnly)) {
                 mResultsBg = loadTasksInBackground(Integer.MAX_VALUE, requestLoadId, loadKeysOnly);
             }
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: loading in bg end");
+            }
             TaskLoadResult loadResult = mResultsBg;
             mMainThreadExecutor.execute(() -> {
+                mLoadingTasksInBackground = false;
                 mResultsUi = loadResult;
                 if (callback != null) {
                     ArrayList<Task> result = copyOf(mResultsUi);
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.GET_RECENTS_FAILED, "getTasks: callback w/ bg results");
+                    }
                     callback.accept(result);
                 }
             });
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index d47217b..ba24e6a 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -28,6 +28,8 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.launcher3.util.MainThreadInitializedObject;
@@ -102,6 +104,14 @@
     }
 
     /**
+     * @return Whether the task list is currently updating in the background
+     */
+    @VisibleForTesting
+    public boolean isLoadingTasksInBackground() {
+        return mTaskList.isLoadingTasksInBackground();
+    }
+
+    /**
      * Finds and returns the task key associated with the given task id.
      *
      * @param callback The callback to receive the task key if it is found or null. This is always
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index ca73041..f4b8b62 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -94,13 +94,7 @@
         mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength(
                 dp, mContext, TEMP_RECT,
                 mTaskViewSimulator.getOrientationState().getOrientationHandler());
-
-        if (mDeviceState.isFullyGesturalNavMode()) {
-            // We can drag all the way to the top of the screen.
-            mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
-        } else {
-            mDragLengthFactor = 1 + AnimatorControllerWithResistance.TWO_BUTTON_EXTRA_DRAG_FACTOR;
-        }
+        mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
 
         PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
         mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 39751c0..17822e6 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -180,6 +180,7 @@
         boolean parallaxCenterAndAdjacentTask =
                 taskIndex != recentsView.getCurrentPage() && !(dp.isTablet
                         && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
+        float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
         int startScroll = recentsView.getScrollOffset(taskIndex);
 
         TaskViewSimulator topMostSimulator = null;
@@ -196,6 +197,8 @@
             tsv.setPreview(targets.apps[targets.apps.length - 1]);
             tsv.fullScreenProgress.value = 0;
             tsv.recentsViewScale.value = 1;
+            tsv.gridProgress.value = 1;
+            tsv.gridTranslationSecondary.value = gridTranslationSecondary;
             tsv.setScroll(startScroll);
 
             // Fade in the task during the initial 20% of the animation
@@ -208,6 +211,7 @@
                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
             out.setFloat(tsv.recentsViewScale,
                     AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
+            out.setFloat(tsv.gridProgress, AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
             out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
 
             TaskViewSimulator finalTsv = tsv;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 8f2356c..13f6137 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -24,9 +24,11 @@
 import android.content.Context;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.FallbackActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
@@ -120,6 +122,10 @@
         // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
         // track the index of the next task appropriately, as if we are switching on any other app.
         if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId && !tasks.isEmpty()) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED,
+                        "FallbackRecentsView.applyLoadPlan: running task is home");
+            }
             // Check if the task list has running task
             boolean found = false;
             for (Task t : tasks) {
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index deb70e0..7f94839 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
@@ -38,7 +37,6 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -49,12 +47,6 @@
  */
 public class AnimatorControllerWithResistance {
 
-    /**
-     * How much farther we can drag past overview in 2-button mode, as a factor of the distance
-     * it takes to drag from an app to overview.
-     */
-    public static final float TWO_BUTTON_EXTRA_DRAG_FACTOR = 0.25f;
-
     private enum RecentsResistanceParams {
         FROM_APP(0.75f, 0.5f, 1f),
         FROM_OVERVIEW(1f, 0.75f, 0.5f);
@@ -161,12 +153,6 @@
         LauncherActivityInterface.INSTANCE.calculateTaskSize(params.context, params.dp, startRect,
                 orientationHandler);
         long distanceToCover = startRect.bottom;
-        boolean isTwoButtonMode = SysUINavigationMode.getMode(params.context) == TWO_BUTTONS;
-        if (isTwoButtonMode) {
-            // We can only drag a small distance past overview, not to the top of the screen.
-            distanceToCover = (long)
-                    ((params.dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR);
-        }
         PendingAnimation resistAnim = params.resistAnim != null
                 ? params.resistAnim
                 : new PendingAnimation(distanceToCover * 2);
@@ -178,43 +164,35 @@
                 / (params.dp.heightPx - startRect.bottom);
         // This is what the scale would be at the end of the drag if we didn't apply resistance.
         float endScale = params.startScale - prevScaleRate * distanceToCover;
-        final TimeInterpolator scaleInterpolator;
-        if (isTwoButtonMode) {
-            // We are bounded by the distance of the drag, so we don't need to apply resistance.
-            scaleInterpolator = LINEAR;
-        } else {
-            // Create an interpolator that resists the scale so the scale doesn't get smaller than
-            // RECENTS_SCALE_MAX_RESIST.
-            float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
-                    params.startScale, endScale);
-            float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
-                    params.startScale, endScale);
-            scaleInterpolator = t -> {
-                if (t < startResist) {
-                    return t;
-                }
-                float resistProgress = Utilities.getProgress(t, startResist, 1);
-                resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
-                return startResist + resistProgress * (maxResist - startResist);
-            };
-        }
+        // Create an interpolator that resists the scale so the scale doesn't get smaller than
+        // RECENTS_SCALE_MAX_RESIST.
+        float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
+                params.startScale, endScale);
+        float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
+                params.startScale, endScale);
+        final TimeInterpolator scaleInterpolator = t -> {
+            if (t < startResist) {
+                return t;
+            }
+            float resistProgress = Utilities.getProgress(t, startResist, 1);
+            resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
+            return startResist + resistProgress * (maxResist - startResist);
+        };
         resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
                 scaleInterpolator);
 
-        if (!isTwoButtonMode) {
-            // Compute where the task view would be based on the end scale, if we didn't translate.
-            RectF endRectF = new RectF(startRect);
-            Matrix temp = new Matrix();
-            temp.setScale(params.resistanceParams.scaleMaxResist,
-                    params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
-            temp.mapRect(endRectF);
-            // Translate such that the task view touches the top of the screen when drag does.
-            float endTranslation = endRectF.top
-                    * orientationHandler.getSecondaryTranslationDirectionFactor()
-                    * params.resistanceParams.translationFactor;
-            resistAnim.addFloat(params.translationTarget, params.translationProperty,
-                    params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
-        }
+        // Compute where the task view would be based on the end scale.
+        RectF endRectF = new RectF(startRect);
+        Matrix temp = new Matrix();
+        temp.setScale(params.resistanceParams.scaleMaxResist,
+                params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
+        temp.mapRect(endRectF);
+        // Translate such that the task view touches the top of the screen when drag does.
+        float endTranslation = endRectF.top
+                * orientationHandler.getSecondaryTranslationDirectionFactor()
+                * params.resistanceParams.translationFactor;
+        resistAnim.addFloat(params.translationTarget, params.translationProperty,
+                params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
 
         return resistAnim;
     }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 584a284..9537247 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -16,7 +16,6 @@
 package com.android.quickstep.util;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
@@ -98,6 +97,7 @@
     private final FullscreenDrawParams mCurrentFullscreenParams;
     public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
     public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
+    public final AnimatedFloat gridTranslationSecondary = new AnimatedFloat();
 
     // RecentsView properties
     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
@@ -317,9 +317,10 @@
         mMatrix.postScale(scale, scale, mIsRecentsRtl ? 0 : taskWidth, 0);
         float taskWidthDiff = taskWidth * (1 - gridScale);
         float taskWidthOffset = mIsRecentsRtl ? taskWidthDiff : -taskWidthDiff;
-        float translationPrimary = Utilities.mapRange(interpolatedGridProgress, 0, taskWidthOffset);
         mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
-                translationPrimary);
+                Utilities.mapRange(interpolatedGridProgress, 0, taskWidthOffset));
+        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
+                Utilities.mapRange(interpolatedGridProgress, 0, gridTranslationSecondary.value));
 
         // Apply TaskView matrix: translate, scroll
         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
diff --git a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
deleted file mode 100644
index 8210ab0..0000000
--- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package com.android.quickstep.views;
-
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.util.FloatProperty;
-import android.view.ViewOverlay;
-
-import com.android.launcher3.anim.Interpolators;
-import com.android.quickstep.util.RecentsOrientedState.SurfaceRotation;
-
-public class LiveTileOverlay extends Drawable {
-
-    private static final long ICON_ANIM_DURATION = 120;
-
-    private static final FloatProperty<LiveTileOverlay> PROGRESS =
-            new FloatProperty<LiveTileOverlay>("progress") {
-                @Override
-                public void setValue(LiveTileOverlay liveTileOverlay, float progress) {
-                    liveTileOverlay.setIconAnimationProgress(progress);
-                }
-
-                @Override
-                public Float get(LiveTileOverlay liveTileOverlay) {
-                    return liveTileOverlay.mIconAnimationProgress;
-                }
-            };
-
-    public static final LiveTileOverlay INSTANCE = new LiveTileOverlay();
-
-    private final Paint mPaint = new Paint();
-    private final RectF mCurrentRect = new RectF();
-    private final Rect mBoundsRect = new Rect();
-
-    private @SurfaceRotation int mRotation = ROTATION_0;
-
-    private float mCornerRadius;
-    private Drawable mIcon;
-    private Animator mIconAnimator;
-
-    private float mIconAnimationProgress = 0f;
-    private boolean mIsAttached;
-
-    private LiveTileOverlay() {
-        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-    }
-
-    public void update(RectF currentRect, float cornerRadius) {
-        invalidateSelf();
-
-        mCurrentRect.set(currentRect);
-        mCornerRadius = cornerRadius;
-
-        mCurrentRect.roundOut(mBoundsRect);
-        setBounds(mBoundsRect);
-        invalidateSelf();
-    }
-
-    public void update(float left, float top, float right, float bottom) {
-        mCurrentRect.set(left, top, right, bottom);
-    }
-
-    public void setRotation(@SurfaceRotation int rotation) {
-        mRotation = rotation;
-    }
-
-    public void setIcon(Drawable icon) {
-        mIcon = icon;
-    }
-
-    // TODO: consider cleaning this up and drawing icon in another way. Previously we place app
-    // below launcher during the initial swipe up and render the icon in this live tile overlay.
-    // However, this resulted in a bunch of touch input issues caused by Launcher getting the input
-    // events during transition (to overview / to another app (quick switch). So now our new
-    // solution places app on top in live tile until it fully settles in Overview.
-    public void startIconAnimation() {
-        if (mIconAnimator != null) {
-            mIconAnimator.cancel();
-        }
-        // This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation.
-        mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1);
-        mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR);
-        mIconAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mIconAnimator = null;
-            }
-        });
-        mIconAnimator.start();
-    }
-
-    public float cancelIconAnimation() {
-        if (mIconAnimator != null) {
-            mIconAnimator.cancel();
-        }
-        return mIconAnimationProgress;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
-        if (mIcon != null && mIconAnimationProgress > 0f) {
-            canvas.save();
-            float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
-                    1f).getInterpolation(mIconAnimationProgress);
-
-            int iconRadius = mIcon.getBounds().width() / 2;
-            float dx = 0;
-            float dy = 0;
-
-            switch (mRotation) {
-                case ROTATION_0:
-                    dx = mCurrentRect.centerX() - iconRadius * scale;
-                    dy = mCurrentRect.top - iconRadius * scale;
-                    break;
-                case ROTATION_90:
-                    dx = mCurrentRect.right - iconRadius * scale;
-                    dy = mCurrentRect.centerY() - iconRadius * scale;
-                    break;
-                case ROTATION_270:
-                    dx = mCurrentRect.left - iconRadius * scale;
-                    dy = mCurrentRect.centerY() - iconRadius * scale;
-                    break;
-                case ROTATION_180:
-                    dx = mCurrentRect.centerX() - iconRadius * scale;
-                    dy = mCurrentRect.bottom - iconRadius * scale;
-                    break;
-            }
-
-            int rotationDegrees = mRotation * 90;
-            if (mRotation == ROTATION_90 || mRotation == ROTATION_270) {
-                canvas.rotate(rotationDegrees, dx + iconRadius, dy + iconRadius);
-            }
-            canvas.translate(dx, dy);
-            canvas.scale(scale, scale);
-            mIcon.draw(canvas);
-            canvas.restore();
-        }
-    }
-
-    @Override
-    public void setAlpha(int i) { }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) { }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    public boolean attach(ViewOverlay overlay) {
-        if (overlay != null && !mIsAttached) {
-            overlay.add(this);
-            mIsAttached = true;
-            return true;
-        }
-
-        return false;
-    }
-
-    public void detach(ViewOverlay overlay) {
-        if (overlay != null) {
-            overlay.remove(this);
-            mIsAttached = false;
-        }
-    }
-
-    private void setIconAnimationProgress(float progress) {
-        mIconAnimationProgress = progress;
-        invalidateSelf();
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 78910ce..f20ca82 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -72,6 +72,7 @@
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -104,6 +105,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
 import com.android.launcher3.util.DynamicResource;
@@ -420,7 +422,6 @@
     private boolean mShowEmptyMessage;
     private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener;
     private Layout mEmptyTextLayout;
-    private boolean mLiveTileOverlayAttached;
 
     // Keeps track of the index where the first TaskView should be
     private int mTaskViewStartIndex = 0;
@@ -823,6 +824,12 @@
     }
 
     protected void applyLoadPlan(ArrayList<Task> tasks) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "applyLoadPlan: taskCount=" + tasks.size());
+            for (Task t : tasks) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "\t" + t);
+            }
+        }
         if (mPendingAnimation != null) {
             mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks));
             return;
@@ -884,12 +891,21 @@
         resetTaskVisuals();
         onTaskStackUpdated();
         updateEnabledOverlays();
+
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "applyLoadPlan: taskViewCount="
+                    + getTaskViewCount());
+        }
     }
 
     private boolean isModal() {
         return mTaskModalness > 0;
     }
 
+    public boolean isLoadingTasks() {
+        return mModel.isLoadingTasksInBackground();
+    }
+
     private void removeTasksViewsAndClearAllButton() {
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
             removeView(getTaskViewAt(i));
@@ -900,6 +916,12 @@
     }
 
     public int getTaskViewCount() {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "getTaskViewCount:"
+                    + " numChildren=" + getChildCount()
+                    + " start=" + mTaskViewStartIndex
+                    + " clearAll=" + indexOfChild(mClearAllButton));
+        }
         int taskViewCount = getChildCount() - mTaskViewStartIndex;
         if (indexOfChild(mClearAllButton) != -1) {
             taskViewCount--;
@@ -926,12 +948,10 @@
             // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
             // to reset the params after it settles in Overview from swipe up so that we don't
             // render with obsolete param values.
+            mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = 0;
+            mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
             mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
             mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
-
-            // Reset the live tile rect
-            DeviceProfile deviceProfile = mActivity.getDeviceProfile();
-            LiveTileOverlay.INSTANCE.update(0, 0, deviceProfile.widthPx, deviceProfile.heightPx);
         }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1264,10 +1284,7 @@
      */
     public void onSwipeUpAnimationSuccess() {
         if (getRunningTaskView() != null) {
-            float startProgress = LIVE_TILE.get() && mLiveTileOverlayAttached
-                    ? LiveTileOverlay.INSTANCE.cancelIconAnimation()
-                    : 0f;
-            animateUpRunningTaskIconScale(startProgress);
+            animateUpRunningTaskIconScale(0f);
         }
         setSwipeDownShouldLaunchApp(true);
     }
@@ -2589,16 +2606,6 @@
         }
     }
 
-    public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
-        mLiveTileOverlayAttached = liveTileOverlayAttached;
-    }
-
-    public void updateLiveTileIcon(Drawable icon) {
-        if (mLiveTileOverlayAttached) {
-            LiveTileOverlay.INSTANCE.setIcon(icon);
-        }
-    }
-
     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
         if (mRecentsAnimationController == null) {
             if (onFinishComplete != null) {
@@ -2729,7 +2736,7 @@
     }
 
     /**
-     * @return How many pixels the page is offset on the currently laid out dominant axis.
+     * Returns how many pixels the page is offset on the currently laid out dominant axis.
      */
     public int getScrollOffset(int pageIndex) {
         if (pageIndex == -1) {
@@ -2745,6 +2752,20 @@
         return getScrollForPage(pageIndex) - scroll;
     }
 
+    /**
+     * Returns how many pixels the task is offset on the currently laid out secondary axis
+     * according to {@link #mGridProgress}.
+     */
+    public float getGridTranslationSecondary(int pageIndex) {
+        TaskView taskView = getTaskViewAtByAbsoluteIndex(pageIndex);
+        if (taskView == null) {
+            return 0;
+        }
+
+        return mOrientationHandler.getSecondaryValue(taskView.getGridTranslationX(),
+                taskView.getGridTranslationY());
+    }
+
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
         float degreesRotated;
         if (navbarRotation == 0) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 3fb59983..0cf7261 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -559,9 +559,6 @@
             mIconLoadRequest = iconCache.updateIconInBackground(mTask,
                     (task) -> {
                         setIcon(task.icon);
-                        if (LIVE_TILE.get() && isRunningTask()) {
-                            getRecentsView().updateLiveTileIcon(task.icon);
-                        }
                         mDigitalWellBeingToast.initialize(mTask);
                     });
         } else {
@@ -919,11 +916,19 @@
         applyTranslationX();
     }
 
+    public float getGridTranslationX() {
+        return mGridTranslationX;
+    }
+
     public void setGridTranslationY(float gridTranslationY) {
         mGridTranslationY = gridTranslationY;
         applyTranslationY();
     }
 
+    public float getGridTranslationY() {
+        return mGridTranslationY;
+    }
+
     public void setGridOffsetTranslationX(float gridOffsetTranslationX) {
         mGridOffsetTranslationX = gridOffsetTranslationX;
         applyTranslationX();
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 2e7e6e0..713fd07 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -42,6 +42,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.RemoteException;
+import android.util.Log;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +56,7 @@
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.views.RecentsView;
@@ -172,9 +174,15 @@
 
     protected <T> T getFromRecents(Function<RecentsActivity, T> f) {
         if (!TestHelpers.isInLauncherProcess()) return null;
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.GET_RECENTS_FAILED, "getFromRecents");
+        }
         Object[] result = new Object[1];
         Wait.atMost("Failed to get from recents", () -> MAIN_EXECUTOR.submit(() -> {
             RecentsActivity activity = RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "activity=" + activity);
+            }
             if (activity == null) {
                 return false;
             }
@@ -200,8 +208,13 @@
                 () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
 
         BaseOverview overview = mLauncher.getBackground().switchToOverview();
-        executeOnRecents(recents ->
-                assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3));
+        executeOnRecents(recents -> {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.GET_RECENTS_FAILED, "isLoading=" +
+                        recents.<RecentsView>getOverviewPanel().isLoadingTasks());
+            }
+            assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3);
+        });
 
         // Test flinging forward and backward.
         overview.flingForward();
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index faff10c..041e007 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -52,7 +52,7 @@
             android:id="@+id/app_subtitle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            tools:text="n widgets" />
+            tools:text="m widgets, n shortcuts" />
 
     </LinearLayout>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 73f9e53..44b5ee7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -52,11 +52,22 @@
     <string name="add_item_request_drag_hint">Touch &amp; hold to place manually</string>
     <!-- Button label to automatically add icon on home screen [CHAR_LIMIT=50] -->
     <string name="place_automatically">Add automatically</string>
-    <!-- Label for showing the number of widgets an app has in the full widgets picker. [CHAR_LIMIT=25] -->
-    <plurals name="widgets_tray_subtitle">
-        <item quantity="one"><xliff:g id="widget_count" example="1">%1$d</xliff:g> widget</item>
-        <item quantity="other"><xliff:g id="widget_count" example="2">%1$d</xliff:g> widgets</item>
+    <!-- Label for showing the number of widgets an app has in the full widgets picker.
+         [CHAR_LIMIT=25] -->
+    <plurals name="widgets_count">
+        <item quantity="one"><xliff:g id="widgets_count" example="1">%1$d</xliff:g> widget</item>
+        <item quantity="other"><xliff:g id="widgets_count" example="2">%1$d</xliff:g> widgets</item>
     </plurals>
+    <!-- Label for showing the number of shortcut an app has in the full widgets picker.
+         [CHAR_LIMIT=25] -->
+    <plurals name="shortcuts_count">
+        <item quantity="one"><xliff:g id="shortcuts_count" example="1">%1$d</xliff:g> shortcut</item>
+        <item quantity="other"><xliff:g id="shortcuts_count" example="2">%1$d</xliff:g> shortcuts</item>
+    </plurals>
+    <!-- Label for showing both the number of widgets and shortcuts an app has in the full widgets
+         picker. [CHAR_LIMIT=50] -->
+    <string name="widgets_and_shortcuts_count"><xliff:g id="widgets_count" example="5 widgets">%1$s</xliff:g>, <xliff:g id="shortcuts_count" example="1 shortcut">%2$s</xliff:g></string>
+
     <!-- Text for both the tile of a popup view, which shows all available widgets installed on
          the device, and the text of a button, which opens this popup view. [CHAR LIMIT=30]-->
     <string name="widget_button_text">Widgets</string>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index a813275..3b28d4d 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -12,12 +12,11 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.util.AttributeSet;
+import android.util.SizeF;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -368,7 +367,7 @@
 
     public static void updateWidgetSizeRanges(AppWidgetHostView widgetView, Launcher launcher,
                                               int spanX, int spanY) {
-        List<PointF> sizes = getWidgetSizes(launcher, spanX, spanY);
+        List<SizeF> sizes = getWidgetSizes(launcher, spanX, spanY);
         if (ATLEAST_S) {
             widgetView.updateAppWidgetSize(new Bundle(), sizes);
         } else {
@@ -378,43 +377,25 @@
         }
     }
 
-    private static PointF getWidgetSize(Context context, Point cellSize, int spanX, int spanY,
+    private static SizeF getWidgetSize(Context context, Point cellSize, int spanX, int spanY,
             int borderSpacing) {
         final float density = context.getResources().getDisplayMetrics().density;
         final float hBorderSpacing = (spanX - 1) * borderSpacing;
         final float vBorderSpacing = (spanY - 1) * borderSpacing;
 
-        PointF widgetSize = new PointF();
-        widgetSize.x = ((spanX * cellSize.x) + hBorderSpacing) / density;
-        widgetSize.y = ((spanY * cellSize.y) + vBorderSpacing) / density;
-        return widgetSize;
-    }
-
-    /** Returns the actual widget size given its span. */
-    public static PointF getWidgetSize(Context context, int spanX, int spanY) {
-        final Point[] cellSize = CELL_SIZE.get(context);
-        final int[] borderSpacing = BORDER_SPACING_SIZE.get(context);
-        if (isLandscape(context)) {
-            return getWidgetSize(context, cellSize[0], spanX, spanY, borderSpacing[0]);
-        }
-        return getWidgetSize(context, cellSize[1], spanX, spanY, borderSpacing[1]);
-    }
-
-    /** Returns true if the screen is in landscape mode. */
-    private static boolean isLandscape(Context context) {
-        return context.getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
+        return new SizeF(((spanX * cellSize.x) + hBorderSpacing) / density,
+                ((spanY * cellSize.y) + vBorderSpacing) / density);
     }
 
     /** Returns the list of sizes for a widget of given span, in dp. */
-    public static ArrayList<PointF> getWidgetSizes(Context context, int spanX, int spanY) {
+    public static ArrayList<SizeF> getWidgetSizes(Context context, int spanX, int spanY) {
         final Point[] cellSize = CELL_SIZE.get(context);
         final int[] borderSpacing = BORDER_SPACING_SIZE.get(context);
 
-        PointF landSize = getWidgetSize(context, cellSize[0], spanX, spanY, borderSpacing[0]);
-        PointF portSize = getWidgetSize(context, cellSize[1], spanX, spanY,  borderSpacing[1]);
+        SizeF landSize = getWidgetSize(context, cellSize[0], spanX, spanY, borderSpacing[0]);
+        SizeF portSize = getWidgetSize(context, cellSize[1], spanX, spanY, borderSpacing[1]);
 
-        ArrayList<PointF> sizes = new ArrayList<>(2);
+        ArrayList<SizeF> sizes = new ArrayList<>(2);
         sizes.add(landSize);
         sizes.add(portSize);
         return sizes;
@@ -430,17 +411,18 @@
      * the right (resp. bottom) for the max. The returned rectangle is set with 0s if the list is
      * empty.
      */
-    public static Rect getMinMaxSizes(List<PointF> sizes, @Nullable Rect outRect) {
+    public static Rect getMinMaxSizes(List<SizeF> sizes, @Nullable Rect outRect) {
         if (outRect == null) {
             outRect = new Rect();
         }
         if (sizes.isEmpty()) {
             outRect.set(0, 0, 0, 0);
         } else {
-            PointF first = sizes.get(0);
-            outRect.set((int) first.x, (int) first.y, (int) first.x, (int) first.y);
+            SizeF first = sizes.get(0);
+            outRect.set((int) first.getWidth(), (int) first.getHeight(), (int) first.getWidth(),
+                    (int) first.getHeight());
             for (int i = 1; i < sizes.size(); i++) {
-                outRect.union((int) sizes.get(i).x, (int) sizes.get(i).y);
+                outRect.union((int) sizes.get(i).getWidth(), (int) sizes.get(i).getHeight());
             }
         }
         return outRect;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index ebaacb6..b2112ad 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -166,4 +167,11 @@
     protected void showInlineQsb() {
         //Does nothing
     }
+
+    /**
+     * Returns the first View for which the given itemOperator returns true, or null.
+     */
+    public View getFirstItemMatch(Workspace.ItemOperator itemOperator) {
+        return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 253a7c7..fa63885 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -121,6 +121,7 @@
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
@@ -2863,6 +2864,10 @@
         return false;
     }
 
+    public DragOptions getDefaultWorkspaceDragOptions() {
+        return new DragOptions();
+    }
+
     private static class NonConfigInstance {
         public Configuration config;
         public Bitmap snapshot;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 77fee08..87fb6fb 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2973,7 +2973,7 @@
      * @param operators List of operators, in order starting from best matching operator.
      * @return
      */
-    private View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator... operators) {
+    View getFirstMatch(CellLayout[] cellLayouts, final ItemOperator... operators) {
         // This array is filled with the first match for each operator.
         final View[] matches = new View[operators.length];
         // For efficiency, the outer loop should be CellLayout.
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 8016b2d..6b9ed09 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,9 +16,6 @@
 
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-
-import android.content.Context;
 import android.graphics.Path;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
@@ -67,9 +64,6 @@
      */
     public static final Interpolator FINAL_FRAME = t -> t < 1 ? 0 : 1;
 
-    private static final int MIN_SETTLE_DURATION = 200;
-    private static final float OVERSHOOT_FACTOR = 0.9f;
-
     static {
         Path exaggeratedEase = new Path();
         exaggeratedEase.moveTo(0, 0);
@@ -175,76 +169,4 @@
             float upperBound) {
         return t -> Utilities.mapRange(interpolator.getInterpolation(t), lowerBound, upperBound);
     }
-
-    /**
-     * Computes parameters necessary for an overshoot effect.
-     */
-    public static class OvershootParams {
-        public Interpolator interpolator;
-        public float start;
-        public float end;
-        public long duration;
-
-        /**
-         * Given the input params, sets OvershootParams variables to be used by the caller.
-         * @param startProgress The progress from 0 to 1 that the overshoot starts from.
-         * @param overshootPastProgress The progress from 0 to 1 where we overshoot past (should
-         *        either be equal to startProgress or endProgress, depending on if we want to
-         *        overshoot immediately or only once we reach the end).
-         * @param endProgress The final progress from 0 to 1 that we will settle to.
-         * @param velocityPxPerMs The initial velocity that causes this overshoot.
-         * @param totalDistancePx The distance against which progress is calculated.
-         */
-        public OvershootParams(float startProgress, float overshootPastProgress,
-                float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) {
-            velocityPxPerMs = Math.abs(velocityPxPerMs);
-            overshootPastProgress = Math.max(overshootPastProgress, startProgress);
-            start = startProgress;
-            int startPx = (int) (start * totalDistancePx);
-            // Overshoot by about half a frame.
-            float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs *
-                    getSingleFrameMs(context) / totalDistancePx / 2;
-            overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
-            end = overshootPastProgress + overshootBy;
-            int endPx = (int) (end  * totalDistancePx);
-            int overshootDistance = endPx - startPx;
-            // Calculate deceleration necessary to reach overshoot distance.
-            // Formula: velocityFinal^2 = velocityInitial^2 + 2 * acceleration * distance
-            //          0 = v^2 + 2ad (velocityFinal == 0)
-            //          a = v^2 / -2d
-            float decelerationPxPerMs = velocityPxPerMs * velocityPxPerMs / (2 * overshootDistance);
-            // Calculate time necessary to reach peak of overshoot.
-            // Formula: acceleration = velocity / time
-            //          time = velocity / acceleration
-            duration = (long) (velocityPxPerMs / decelerationPxPerMs);
-
-            // Now that we're at the top of the overshoot, need to settle back to endProgress.
-            float settleDistance = end - endProgress;
-            int settleDistancePx = (int) (settleDistance * totalDistancePx);
-            // Calculate time necessary for the settle.
-            // Formula: distance = velocityInitial * time + 1/2 * acceleration * time^2
-            //          d = 1/2at^2 (velocityInitial = 0, since we just stopped at the top)
-            //          t = sqrt(2d/a)
-            // Above formula assumes constant acceleration. Since we use ACCEL_DEACCEL, we actually
-            // have acceleration to halfway then deceleration the rest. So the formula becomes:
-            //          t = sqrt(d/a) * 2 (half the distance for accel, half for deaccel)
-            long settleDuration = (long) Math.sqrt(settleDistancePx / decelerationPxPerMs) * 4;
-
-            settleDuration = Math.max(MIN_SETTLE_DURATION, settleDuration);
-            // How much of the animation to devote to playing the overshoot (the rest is for settle).
-            float overshootFraction = (float) duration / (duration + settleDuration);
-            duration += settleDuration;
-            // Finally, create the interpolator, composed of two interpolators: an overshoot, which
-            // reaches end > 1, and then a settle to endProgress.
-            Interpolator overshoot = Interpolators.clampToProgress(DEACCEL, 0, overshootFraction);
-            // The settle starts at 1, where 1 is the top of the overshoot, and maps to a fraction
-            // such that final progress is endProgress. For example, if we overshot to 1.1 but want
-            // to end at 1, we need to map to 1/1.1.
-            Interpolator settle = Interpolators.clampToProgress(Interpolators.mapToProgress(
-                    ACCEL_DEACCEL, 1, (endProgress - start) / (end - start)), overshootFraction, 1);
-            interpolator = t -> t <= overshootFraction
-                    ? overshoot.getInterpolation(t)
-                    : settle.getInterpolation(t);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 209c5be..f6c7c06 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -192,6 +192,16 @@
             loadWorkspace(allShortcuts);
             logger.addSplit("loadWorkspace");
 
+            // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
+            // sanitizeData should not be invoked if the workspace is loaded from a db different
+            // from the main db as defined in the invariant device profile.
+            // (e.g. both grid preview and minimal device mode uses a different db)
+            if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
+                verifyNotStopped();
+                sanitizeData();
+                logger.addSplit("sanitizeData");
+            }
+
             verifyNotStopped();
             mResults.bindWorkspace();
             logger.addSplit("bindWorkspace");
@@ -274,14 +284,6 @@
                 loadFolderNames();
             }
 
-            // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
-            // sanitizeData should not be invoked if the workspace is loaded from a db different
-            // from the main db as defined in the invariant device profile.
-            // (e.g. both grid preview and minimal device mode uses a different db)
-            if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
-                sanitizeData();
-            }
-
             verifyNotStopped();
             updateHandler.finish();
             logger.addSplit("finish icon update");
@@ -875,10 +877,11 @@
                     mBgDataModel.itemsIdMap.remove(folderId);
                 }
             }
-            // Remove any ghost widgets
-            LauncherSettings.Settings.call(contentResolver,
-                    LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
+
         }
+        // Remove any ghost widgets
+        LauncherSettings.Settings.call(contentResolver,
+                LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
 
         // Update pinned state of model shortcuts
         mBgDataModel.updateShortcutPinnedState(context);
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 459aefe..a191df4 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -32,11 +32,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.AttributeSet;
+import android.util.SizeF;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -299,7 +299,7 @@
             InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
 
             Bundle opts = new Bundle();
-            ArrayList<PointF> sizes = AppWidgetResizeFrame
+            ArrayList<SizeF> sizes = AppWidgetResizeFrame
                     .getWidgetSizes(getContext(), idp.numColumns, 1);
             Rect size = AppWidgetResizeFrame.getMinMaxSizes(sizes, null /* outRect */);
             opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index beb5b68..a18f340 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -302,10 +302,6 @@
     }
 
     private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "createAnimationToNewWorkspaceInternal: "
-                    + state);
-        }
         PendingAnimation builder = new PendingAnimation(mConfig.duration);
         if (mConfig.getAnimComponents() != 0) {
             for (StateHandler handler : getStateHandlers()) {
@@ -328,9 +324,6 @@
 
             @Override
             public void onAnimationSuccess(Animator animator) {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "onAnimationSuccess: " + state);
-                }
                 onStateTransitionEnd(state);
             }
         };
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 72bbc43..7cb6e34 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -103,8 +103,8 @@
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
-    public static final String OVERIEW_NOT_ALLAPPS = "b/156095088";
     public static final String NO_SWIPE_TO_HOME = "b/158017601";
     public static final String WORK_PROFILE_REMOVED = "b/159671700";
     public static final String TIS_NO_EVENTS = "b/180915942";
+    public static final String GET_RECENTS_FAILED = "b/177472267";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index b28d6f7..6f1b2f9 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -202,10 +202,6 @@
 
         mFromState = newFromState;
         mToState = newToState;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS, "reinitCurrentAnimation: "
-                    + newToState.ordinal + " " + getClass().getSimpleName());
-        }
 
         mStartProgress = 0;
         mPassedOverviewAtomicThreshold = false;
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 919673f..f876dd9 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -57,7 +57,7 @@
         if (!(v.getTag() instanceof ItemInfo)) return false;
 
         launcher.setWaitingForResult(null);
-        beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
+        beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
         return true;
     }
 
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 54e51b2..3285c18 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -16,12 +16,9 @@
 
 package com.android.launcher3.widget;
 
-import static com.android.launcher3.Utilities.ATLEAST_S;
-
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.PointF;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.SparseBooleanArray;
@@ -219,16 +216,6 @@
     }
 
     @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-
-        if (ATLEAST_S) {
-            float density = getContext().getResources().getDisplayMetrics().density;
-            setCurrentSize(new PointF(w / density, h / density));
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         info.setClassName(getClass().getName());
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 8c3206d..4b113d8 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -30,6 +29,7 @@
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
+import android.util.SizeF;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
 import android.view.View;
@@ -112,7 +112,7 @@
     }
 
     @Override
-    public void updateAppWidgetSize(Bundle newOptions, List<PointF> sizes) {
+    public void updateAppWidgetSize(Bundle newOptions, List<SizeF> sizes) {
         // No-op
     }
 
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 31cbff6..12e0d43 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -3,11 +3,11 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.Log;
+import android.util.SizeF;
 import android.view.View;
 
 import com.android.launcher3.AppWidgetResizeFrame;
@@ -19,6 +19,7 @@
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
+import java.util.stream.Collectors;
 
 public class WidgetHostViewLoader implements DragController.DragListener {
     private static final String TAG = "WidgetHostViewLoader";
@@ -154,7 +155,7 @@
     }
 
     public static Bundle getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info) {
-        ArrayList<PointF> sizes = AppWidgetResizeFrame
+        ArrayList<SizeF> sizes = AppWidgetResizeFrame
                 .getWidgetSizes(context, info.spanX, info.spanY);
 
         Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
@@ -163,19 +164,19 @@
         float xPaddingDips = (padding.left + padding.right) / density;
         float yPaddingDips = (padding.top + padding.bottom) / density;
 
-        for (PointF size : sizes) {
-            size.x = Math.max(0.f, size.x - xPaddingDips);
-            size.y = Math.max(0.f, size.y - yPaddingDips);
-        }
+        ArrayList<SizeF> paddedSizes = sizes.stream().map(
+                size -> new SizeF(Math.max(0.f, size.getWidth() - xPaddingDips),
+                        Math.max(0.f, size.getHeight() - yPaddingDips))).collect(
+                Collectors.toCollection(ArrayList::new));
 
-        Rect rect = AppWidgetResizeFrame.getMinMaxSizes(sizes, null /* outRect */);
+        Rect rect = AppWidgetResizeFrame.getMinMaxSizes(paddedSizes, null /* outRect */);
 
         Bundle options = new Bundle();
         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, rect.left);
         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, rect.top);
         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, rect.right);
         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, rect.bottom);
-        options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, sizes);
+        options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes);
         return options;
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index a5ed20a..95fa05f 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -16,10 +16,10 @@
 package com.android.launcher3.widget.picker;
 
 import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.RelativeLayout;
 
 import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.widget.picker.WidgetsFullSheet.SearchAndRecommendationViewHolder;
@@ -33,20 +33,21 @@
         RecyclerViewFastScroller.OnFastScrollChangeListener {
     private final boolean mHasWorkProfile;
     private final SearchAndRecommendationViewHolder mViewHolder;
-    private final RecyclerView mPrimaryRecyclerView;
+    private final WidgetsRecyclerView mPrimaryRecyclerView;
 
     // The following are only non null if mHasWorkProfile is true.
-    @Nullable private final RecyclerView mWorkRecyclerView;
+    @Nullable private final WidgetsRecyclerView mWorkRecyclerView;
     @Nullable private final View mPrimaryWorkTabsView;
     @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager;
 
+    private WidgetsRecyclerView mCurrentRecyclerView;
     private int mMaxCollapsibleHeight = 0;
 
     SearchAndRecommendationsScrollController(
             boolean hasWorkProfile,
             SearchAndRecommendationViewHolder viewHolder,
-            RecyclerView primaryRecyclerView,
-            @Nullable RecyclerView workRecyclerView,
+            WidgetsRecyclerView primaryRecyclerView,
+            @Nullable WidgetsRecyclerView workRecyclerView,
             @Nullable View personalWorkTabsView,
             @Nullable PersonalWorkPagedView primaryWorkViewPager) {
         mHasWorkProfile = hasWorkProfile;
@@ -55,6 +56,12 @@
         mWorkRecyclerView = workRecyclerView;
         mPrimaryWorkTabsView = personalWorkTabsView;
         mPrimaryWorkViewPager = primaryWorkViewPager;
+        mCurrentRecyclerView = mPrimaryRecyclerView;
+    }
+
+    /** Sets the current active {@link WidgetsRecyclerView}. */
+    public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) {
+        mCurrentRecyclerView = currentRecyclerView;
     }
 
     /**
@@ -64,10 +71,10 @@
         // The maximum vertical distance, in pixels, until the last collapsible element is not
         // visible from the screen when the user scrolls down the recycler view.
         mMaxCollapsibleHeight = mViewHolder.mContainer.getPaddingTop()
-                + mViewHolder.mCollapseHandle.getMeasuredHeight()
-                + mViewHolder.mHeaderTitle.getMeasuredHeight();
+                + measureHeightWithVerticalMargins(mViewHolder.mCollapseHandle)
+                + measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle);
 
-        int topContainerHeight = mViewHolder.mContainer.getMeasuredHeight();
+        int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
         if (mHasWorkProfile) {
             // In a work profile setup, the full widget sheet contains the following views:
             //           -------               -|
@@ -114,8 +121,8 @@
             //
             // When the views are first inflated, the sum of topOffsetAfterAllViewsCollapsed and
             // mMaxCollapsibleDistance should equal to the top container height.
-            int tabsViewActualHeight =
-                    mPrimaryWorkTabsView.getMeasuredHeight() - mPrimaryWorkTabsView.getPaddingTop();
+            int tabsViewActualHeight = measureHeightWithVerticalMargins(mPrimaryWorkTabsView)
+                    - mPrimaryWorkTabsView.getPaddingTop();
             int topOffsetAfterAllViewsCollapsed =
                     topContainerHeight + tabsViewActualHeight - mMaxCollapsibleHeight;
 
@@ -149,9 +156,12 @@
      * views (e.g. recycler views, tabs) upon scrolling.
      */
     @Override
-    public void onThumbOffsetYChanged(int y) {
+    public void onThumbOffsetYChanged(int unused) {
+        // Always use the recycler view offset because fast scroller offset has a different scale.
+        int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY();
+        if (recyclerViewYOffset < 0) return;
         if (mMaxCollapsibleHeight > 0) {
-            int yDisplacement = Math.max(-y, -mMaxCollapsibleHeight);
+            int yDisplacement = Math.max(-recyclerViewYOffset, -mMaxCollapsibleHeight);
             mViewHolder.mHeaderTitle.setTranslationY(yDisplacement);
             mViewHolder.mSearchBar.setTranslationY(yDisplacement);
             if (mHasWorkProfile) {
@@ -159,4 +169,20 @@
             }
         }
     }
+
+    /** Resets any previous view translation. */
+    public void reset() {
+        mViewHolder.mHeaderTitle.setTranslationY(0);
+        mViewHolder.mSearchBar.setTranslationY(0);
+        if (mHasWorkProfile) {
+            mPrimaryWorkTabsView.setTranslationY(0);
+        }
+    }
+
+    /** private the height, in pixel, + the vertical margins of a given view. */
+    private static int measureHeightWithVerticalMargins(View view) {
+        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
+        return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
+                + marginLayoutParams.topMargin;
+    }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 91b79f9..52a2fc5 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -149,7 +149,10 @@
 
     @Override
     public void onActivePageChanged(int currentActivePage) {
-        mAdapters.get(currentActivePage).mWidgetsRecyclerView.bindFastScrollbar();
+        WidgetsRecyclerView currentRecyclerView =
+                mAdapters.get(currentActivePage).mWidgetsRecyclerView;
+        currentRecyclerView.bindFastScrollbar();
+        mSearchAndRecommendationsScrollController.setCurrentRecyclerView(currentRecyclerView);
 
         reset();
     }
@@ -159,6 +162,7 @@
         if (mHasWorkProfile) {
             mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
         }
+        mSearchAndRecommendationsScrollController.reset();
     }
 
     @VisibleForTesting
@@ -241,6 +245,12 @@
             mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
                     maxSpansPerRow);
         }
+
+        if (mInitialTabsHeight == 0 && mTabsView != null) {
+            mInitialTabsHeight = measureHeightWithVerticalMargins(mTabsView);
+        }
+
+        mSearchAndRecommendationsScrollController.updateMarginAndPadding();
     }
 
     @Override
@@ -255,12 +265,6 @@
                 contentLeft + contentWidth, height);
 
         setTranslationShift(mTranslationShift);
-
-        if (mInitialTabsHeight == 0 && mTabsView != null) {
-            mInitialTabsHeight = mTabsView.getMeasuredHeight();
-        }
-
-        mSearchAndRecommendationsScrollController.updateMarginAndPadding();
     }
 
     @Override
@@ -371,7 +375,14 @@
         // No need to check work profile here because mInitialTabHeight is always 0 if there is no
         // work profile.
         return mInitialTabsHeight
-                + mSearchAndRecommendationViewHolder.mContainer.getMeasuredHeight();
+                + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mContainer);
+    }
+
+    /** private the height, in pixel, + the vertical margins of a given view. */
+    private static int measureHeightWithVerticalMargins(View view) {
+        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
+        return view.getMeasuredHeight() + marginLayoutParams.bottomMargin
+                + marginLayoutParams.topMargin;
     }
 
     /** A holder class for holding adapters & their corresponding recycler view. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 823fb7b..070a9aa 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -157,14 +157,29 @@
     private void setTitles(WidgetsListHeaderEntry entry) {
         mTitle.setText(entry.mPkgItem.title);
 
-        if (entry.widgetsCount > 0) {
-            Resources resources = getContext().getResources();
-            mSubtitle.setText(resources.getQuantityString(R.plurals.widgets_tray_subtitle,
-                    entry.widgetsCount, entry.widgetsCount));
-            mSubtitle.setVisibility(VISIBLE);
-        } else {
+        Resources resources = getContext().getResources();
+        if (entry.widgetsCount == 0 && entry.shortcutsCount == 0) {
             mSubtitle.setVisibility(GONE);
+            return;
         }
+
+        String subtitle;
+        if (entry.widgetsCount > 0 && entry.shortcutsCount > 0) {
+            String widgetsCount = resources.getQuantityString(R.plurals.widgets_count,
+                    entry.widgetsCount, entry.widgetsCount);
+            String shortcutsCount = resources.getQuantityString(R.plurals.shortcuts_count,
+                    entry.shortcutsCount, entry.shortcutsCount);
+            subtitle = resources.getString(R.string.widgets_and_shortcuts_count, widgetsCount,
+                    shortcutsCount);
+        } else if (entry.widgetsCount > 0) {
+            subtitle = resources.getQuantityString(R.plurals.widgets_count,
+                     entry.widgetsCount, entry.widgetsCount);
+        } else {
+            subtitle = resources.getQuantityString(R.plurals.shortcuts_count,
+                    entry.shortcutsCount, entry.shortcutsCount);
+        }
+        mSubtitle.setText(subtitle);
+        mSubtitle.setVisibility(VISIBLE);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java b/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java
new file mode 100644
index 0000000..d12782c
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsPickerSearchPipeline.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.widget.picker.search;
+
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * An interface for a pipeline to handle widgets search.
+ */
+public interface WidgetsPickerSearchPipeline {
+
+    /**
+     * Performs a search query asynchronically. Invokes {@code callback} when the search is
+     * complete.
+     */
+    void query(String input, Consumer<List<WidgetsListBaseEntry>> callback);
+
+    /**
+     * Cancels any ongoing search request.
+     */
+    default void cancel() {};
+
+    /**
+     * Cleans up after search is no longer needed.
+     */
+    default void destroy() {};
+}
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 5e41d43d..63220ad 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -77,6 +78,17 @@
         mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
     }
 
+    @After
+    public void resumeAppStoreUpdate() {
+        executeOnLauncher(launcher -> {
+            if (launcher == null || launcher.getAppsView() == null) {
+                return;
+            }
+            launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+            Log.d(TestProtocol.WORK_PROFILE_REMOVED, "resuming AppStore updates");
+        });
+    }
+
     @Test
     public void workTabExists() {
         mDevice.pressHome();
@@ -145,6 +157,12 @@
                 "work profile status (" + mProfileUserId + ") :"
                         + launcher.getAppsView().isWorkTabVisible()));
 
+
+        executeOnLauncher(launcher -> {
+            launcher.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+            Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Defer all apps update");
+        });
+
         AtomicInteger attempt = new AtomicInteger(0);
         // verify work edu is seen next
         waitForLauncherCondition("Launcher did not show the next edu screen", l -> {
@@ -157,7 +175,6 @@
             if (((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage()
                     != WORK_PAGE) {
                 Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Work page not highlighted");
-                return false;
             }
             return ((TextView) workEduView.findViewById(R.id.content_text)).getText().equals(
                     l.getResources().getString(R.string.work_profile_edu_work_apps));