Merge "[Search] Apply background to entire SearchInput view instead of just EditText" into sc-v2-dev
diff --git a/Android.bp b/Android.bp
index 43d28c9..c8d9186 100644
--- a/Android.bp
+++ b/Android.bp
@@ -33,6 +33,9 @@
 
 android_library {
     name: "launcher-aosp-tapl",
+    libs: [
+        "framework-statsd",
+    ],
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.runner",
@@ -192,6 +195,9 @@
     resource_dirs: [
         "quickstep/res",
     ],
+    libs: [
+        "framework-statsd",
+    ],
     static_libs: [
         "Launcher3ResLib",
         "SystemUISharedLib",
@@ -261,6 +267,9 @@
     resource_dirs: [
         "quickstep/res",
     ],
+    libs: [
+        "framework-statsd",
+    ],
     static_libs: [
         "SystemUI-statsd",
         "SystemUISharedLib",
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 6d49d75..ca9f063 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -59,6 +59,7 @@
     SettingsContainer settings_container = 9;
     PredictedHotseatContainer predicted_hotseat_container = 10;
     TaskSwitcherContainer task_switcher_container = 11;
+    TaskBarContainer task_bar_container = 12;
     ExtendedContainers extended_containers = 20;
   }
 }
@@ -100,6 +101,16 @@
 message TaskSwitcherContainer {
 }
 
+// Container for taskbar.
+// Configured to show up on large screens(tablet-sized) such as foldables in expanded state, within
+// an app view(not in launcher screen).
+message TaskBarContainer {
+  optional int32 index = 1;
+
+  // Bit encoded value to capture pinned and predicted taskbar positions.
+  optional int32 cardinality = 2;
+}
+
 enum Attribute {
   UNKNOWN = 0;
   DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
@@ -230,6 +241,7 @@
   oneof ParentContainer {
     WorkspaceContainer workspace = 4;
     HotseatContainer hotseat = 5;
+    TaskBarContainer taskbar = 6;
   }
 }
 
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d67b23b..31c0f5f 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -30,6 +30,7 @@
          determines how many thumbnails will be fetched in the background. -->
     <integer name="recentsThumbnailCacheSize">3</integer>
     <integer name="recentsIconCacheSize">12</integer>
+    <integer name="recentsScrollHapticMinGapMillis">20</integer>
 
     <!-- Assistant Gesture -->
     <integer name="assistant_gesture_min_time_threshold">200</integer>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 2534699..d24d752 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -90,6 +90,8 @@
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.List;
 import java.util.stream.Stream;
 
@@ -122,6 +124,15 @@
     private final ServiceConnection mTisBinderConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+            if (!(iBinder instanceof TISBinder)) {
+                // Seems like there can be a race condition when user unlocks, which kills the TIS
+                // process and re-starts it. I guess in the meantime service can be connected to
+                // a killed TIS? Either way, unbind and try to re-connect in that case.
+                unbindService(mTisBinderConnection);
+                mHandler.postDelayed(mConnectionRunnable, BACKOFF_MILLIS);
+                return;
+            }
+
             mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
             mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
 
@@ -619,4 +630,12 @@
             recentsView.finishRecentsAnimation(/* toRecents= */ true, null);
         }
     }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        super.dump(prefix, fd, writer, args);
+        if (mDepthController != null) {
+            mDepthController.dump(prefix, writer);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 5769f0b..55a140d 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -25,7 +25,9 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.app.StatsManager;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionManager;
 import android.app.prediction.AppPredictor;
@@ -39,12 +41,14 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.StatsEvent;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -53,10 +57,10 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.PersistedItemArray;
 import com.android.quickstep.logging.StatsLogCompatManager;
+import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.util.Collections;
 import java.util.List;
@@ -85,6 +89,7 @@
 
     private final InvariantDeviceProfile mIDP;
     private final AppEventProducer mAppEventProducer;
+    private final StatsManager mStatsManager;
 
     protected boolean mActive = false;
 
@@ -93,6 +98,7 @@
 
         mIDP = InvariantDeviceProfile.INSTANCE.get(context);
         StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
+        mStatsManager = context.getSystemService(StatsManager.class);
     }
 
     @Override
@@ -155,10 +161,60 @@
             additionalSnapshotEvents(instanceId);
             prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
         }
+
+        // Only register for launcher snapshot logging if this is the primary ModelDelegate
+        // instance, as there will be additional instances that may be destroyed at any time.
+        if (mIsPrimaryInstance) {
+            registerSnapshotLoggingCallback();
+        }
     }
 
     protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
 
+    /**
+     * Registers a callback to log launcher workspace layout using Statsd pulled atom.
+     */
+    protected void registerSnapshotLoggingCallback() {
+        if (mStatsManager == null) {
+            Log.d(TAG, "Failed to get StatsManager");
+        }
+
+        try {
+            mStatsManager.setPullAtomCallback(
+                    SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT,
+                    null /* PullAtomMetadata */,
+                    MODEL_EXECUTOR,
+                    (i, eventList) -> {
+                        InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+                        IntSparseArrayMap<ItemInfo> itemsIdMap;
+                        synchronized (mDataModel) {
+                            itemsIdMap = mDataModel.itemsIdMap.clone();
+                        }
+
+                        for (ItemInfo info : itemsIdMap) {
+                            FolderInfo parent = info.container > 0
+                                    ? (FolderInfo) itemsIdMap.get(info.container) : null;
+                            LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
+                            Log.d(TAG, itemInfo.toString());
+                            StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
+                                    instanceId);
+                            eventList.add(statsEvent);
+                        }
+                        Log.d(TAG,
+                                String.format(
+                                        "Successfully logged %d workspace items with instanceId=%d",
+                                        itemsIdMap.size(), instanceId.getId()));
+                        additionalSnapshotEvents(instanceId);
+                        return StatsManager.PULL_SUCCESS;
+                    }
+            );
+            Log.d(TAG, "Successfully registered for launcher snapshot logging!");
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to register launcher snapshot logging callback with StatsManager",
+                    e);
+        }
+    }
+
     @Override
     public void validateData() {
         super.validateData();
@@ -175,7 +231,9 @@
         super.destroy();
         mActive = false;
         StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
-
+        if (mIsPrimaryInstance) {
+            mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
+        }
         destroyPredictors();
     }
 
@@ -221,7 +279,7 @@
     private void registerPredictor(PredictorState state, AppPredictor predictor) {
         state.predictor = predictor;
         state.predictor.registerPredictionUpdates(
-                Executors.MODEL_EXECUTOR, t -> handleUpdate(state, t));
+                MODEL_EXECUTOR, t -> handleUpdate(state, t));
         state.predictor.requestPredictionUpdate();
     }
 
@@ -236,7 +294,7 @@
     private void registerWidgetsPredictor(AppPredictor predictor) {
         mWidgetsRecommendationState.predictor = predictor;
         mWidgetsRecommendationState.predictor.registerPredictionUpdates(
-                Executors.MODEL_EXECUTOR, targets -> {
+                MODEL_EXECUTOR, targets -> {
                     if (mWidgetsRecommendationState.setTargets(targets)) {
                         // No diff, skip
                         return;
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 3e2fb63..9d70cfa 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -44,6 +44,8 @@
 import com.android.systemui.shared.system.BlurUtils;
 import com.android.systemui.shared.system.WallpaperManagerCompat;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.function.Consumer;
 
 /**
@@ -138,6 +140,11 @@
      */
     private float mDepth;
     /**
+     * Last blur value, in pixels, that was applied.
+     * For debugging purposes.
+     */
+    private int mCurrentBlur;
+    /**
      * If we're launching and app and should not be blurring the screen for performance reasons.
      */
     private boolean mBlurDisabledForAppLaunch;
@@ -306,10 +313,10 @@
         if (supportsBlur) {
             boolean opaque = mLauncher.getScrimView().isFullyOpaque();
 
-            int blur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
+            mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
                     ? 0 : (int) (depth * mMaxBlurRadius);
             SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
-                    .setBackgroundBlurRadius(mSurface, blur)
+                    .setBackgroundBlurRadius(mSurface, mCurrentBlur)
                     .setOpaque(mSurface, opaque);
 
             // Set early wake-up flags when we know we're executing an expensive operation, this way
@@ -348,4 +355,18 @@
         mwAnimation.setAutoCancel(true);
         mwAnimation.start();
     }
+
+    public void dump(String prefix, PrintWriter writer) {
+        writer.println(prefix + this.getClass().getSimpleName());
+        writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
+        writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
+        writer.println(prefix + "\tmSurface=" + mSurface);
+        writer.println(prefix + "\tmOverlayScrollProgress=" + mOverlayScrollProgress);
+        writer.println(prefix + "\tmDepth=" + mDepth);
+        writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur);
+        writer.println(prefix + "\tmBlurDisabledForAppLaunch=" + mBlurDisabledForAppLaunch);
+        writer.println(prefix + "\tmInEarlyWakeUp=" + mInEarlyWakeUp);
+        writer.println(prefix + "\tmIgnoreStateChangesDuringMultiWindowAnimation="
+                + mIgnoreStateChangesDuringMultiWindowAnimation);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 764b0d3..359f436 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -16,6 +16,8 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
 
@@ -36,7 +38,10 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.OnboardingPrefs;
@@ -128,7 +133,7 @@
 
     @Override
     protected boolean isTaskbarTouchable() {
-        return !isAnimatingToLauncher() && !mControllers.taskbarStashController.isStashed();
+        return !isAnimatingToLauncher();
     }
 
     private boolean isAnimatingToLauncher() {
@@ -262,6 +267,11 @@
     @Override
     protected void onStashedInAppChanged() {
         onStashedInAppChanged(mLauncher.getDeviceProfile());
+        if (mControllers.taskbarStashController.isStashedInApp()) {
+            mContext.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_HIDE);
+        } else {
+            mContext.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_SHOW);
+        }
     }
 
     private void onStashedInAppChanged(DeviceProfile deviceProfile) {
@@ -306,6 +316,12 @@
         mControllers.taskbarEduController.hideEdu();
     }
 
+    @Override
+    public void onTaskbarIconLaunched(WorkspaceItemInfo item) {
+        InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+        mLauncher.logAppLaunch(mContext.getStatsLogManager(), item, instanceId);
+    }
+
     private final class TaskBarRecentsAnimationListener implements RecentsAnimationListener {
         private final RecentsAnimationCallbacks mCallbacks;
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 0d684a0..0b6f9c4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
 
@@ -52,6 +53,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.taskbar.contextual.RotationButtonController;
@@ -162,6 +164,8 @@
         mWindowLayoutParams.setFitInsetsTypes(0);
         mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
         mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mWindowLayoutParams.privateFlags =
+                WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 
         WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
         wmWrapper.setProvidesInsetsTypes(
@@ -230,6 +234,60 @@
     }
 
     /**
+     * Change from hotseat/predicted hotseat to taskbar container.
+     */
+    @Override
+    public void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) {
+        if (!itemInfoBuilder.hasContainerInfo()) {
+            return;
+        }
+        LauncherAtom.ContainerInfo oldContainer = itemInfoBuilder.getContainerInfo();
+
+        if (oldContainer.hasPredictedHotseatContainer()) {
+            LauncherAtom.PredictedHotseatContainer predictedHotseat =
+                    oldContainer.getPredictedHotseatContainer();
+            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+                    LauncherAtom.TaskBarContainer.newBuilder();
+
+            if (predictedHotseat.hasIndex()) {
+                taskbarBuilder.setIndex(predictedHotseat.getIndex());
+            }
+            if (predictedHotseat.hasCardinality()) {
+                taskbarBuilder.setCardinality(predictedHotseat.getCardinality());
+            }
+
+            itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+                    .setTaskBarContainer(taskbarBuilder));
+        } else if (oldContainer.hasHotseat()) {
+            LauncherAtom.HotseatContainer hotseat = oldContainer.getHotseat();
+            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+                    LauncherAtom.TaskBarContainer.newBuilder();
+
+            if (hotseat.hasIndex()) {
+                taskbarBuilder.setIndex(hotseat.getIndex());
+            }
+
+            itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+                    .setTaskBarContainer(taskbarBuilder));
+        } else if (oldContainer.hasFolder() && oldContainer.getFolder().hasHotseat()) {
+            LauncherAtom.FolderContainer.Builder folderBuilder = oldContainer.getFolder()
+                    .toBuilder();
+            LauncherAtom.HotseatContainer hotseat = folderBuilder.getHotseat();
+            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+                    LauncherAtom.TaskBarContainer.newBuilder();
+
+            if (hotseat.hasIndex()) {
+                taskbarBuilder.setIndex(hotseat.getIndex());
+            }
+
+            folderBuilder.setTaskbar(taskbarBuilder);
+            folderBuilder.clearHotseat();
+            itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+                    .setFolder(folderBuilder));
+        }
+    }
+
+    /**
      * Sets a new data-source for this taskbar instance
      */
     public void setUIController(@NonNull TaskbarUIController uiController) {
@@ -326,6 +384,7 @@
 
             getDragLayer().post(() -> {
                 folder.animateOpen();
+                getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
 
                 folder.iterateOverItems((itemInfo, itemView) -> {
                     mControllers.taskbarViewController
@@ -363,6 +422,8 @@
                         getSystemService(LauncherApps.class).startMainActivity(
                                 intent.getComponent(), info.user, intent.getSourceBounds(), null);
                     }
+
+                    mControllers.uiController.onTaskbarIconLaunched(info);
                 } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
                     Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
                             .show();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index b89032e..1afbd17 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -34,6 +34,8 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
@@ -48,6 +50,7 @@
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.systemui.shared.recents.model.Task;
@@ -284,10 +287,22 @@
         }
 
         if (clipDescription != null && intent != null) {
+            // Need to share the same InstanceId between launcher3 and WM Shell (internal).
+            InstanceId internalInstanceId = new InstanceIdSequence(
+                    com.android.launcher3.logging.InstanceId.INSTANCE_ID_MAX).newInstanceId();
+            com.android.launcher3.logging.InstanceId launcherInstanceId =
+                    new com.android.launcher3.logging.InstanceId(internalInstanceId.getId());
+
+            intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);
+
             ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
             if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
                     View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE)) {
                 onSystemDragStarted();
+
+                mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
+                        .withInstanceId(launcherInstanceId)
+                        .log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index b7c5db2..10a5b89 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -135,8 +135,9 @@
             } else if (!mControllers.uiController.isTaskbarTouchable()) {
                 // Let touches pass through us.
                 insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (mControllers.taskbarViewController.areIconsVisible()) {
-                // Buttons are visible, take over the full taskbar area
+            } else if (mControllers.taskbarViewController.areIconsVisible()
+                    || AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) != null) {
+                // Taskbar has some touchable elements, take over the full taskbar area
                 insetsInfo.setTouchableInsets(mActivity.isTaskbarWindowFullscreen()
                         ? TOUCHABLE_INSETS_FRAME : TOUCHABLE_INSETS_CONTENT);
             } else {
@@ -155,7 +156,7 @@
          * Called when a child is removed from TaskbarDragLayer.
          */
         public void onDragLayerViewRemoved() {
-            if (AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) == null) {
+            if (AbstractFloatingView.getAnyView(mActivity, TYPE_ALL) == null) {
                 mActivity.setTaskbarWindowFullscreen(false);
             }
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 453bf1c..2e9d8bc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -24,6 +24,7 @@
 
 import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
@@ -41,6 +42,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
@@ -62,6 +64,7 @@
     private final TaskbarNavButtonController mNavButtonController;
     private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
     private final ComponentCallbacks mComponentCallbacks;
+    private final SimpleBroadcastReceiver mShutdownReceiver;
 
     // The source for this provider is set when Launcher is available
     private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
@@ -103,12 +106,14 @@
             @Override
             public void onLowMemory() { }
         };
+        mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
 
         mDisplayController.addChangeListener(this);
         mSysUINavigationMode.addModeChangeListener(this);
         SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
                 mUserSetupCompleteListener);
         mContext.registerComponentCallbacks(mComponentCallbacks);
+        mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
 
         recreateTaskbar();
     }
@@ -231,6 +236,7 @@
         SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
                 mUserSetupCompleteListener);
         mContext.unregisterComponentCallbacks(mComponentCallbacks);
+        mContext.unregisterReceiver(mShutdownReceiver);
     }
 
     public @Nullable TaskbarActivityContext getCurrentActivityContext() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index c0312a0..d8360e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -18,6 +18,7 @@
 import android.graphics.Rect;
 
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 
 import java.util.stream.Stream;
 
@@ -43,4 +44,6 @@
     public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
         return Stream.empty();
     }
+
+    public void onTaskbarIconLaunched(WorkspaceItemInfo item) { }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
index 43f015c..978bd47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -77,6 +77,7 @@
         @Override
         public void onTransitionFinished() {
             mMoveFromCenterAnimator.onTransitionFinished();
+            mMoveFromCenterAnimator.clearRegisteredViews();
         }
 
         @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 2009cd7..3738dce 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -51,6 +51,7 @@
 import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.ItemInfo;
@@ -104,7 +105,8 @@
     }
 
     @Override
-    protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+    public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
+            InstanceId instanceId) {
         // If the app launch is from any of the surfaces in AllApps then add the InstanceId from
         // LiveSearchManager to recreate the AllApps session on the server side.
         if (mAllAppsSessionLogId != null && ALL_APPS.equals(
@@ -112,8 +114,7 @@
             instanceId = mAllAppsSessionLogId;
         }
 
-        StatsLogger logger = getStatsLogManager()
-                .logger().withItemInfo(info).withInstanceId(instanceId);
+        StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
 
         if (mAllAppsPredictions != null
                 && (info.itemType == ITEM_TYPE_APPLICATION
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
deleted file mode 100644
index 0e12e30..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.plugins;
-
-import android.content.Context;
-
-import com.android.launcher3.Utilities;
-import com.android.systemui.shared.plugins.PluginInitializer;
-
-public class PluginInitializerImpl implements PluginInitializer {
-    @Override
-    public String[] getPrivilegedPlugins(Context context) {
-        return new String[0];
-    }
-
-    @Override
-    public void handleWtfs() {
-    }
-
-    public boolean isDebuggable() {
-        return Utilities.IS_DEBUG_DEVICE;
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index e12f42e..df0ac7c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -24,17 +24,19 @@
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginInstanceManager;
+import com.android.systemui.shared.plugins.PluginActionManager;
+import com.android.systemui.shared.plugins.PluginInstance;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.plugins.PluginPrefs;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -52,29 +54,36 @@
 
     private PluginManagerWrapper(Context c) {
         mContext = c;
-        PluginInitializerImpl pluginInitializer  = new PluginInitializerImpl();
         mPluginEnabler = new PluginEnablerImpl(c);
-        PluginInstanceManager.Factory instanceManagerFactory = new PluginInstanceManager.Factory(
-                c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR, pluginInitializer,
+        List<String> privilegedPlugins = Collections.emptyList();
+        PluginInstance.Factory instanceFactory = new PluginInstance.Factory(
+                getClass().getClassLoader(), new PluginInstance.InstanceFactory<>(),
+                new PluginInstance.VersionChecker(), privilegedPlugins,
+                Utilities.IS_DEBUG_DEVICE);
+        PluginActionManager.Factory instanceManagerFactory = new PluginActionManager.Factory(
+                c, c.getPackageManager(), c.getMainExecutor(), MODEL_EXECUTOR,
                 c.getSystemService(NotificationManager.class), mPluginEnabler,
-                Arrays.asList(pluginInitializer.getPrivilegedPlugins(c)));
+                privilegedPlugins, instanceFactory);
 
         mPluginManager = new PluginManagerImpl(c, instanceManagerFactory,
-                pluginInitializer.isDebuggable(),
+                Utilities.IS_DEBUG_DEVICE,
                 Optional.ofNullable(Thread.getDefaultUncaughtExceptionHandler()), mPluginEnabler,
-                new PluginPrefs(c), Arrays.asList(pluginInitializer.getPrivilegedPlugins(c)));
+                new PluginPrefs(c), privilegedPlugins);
     }
 
     public PluginEnablerImpl getPluginEnabler() {
         return mPluginEnabler;
     }
 
-    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
+    /** */
+    public <T extends Plugin> void addPluginListener(
+            PluginListener<T> listener, Class<T> pluginClass) {
         addPluginListener(listener, pluginClass, false);
     }
 
-    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
-            boolean allowMultiple) {
+    /** */
+    public <T extends Plugin> void addPluginListener(
+            PluginListener<T> listener, Class<T> pluginClass, boolean allowMultiple) {
         mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 283743d..ef6f53e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -24,7 +24,7 @@
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.ObjectAnimator;
@@ -38,11 +38,11 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.OverviewToHomeAnim;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.views.RecentsView;
 
 /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 40c3e02..ff3c517 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -41,7 +41,7 @@
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -67,14 +67,15 @@
 import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.BothAxesSwipeDetector;
 import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.util.WorkspaceRevealAnim;
 import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
 
 /**
  * Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -398,6 +399,14 @@
             nonOverviewAnim.setFloatValues(startProgress, endProgress);
             mNonOverviewAnim.dispatchOnStart();
         }
+        if (targetState == QUICK_SWITCH) {
+            // Navigating to quick switch, add scroll feedback since the first time is not
+            // considered a scroll by the RecentsView.
+            VibratorWrapper.INSTANCE.get(mLauncher).vibrate(
+                    RecentsView.SCROLL_VIBRATION_PRIMITIVE,
+                    RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
+                    RecentsView.SCROLL_VIBRATION_FALLBACK);
+        }
 
         nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
         mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 0603ba5..010f463 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -22,6 +22,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.os.SystemClock;
+import android.os.VibrationEffect;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -41,6 +42,7 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -55,6 +57,12 @@
     private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
     private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600;
 
+    public static final int TASK_DISMISS_VIBRATION_PRIMITIVE =
+            Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
+    public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f;
+    public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
+            VibratorWrapper.EFFECT_TEXTURE_TICK;
+
     protected final T mActivity;
     private final SingleAxisSwipeDetector mDetector;
     private final RecentsView mRecentsView;
@@ -334,10 +342,10 @@
             fling = false;
         }
         PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+        boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
         float progress = mCurrentAnimation.getProgressFraction();
         float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
         if (fling) {
-            boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
             goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
         } else {
             goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
@@ -357,6 +365,10 @@
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
                 velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
                 mEndDisplacement, animationDuration);
+        if (goingUp && goingToEnd) {
+            VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
+                    TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK);
+        }
     }
 
     private void clearState() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 830737d..47c865f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -36,7 +36,6 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -46,6 +45,7 @@
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
@@ -65,7 +65,6 @@
 import android.os.Build;
 import android.os.IBinder;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
@@ -88,11 +87,9 @@
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
 import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
@@ -112,6 +109,7 @@
 import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -270,7 +268,7 @@
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
-        super(context, deviceState, gestureState, new TransformParams());
+        super(context, deviceState, gestureState);
         mActivityInterface = gestureState.getActivityInterface();
         mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
         mInputConsumerProxy =
@@ -883,9 +881,6 @@
      */
     @UiThread
     public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME, "3");
-        }
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_speed);
         boolean isFling = mGestureStarted && !mIsMotionPaused
@@ -945,6 +940,7 @@
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
                 // Notify swipe-to-home (recents animation) is finished
                 SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
+                LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
                 break;
             case RECENTS:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -1047,9 +1043,6 @@
     @UiThread
     private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
             boolean isCancel) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME, "4");
-        }
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
         final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
@@ -1168,9 +1161,6 @@
     @UiThread
     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
             GestureEndTarget target, PointF velocityPxPerMs) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME, "5");
-        }
         runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
                 interpolator, target, velocityPxPerMs));
     }
@@ -1221,9 +1211,6 @@
             }
         }
 
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME, "7, end target=" + mGestureState.getEndTarget());
-        }
         if (mGestureState.getEndTarget() == HOME) {
             getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
             final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
@@ -1238,9 +1225,6 @@
                     && runningTaskTarget.allowEnterPip
                     && runningTaskTarget.taskInfo.pictureInPictureParams != null
                     && runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                Log.d(TestProtocol.L3_SWIPE_TO_HOME, "8, class=" + getClass().getSimpleName());
-            }
             HomeAnimationFactory homeAnimFactory =
                     createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
                             runningTaskTarget);
@@ -1805,8 +1789,13 @@
                 mGestureState.updateLastStartedTaskId(taskId);
                 boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
                         .contains(taskId);
+                boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+                        .getRunningSplitTaskIds().length > 0;
                 nextTask.launchTask(success -> {
                     resultCallback.accept(success);
+                    if (isOldTaskSplit) {
+                        SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(taskId);
+                    }
                     if (success) {
                         if (hasTaskPreviouslyAppeared) {
                             onRestartPreviouslyAppearedTask();
@@ -1834,9 +1823,6 @@
      * be run when it is next started.
      */
     protected void runOnRecentsAnimationStart(Runnable action) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME, "6");
-        }
         if (mRecentsAnimationTargets == null) {
             mRecentsAnimationStartCallbacks.add(action);
         } else {
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 80ae65e..c1b45e0 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -45,7 +45,6 @@
 import android.os.Messenger;
 import android.os.ParcelUuid;
 import android.os.UserHandle;
-import android.util.Log;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -58,7 +57,6 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringAnimationBuilder;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.AppCloseConfig;
@@ -141,10 +139,6 @@
         mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
         ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
         Intent intent = new Intent(mGestureState.getHomeIntent());
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME,
-                    "createHomeAnimationFactory: " + intent.toShortString(true, true, true, false));
-        }
         mActiveAnimationFactory.addGestureContract(intent);
         try {
             mContext.startActivity(intent, options.toBundle());
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 8fb851c..b232464 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -157,6 +158,7 @@
             }
             if (cmd.type == TYPE_HOME) {
                 mService.startActivity(mOverviewComponentObserver.getHomeIntent());
+                LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
                 return true;
             }
         } else {
@@ -175,6 +177,7 @@
                     return launchTask(recents, getNextTask(recents), cmd);
                 case TYPE_HOME:
                     recents.startHome();
+                    LauncherSplitScreenListener.INSTANCE.getNoCreate().notifySwipingToHome();
                     return true;
             }
         }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 3c05a3e..e948221 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -20,14 +20,12 @@
 
 import android.graphics.Rect;
 import android.util.ArraySet;
-import android.util.Log;
 import android.view.RemoteAnimationTarget;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Preconditions;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -97,9 +95,6 @@
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
             Rect homeContentInsets, Rect minimizedHomeBounds) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME, "RecentsAnimationCallbacks.onAnimationStart");
-        }
         // Convert appTargets to type RemoteAnimationTarget for all apps except Home app
         RemoteAnimationTarget[] nonHomeApps = Arrays.stream(appTargets)
                 .filter(remoteAnimationTarget ->
@@ -121,10 +116,6 @@
                     mController::finishAnimationToApp);
         } else {
             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
-                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                    Log.d(TestProtocol.L3_SWIPE_TO_HOME,
-                            "RecentsAnimationCallbacks.onAnimationStart callback");
-                }
                 for (RecentsAnimationListener listener : getListeners()) {
                     listener.onRecentsAnimationStart(mController, targets);
                 }
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 51a491e..d188018 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -80,7 +80,7 @@
     protected boolean mIsSwipeForStagedSplit;
 
     public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, TransformParams transformParams) {
+            GestureState gestureState) {
         mContext = context;
         mDeviceState = deviceState;
         mGestureState = gestureState;
@@ -89,13 +89,13 @@
                 LauncherSplitScreenListener.INSTANCE.getNoCreate()
                         .getRunningSplitTaskIds().length > 1;
 
-        TaskViewSimulator primaryTVS = new TaskViewSimulator(context,
-                gestureState.getActivityInterface());
-        primaryTVS.getOrientationState().update(
-                mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
-                mDeviceState.getRotationTouchHelper().getDisplayRotation());
         mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
         mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
+        runActionOnRemoteHandles(remoteTargetHandle ->
+                remoteTargetHandle.getTaskViewSimulator().getOrientationState().update(
+                        mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
+                        mDeviceState.getRotationTouchHelper().getDisplayRotation()
+                ));
     }
 
     protected void initTransitionEndpoints(DeviceProfile dp) {
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 7d2d413..aea2d4c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -533,10 +533,17 @@
         }
     }
 
-    public void exitSplitScreen() {
+    /**
+     * To be called whenever the user exits out of split screen apps (either by launching another
+     * app or by swiping home)
+     * @param topTaskId The taskId of the new app that was launched. System will then move this task
+     *                  to the front of what the user sees while removing all other split stages.
+     *                  If swiping to home (or there is no task to put at the top), can pass in -1.
+     */
+    public void exitSplitScreen(int topTaskId) {
         if (mSplitScreen != null) {
             try {
-                mSplitScreen.exitSplitScreen();
+                mSplitScreen.exitSplitScreen(topTaskId);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call exitSplitScreen");
             }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e6285f2..725c7c4 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -50,7 +50,6 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.InputConsumerProto;
@@ -357,9 +356,6 @@
             }
             case ACTION_CANCEL:
             case ACTION_UP: {
-                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                    Log.d(TestProtocol.L3_SWIPE_TO_HOME, "1");
-                }
                 if (DEBUG_FAILED_QUICKSWITCH && !mPassedWindowMoveSlop) {
                     float displacementX = mLastPos.x - mDownPos.x;
                     float displacementY = mLastPos.y - mDownPos.y;
@@ -413,9 +409,6 @@
      * the animation can still be running.
      */
     private void finishTouchTracking(MotionEvent ev) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME, "2");
-        }
         Object traceToken = TraceHelper.INSTANCE.beginSection(UP_EVT,
                 FLAG_CHECK_FOR_RACE_CONDITIONS);
 
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 7465db3..b2b2f59 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -43,7 +43,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.util.VibratorWrapper;
 
 /** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
 public class EdgeBackGesturePanel extends View {
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index fc1dd6f..851cccf 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -16,7 +16,6 @@
 package com.android.quickstep.interaction;
 
 import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@@ -26,6 +25,7 @@
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -47,11 +47,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 
 /** Utility class to handle Home and Assistant gestures. */
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 1f75936..ce8047e 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -50,6 +50,7 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RemoteTargetGluer;
 import com.android.quickstep.SwipeUpAnimationLogic;
 import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
 import com.android.quickstep.util.AppCloseConfig;
@@ -254,7 +255,9 @@
 
         ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
                              GestureState gestureState) {
-            super(context, deviceState, gestureState, new FakeTransformParams());
+            super(context, deviceState, gestureState);
+            mRemoteTargetHandles[0] = new RemoteTargetGluer.RemoteTargetHandle(
+                    mRemoteTargetHandles[0].getTaskViewSimulator(), new FakeTransformParams());
         }
 
         void initDp(DeviceProfile dp) {
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 6575996..676161e 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -31,9 +31,11 @@
 
 import android.content.Context;
 import android.util.Log;
+import android.util.StatsEvent;
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 import androidx.slice.SliceItem;
 
@@ -56,6 +58,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.LogConfig;
+import com.android.launcher3.views.ActivityContext;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.SysUiStatsLog;
 
@@ -97,7 +100,7 @@
 
     @Override
     protected StatsLogger createLogger() {
-        return new StatsCompatLogger(mContext);
+        return new StatsCompatLogger(mContext, mActivityContext);
     }
 
     /**
@@ -135,13 +138,44 @@
     }
 
     /**
+     * Builds {@link StatsEvent} from {@link LauncherAtom.ItemInfo}. Used for pulled atom callback
+     * implementation.
+     */
+    public static StatsEvent buildStatsEvent(LauncherAtom.ItemInfo info,
+            @Nullable InstanceId instanceId) {
+        return SysUiStatsLog.buildStatsEvent(
+                SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT, // atom ID,
+                LAUNCHER_WORKSPACE_SNAPSHOT.getId(), // event_id = 1;
+                info.getAttribute().getNumber() * ATTRIBUTE_MULTIPLIER
+                        + info.getItemCase().getNumber(), // item_id = 2;
+                instanceId == null ? 0 : instanceId.getId(), //instance_id = 3;
+                0, //uid = 4 [(is_uid) = true];
+                getPackageName(info), // package_name = 5;
+                getComponentName(info), // component_name = 6;
+                getGridX(info, false), //grid_x = 7 [default = -1];
+                getGridY(info, false), //grid_y = 8 [default = -1];
+                getPageId(info), // page_id = 9 [default = -2];
+                getGridX(info, true), //grid_x_parent = 10 [default = -1];
+                getGridY(info, true), //grid_y_parent = 11 [default = -1];
+                getParentPageId(info), //page_id_parent = 12 [default = -2];
+                getHierarchy(info), // container_id = 13;
+                info.getIsWork(), // is_work_profile = 14;
+                info.getAttribute().getNumber(), // attribute_id = 15;
+                getCardinality(info), // cardinality = 16;
+                info.getWidget().getSpanX(), // span_x = 17 [default = 1];
+                info.getWidget().getSpanY() // span_y = 18 [default = 1];
+        );
+    }
+
+    /**
      * Helps to construct and write statsd compatible log message.
      */
     private static class StatsCompatLogger implements StatsLogger {
 
         private static final ItemInfo DEFAULT_ITEM_INFO = new ItemInfo();
 
-        private Context mContext;
+        private final Context mContext;
+        private final Optional<ActivityContext> mActivityContext;
         private ItemInfo mItemInfo = DEFAULT_ITEM_INFO;
         private InstanceId mInstanceId = DEFAULT_INSTANCE_ID;
         private OptionalInt mRank = OptionalInt.empty();
@@ -154,8 +188,9 @@
         private SliceItem mSliceItem;
         private LauncherAtom.Slice mSlice;
 
-        StatsCompatLogger(Context context) {
+        StatsCompatLogger(Context context, ActivityContext activityContext) {
             mContext = context;
+            mActivityContext = Optional.ofNullable(activityContext);
         }
 
         @Override
@@ -307,6 +342,9 @@
             mRank.ifPresent(itemInfoBuilder::setRank);
             mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
 
+            mActivityContext.ifPresent(activityContext ->
+                    activityContext.applyOverwritesToLogItem(itemInfoBuilder));
+
             if (mFromState.isPresent() || mToState.isPresent() || mEditText.isPresent()) {
                 FolderIcon.Builder folderIconBuilder = itemInfoBuilder
                         .getFolderIcon()
@@ -375,6 +413,8 @@
         switch (info.getContainerInfo().getContainerCase()) {
             case PREDICTED_HOTSEAT_CONTAINER:
                 return info.getContainerInfo().getPredictedHotseatContainer().getCardinality();
+            case TASK_BAR_CONTAINER:
+                return info.getContainerInfo().getTaskBarContainer().getCardinality();
             case SEARCH_RESULT_CONTAINER:
                 return info.getContainerInfo().getSearchResultContainer().getQueryLength();
             case EXTENDED_CONTAINERS:
@@ -461,6 +501,8 @@
                 return info.getContainerInfo().getHotseat().getIndex();
             case PREDICTED_HOTSEAT_CONTAINER:
                 return info.getContainerInfo().getPredictedHotseatContainer().getIndex();
+            case TASK_BAR_CONTAINER:
+                return info.getContainerInfo().getTaskBarContainer().getIndex();
             default:
                 return info.getContainerInfo().getWorkspace().getPageIndex();
         }
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
index 0f4ed01..fa4cddc 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
@@ -44,8 +44,6 @@
             if (frozen) {
                 mPersistentGroupedIds = getRunningSplitTaskIds();
             } else {
-                // TODO(b/198310766) Need to also explicitly exit split screen if
-                //  we're not currently viewing split screened apps
                 mPersistentGroupedIds = EMPTY_ARRAY;
             }
         }
@@ -53,8 +51,11 @@
 
     /**
      * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array
-     * whenever task list unfreezes.
-     * When not null, this indicates that we need to load a GroupedTaskView as the most recent
+     * whenever task list unfreezes. This also gets set to empty array whenever the user swipes to
+     * home - in that case the task list does not unfreeze immediately after the gesture, so it's
+     * done via {@link #notifySwipingToHome()}.
+     *
+     * When not empty, this indicates that we need to load a GroupedTaskView as the most recent
      * page, so user can quickswitch back to a grouped task.
      */
     private int[] mPersistentGroupedIds;
@@ -140,6 +141,18 @@
         }
     }
 
+    /** Notifies SystemUi to remove any split screen state */
+    public void notifySwipingToHome() {
+        boolean hasSplitTasks = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+                .getPersistentSplitIds().length > 0;
+        if (!hasSplitTasks) {
+            return;
+        }
+
+        SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(-1);
+        mPersistentGroupedIds = EMPTY_ARRAY;
+    }
+
     private void resetTaskId(StagedSplitTaskPosition taskPosition) {
         taskPosition.taskId = -1;
     }
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index ccc587c..44396fa 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -197,7 +197,7 @@
         launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start();
 
         // Stop scrolling so that it doesn't interfere with the translation offscreen.
-        launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
+        launcher.<RecentsView>getOverviewPanel().forceFinishScroller();
 
         if (animateOverviewScrim) {
             launcher.getWorkspace().getStateTransitionAnimation()
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
similarity index 65%
rename from src/com/android/launcher3/util/VibratorWrapper.java
rename to quickstep/src/com/android/quickstep/util/VibratorWrapper.java
index b0defd4..211bd08 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.util;
+package com.android.quickstep.util;
 
 import static android.os.VibrationEffect.createPredefined;
 import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
@@ -21,15 +21,20 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.media.AudioAttributes;
 import android.os.Build;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.Settings;
 
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
 /**
  * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
  */
@@ -39,8 +44,15 @@
     public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
             new MainThreadInitializedObject<>(VibratorWrapper::new);
 
+    public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .build();
+
     public static final VibrationEffect EFFECT_CLICK =
             createPredefined(VibrationEffect.EFFECT_CLICK);
+    public static final VibrationEffect EFFECT_TEXTURE_TICK =
+            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK);
 
     /**
      * Haptic when entering overview.
@@ -78,7 +90,27 @@
     /** Vibrates with the given effect if haptic feedback is available and enabled. */
     public void vibrate(VibrationEffect vibrationEffect) {
         if (mHasVibrator && mIsHapticFeedbackEnabled) {
-            UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
+            UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS));
+        }
+    }
+
+    /**
+     * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only
+     * vibrates if haptic feedback is available and enabled.
+     */
+    @SuppressLint("NewApi")
+    public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) {
+        if (mHasVibrator && mIsHapticFeedbackEnabled) {
+            UI_HELPER_EXECUTOR.execute(() -> {
+                if (Utilities.ATLEAST_R && primitiveId >= 0
+                        && mVibrator.areAllPrimitivesSupported(primitiveId)) {
+                    mVibrator.vibrate(VibrationEffect.startComposition()
+                            .addPrimitive(primitiveId, primitiveScale)
+                            .compose(), VIBRATION_ATTRS);
+                } else {
+                    mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS);
+                }
+            });
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
index df94d0b..7ae6cb7 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
@@ -120,7 +120,7 @@
         launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start();
 
         // Stop scrolling so that it doesn't interfere with the translation offscreen.
-        launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
+        launcher.<RecentsView>getOverviewPanel().forceFinishScroller();
 
         if (animateOverviewScrim) {
             launcher.getWorkspace().getStateTransitionAnimation()
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 8562719..16303f3 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -31,6 +31,7 @@
     private Task mSecondaryTask;
     private TaskThumbnailView mSnapshotView2;
     private CancellableTask mThumbnailLoadRequest2;
+    private SplitConfigurationOptions.StagedSplitBounds mSplitBoundsConfig;
 
     public GroupedTaskView(Context context) {
         super(context);
@@ -57,7 +58,7 @@
         mTaskIdContainer[1] = secondary.key.id;
         mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2);
         mSnapshotView2.bind(secondary);
-        adjustThumbnailBoundsForSplit(splitBoundsConfig, orientedState);
+        mSplitBoundsConfig = splitBoundsConfig;
     }
 
     @Override
@@ -121,6 +122,21 @@
     public void onRecycle() {
         super.onRecycle();
         mSnapshotView2.setThumbnail(mSecondaryTask, null);
+        mSplitBoundsConfig = null;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension(widthSize, heightSize);
+        if (mSplitBoundsConfig == null || mSnapshotView == null || mSnapshotView2 == null) {
+            return;
+        }
+        getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
+                mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
+                mActivity.getDeviceProfile());
     }
 
     @Override
@@ -128,16 +144,4 @@
         super.setOverlayEnabled(overlayEnabled);
         mSnapshotView2.setOverlayEnabled(overlayEnabled);
     }
-
-    private void adjustThumbnailBoundsForSplit(
-            SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
-            RecentsOrientedState orientedState) {
-        if (splitBoundsConfig == null) {
-            return;
-        }
-
-        orientedState.getOrientationHandler().setGroupedTaskViewThumbnailBounds(
-                mSnapshotView, mSnapshotView2, this, splitBoundsConfig,
-                mActivity.getDeviceProfile());
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e85adcc..5fa95f4 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -79,7 +79,9 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.VibrationEffect;
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
@@ -160,6 +162,7 @@
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -244,6 +247,12 @@
                 }
             };
 
+    public static final int SCROLL_VIBRATION_PRIMITIVE =
+            Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
+    public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
+    public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
+            VibratorWrapper.EFFECT_TEXTURE_TICK;
+
     /**
      * Can be used to tint the color of the RecentsView to simulate a scrim that can views
      * excluded from. Really should be a proper scrim.
@@ -397,6 +406,7 @@
 
     protected final ACTIVITY_TYPE mActivity;
     private final float mFastFlingVelocity;
+    private final int mScrollHapticMinGapMillis;
     private final RecentsModel mModel;
     private final int mGridSideMargin;
     private final ClearAllButton mClearAllButton;
@@ -443,6 +453,7 @@
     private ObjectAnimator mTintingAnimator;
 
     private int mOverScrollShift = 0;
+    private long mScrollLastHapticTimestamp;
 
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -629,6 +640,8 @@
         final int rotation = mActivity.getDisplay().getRotation();
         mOrientationState.setRecentsRotation(rotation);
 
+        mScrollHapticMinGapMillis = getResources()
+                .getInteger(R.integer.recentsScrollHapticMinGapMillis);
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
         mModel = RecentsModel.INSTANCE.get(context);
@@ -1229,6 +1242,25 @@
     }
 
     @Override
+    protected void onEdgeAbsorbingScroll() {
+        vibrateForScroll();
+    }
+
+    @Override
+    protected void onScrollOverPageChanged() {
+        vibrateForScroll();
+    }
+
+    private void vibrateForScroll() {
+        long now = SystemClock.uptimeMillis();
+        if (now - mScrollLastHapticTimestamp > mScrollHapticMinGapMillis) {
+            mScrollLastHapticTimestamp = now;
+            VibratorWrapper.INSTANCE.get(mContext).vibrate(SCROLL_VIBRATION_PRIMITIVE,
+                    SCROLL_VIBRATION_PRIMITIVE_SCALE, SCROLL_VIBRATION_FALLBACK);
+        }
+    }
+
+    @Override
     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
         // Enables swiping to the left or right only if the task overlay is not modal.
         if (!isModal()) {
@@ -2002,9 +2034,6 @@
     /**
      * Called only when a swipe-up gesture from an app has completed. Only called after
      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
-     *
-     * TODO(b/198310766) Need to also explicitly exit split screen if
-     *  the swipe up was to home
      */
     public void onSwipeUpAnimationSuccess() {
         animateUpTaskIconScale();
@@ -2030,10 +2059,10 @@
         int runningIndex = getCurrentPage();
         AnimatorSet as = new AnimatorSet();
         for (int i = 0; i < getTaskViewCount(); i++) {
-            if (runningIndex == i) {
+            View taskView = getTaskViewAt(i);
+            if (runningIndex == i && taskView.getAlpha() != 0) {
                 continue;
             }
-            View taskView = getTaskViewAt(i);
             as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1));
         }
         return as;
@@ -3121,7 +3150,7 @@
      */
     private void updateFocusedSplitButtonVisibility() {
         mActionsView.setSplitButtonVisible(mActivity.getDeviceProfile().isTablet &&
-                !(getRunningTaskView() instanceof GroupedTaskView) &&
+                !(getFocusedTaskView() instanceof GroupedTaskView) &&
                 getTaskViewCount() > 1
         );
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index b650c95..7c558c2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -99,6 +99,7 @@
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.util.TransformParams;
@@ -693,8 +694,13 @@
             TestLogging.recordEvent(
                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
             ActivityOptionsWrapper opts =  mActivity.getActivityLaunchOptions(this, null);
+            boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
+                    .getPersistentSplitIds().length > 0;
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
+                if (isOldTaskSplit) {
+                    SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(mTask.key.id);
+                }
                 RecentsView recentsView = getRecentsView();
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) {
                     recentsView.onTaskLaunchedInLiveTileMode();
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 45e7e69..cba4833 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -42,7 +42,6 @@
 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;
@@ -56,7 +55,6 @@
 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;
@@ -188,15 +186,9 @@
 
     protected <T> T getFromRecents(Function<RecentsActivity, T> f) {
         if (!TestHelpers.isInLauncherProcess()) return null;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.FALLBACK_ACTIVITY_NO_SET, "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.FALLBACK_ACTIVITY_NO_SET, "activity=" + activity);
-            }
             if (activity == null) {
                 return false;
             }
diff --git a/res/xml/default_workspace_splitdisplay_5x5.xml b/res/xml/default_workspace_splitdisplay_5x5.xml
deleted file mode 100644
index 162367b..0000000
--- a/res/xml/default_workspace_splitdisplay_5x5.xml
+++ /dev/null
@@ -1,107 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-
-    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Messaging, [Maps/Music], Browser, Camera -->
-    <resolve
-        launcher:container="-101"
-        launcher:screen="0"
-        launcher:x="0"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-        <favorite launcher:uri="tel:123" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-    </resolve>
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="1"
-        launcher:x="1"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-        <favorite launcher:uri="sms:" />
-        <favorite launcher:uri="smsto:" />
-        <favorite launcher:uri="mms:" />
-        <favorite launcher:uri="mmsto:" />
-    </resolve>
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="2"
-        launcher:x="2"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;end" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MUSIC;end" />
-    </resolve>
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="3"
-        launcher:x="3"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-        <favorite launcher:uri="http://www.example.com/" />
-    </resolve>
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="4"
-        launcher:x="4"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
-    </resolve>
-
-    <!-- Bottom row -->
-    <resolve
-        launcher:screen="0"
-        launcher:x="0"
-        launcher:y="-1" >
-	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
-	    <favorite launcher:uri="mailto:" />
-
-    </resolve>
-
-    <resolve
-        launcher:screen="0"
-        launcher:x="1"
-        launcher:y="-1" >
-	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
-	    <favorite launcher:uri="#Intent;type=images/*;end" />
-
-    </resolve>
-
-    <resolve
-        launcher:screen="0"
-        launcher:x="4"
-        launcher:y="-1" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
-        <favorite launcher:uri="market://details?id=com.android.launcher" />
-    </resolve>
-
-    <!-- Placeholder before we add page pairing in b/196376162 -->
-    <resolve
-        launcher:screen="1"
-        launcher:x="0"
-        launcher:y="-4" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
-        <favorite launcher:uri="#Intent;type=images/*;end" />
-    </resolve>
-
-</favorites>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index d8ee6f2..256999c 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -105,8 +105,7 @@
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="5"
         launcher:dbFile="launcher.db"
-        launcher:defaultLayoutId="@xml/default_workspace_5x5"
-        launcher:defaultSplitDisplayLayoutId="@xml/default_workspace_splitdisplay_5x5" >
+        launcher:defaultLayoutId="@xml/default_workspace_5x5" >
 
         <display-option
             launcher:name="Large Phone"
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index e080537..e3cfb59 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -195,10 +195,24 @@
     }
 
     /**
-     * Returns a view matching FloatingViewType
+     * Returns a view matching FloatingViewType and {@link #isOpen()} == true.
      */
     public static <T extends AbstractFloatingView> T getOpenView(
             ActivityContext activity, @FloatingViewType int type) {
+        return getView(activity, type, true /* mustBeOpen */);
+    }
+
+    /**
+     * Returns a view matching FloatingViewType, and {@link #isOpen()} may be false (if animating
+     * closed).
+     */
+    public static <T extends AbstractFloatingView> T getAnyView(
+            ActivityContext activity, @FloatingViewType int type) {
+        return getView(activity, type, false /* mustBeOpen */);
+    }
+
+    private static <T extends AbstractFloatingView> T getView(
+            ActivityContext activity, @FloatingViewType int type, boolean mustBeOpen) {
         BaseDragLayer dragLayer = activity.getDragLayer();
         if (dragLayer == null) return null;
         // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
@@ -207,7 +221,7 @@
             View child = dragLayer.getChildAt(i);
             if (child instanceof AbstractFloatingView) {
                 AbstractFloatingView view = (AbstractFloatingView) child;
-                if (view.isOfType(type) && view.isOpen()) {
+                if (view.isOfType(type) && (!mustBeOpen || view.isOpen())) {
                     return (T) view;
                 }
             }
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 2c76e52..7954011 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
@@ -224,7 +225,7 @@
             }
             if (item != null) {
                 InstanceId instanceId = new InstanceIdSequence().newInstanceId();
-                logAppLaunch(item, instanceId);
+                logAppLaunch(getStatsLogManager(), item, instanceId);
             }
             return true;
         } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
@@ -234,8 +235,12 @@
         return false;
     }
 
-    protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
-        getStatsLogManager().logger().withItemInfo(info).withInstanceId(instanceId)
+    /**
+     * Creates and logs a new app launch event.
+     */
+    public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
+            InstanceId instanceId) {
+        statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId)
                 .log(LAUNCHER_APP_LAUNCH_TAP);
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ed9f044..85dd3b3 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -87,6 +87,7 @@
 import android.os.Process;
 import android.os.StrictMode;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.util.Log;
@@ -280,6 +281,11 @@
 
     private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
 
+    private static final String DISPLAY_WORKSPACE_TRACE_METHOD_NAME = "DisplayWorkspaceFirstFrame";
+    private static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
+    public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
+    public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
+
     private Configuration mOldConfig;
 
     @Thunk
@@ -366,7 +372,15 @@
     private LauncherState mPrevLauncherState;
 
     @Override
+    @TargetApi(Build.VERSION_CODES.S)
     protected void onCreate(Bundle savedInstanceState) {
+        // Only use a hard-coded cookie since we only want to trace this once.
+        if (Utilities.ATLEAST_S) {
+            Trace.beginAsyncSection(
+                    DISPLAY_WORKSPACE_TRACE_METHOD_NAME, DISPLAY_WORKSPACE_TRACE_COOKIE);
+            Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+                    DISPLAY_ALL_APPS_TRACE_COOKIE);
+        }
         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
                 TraceHelper.FLAG_UI_EVENT);
         if (DEBUG_STRICT_MODE) {
@@ -2584,6 +2598,7 @@
     }
 
     @Override
+    @TargetApi(Build.VERSION_CODES.S)
     public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
         mSynchronouslyBoundPages = boundPages;
         mPagesToBindSynchronously = new IntSet();
@@ -2606,6 +2621,10 @@
             executor.onLoadAnimationCompleted();
         }
         executor.attachTo(this);
+        if (Utilities.ATLEAST_S) {
+            Trace.endAsyncSection(DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
+                    DISPLAY_WORKSPACE_TRACE_COOKIE);
+        }
     }
 
     /**
@@ -2669,9 +2688,14 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     @Override
+    @TargetApi(Build.VERSION_CODES.S)
     public void bindAllApplications(AppInfo[] apps, int flags) {
         mAppsView.getAppsStore().setApps(apps, flags);
         PopupContainerWithArrow.dismissInvalidPopup(this);
+        if (Utilities.ATLEAST_S) {
+            Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+                    DISPLAY_ALL_APPS_TRACE_COOKIE);
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 86217d7..10023b4 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -138,10 +138,11 @@
         mContext = context;
 
         mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
-        mIconProvider =  new IconProvider(context, Themes.isThemedIconEnabled(context));
+        mIconProvider = new IconProvider(context, Themes.isThemedIconEnabled(context));
         mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
                 iconCacheFileName, mIconProvider);
-        mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
+        mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
+                iconCacheFileName != null);
         mOnTerminateCallback.add(mIconCache::close);
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 9ebec0a..f38f662 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -126,10 +126,12 @@
         }
     };
 
-    LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+    LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter,
+            boolean isPrimaryInstance) {
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
-        mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel);
+        mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
+                isPrimaryInstance);
     }
 
     public ModelDelegate getModelDelegate() {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 2f9b5af..a7198a8 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -108,6 +108,8 @@
     // relative scroll position unchanged in updateCurrentPageScroll. Cleared when snapping to a
     // page.
     protected int mCurrentPageScrollDiff;
+    // The current page the PagedView is scrolling over on it's way to the destination page.
+    protected int mCurrentScrollOverPage;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mNextPage = INVALID_PAGE;
@@ -180,6 +182,7 @@
 
         mScroller = new OverScroller(context, SCROLL);
         mCurrentPage = 0;
+        mCurrentScrollOverPage = 0;
 
         final ViewConfiguration configuration = ViewConfiguration.get(context);
         mTouchSlop = configuration.getScaledTouchSlop();
@@ -251,7 +254,7 @@
         }
         mOrientationHandler.set(this, VIEW_SCROLL_TO, newPosition);
         mScroller.startScroll(mScroller.getCurrX(), 0, newPosition - mScroller.getCurrX(), 0);
-        forceFinishScroller(true);
+        forceFinishScroller();
     }
 
     /**
@@ -273,14 +276,16 @@
         }
     }
 
-    private void forceFinishScroller(boolean resetNextPage) {
+    /**
+     * Immediately finishes any in-progress scroll, maintaining the current position. Also sets
+     * mNextPage = INVALID_PAGE and calls pageEndTransition().
+     */
+    public void forceFinishScroller() {
         mScroller.forceFinished(true);
         // We need to clean up the next page here to avoid computeScrollHelper from
         // updating current page on the pass.
-        if (resetNextPage) {
-            mNextPage = INVALID_PAGE;
-            pageEndTransition();
-        }
+        mNextPage = INVALID_PAGE;
+        pageEndTransition();
     }
 
     private int validateNewPage(int newPage) {
@@ -437,6 +442,7 @@
         }
         int prevPage = overridePrevPage != INVALID_PAGE ? overridePrevPage : mCurrentPage;
         mCurrentPage = validateNewPage(currentPage);
+        mCurrentScrollOverPage = mCurrentPage;
         updateCurrentPageScroll();
         notifyPageSwitchListener(prevPage);
         invalidate();
@@ -557,9 +563,11 @@
                 if (newPos < mMinScroll && oldPos >= mMinScroll) {
                     mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
                     mScroller.abortAnimation();
+                    onEdgeAbsorbingScroll();
                 } else if (newPos > mMaxScroll && oldPos <= mMaxScroll) {
                     mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
                     mScroller.abortAnimation();
+                    onEdgeAbsorbingScroll();
                 }
             }
 
@@ -577,6 +585,7 @@
             sendScrollAccessibilityEvent();
             int prevPage = mCurrentPage;
             mCurrentPage = validateNewPage(mNextPage);
+            mCurrentScrollOverPage = mCurrentPage;
             mNextPage = INVALID_PAGE;
             notifyPageSwitchListener(prevPage);
 
@@ -766,7 +775,8 @@
                 childStart += primaryDimension + getChildGap();
 
                 // This makes sure that the space is added after the page, not after each panel
-                if (i % panelCount == panelCount - 1) {
+                int lastPanel = mIsRtl ? 0 : panelCount - 1;
+                if (i % panelCount == lastPanel) {
                     childStart += mPageSpacing;
                 }
             }
@@ -837,6 +847,7 @@
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         mCurrentPage = validateNewPage(mCurrentPage);
+        mCurrentScrollOverPage = mCurrentPage;
         dispatchPageCountChanged();
     }
 
@@ -1408,6 +1419,20 @@
 
     protected void onNotSnappingToPageInFreeScroll() { }
 
+    /**
+     * Called when the view edges absorb part of the scroll. Subclasses can override this
+     * to provide custom behavior during animation.
+     */
+    protected void onEdgeAbsorbingScroll() {
+    }
+
+    /**
+     * Called when the current page closest to the center of the screen changes as part of the
+     * scroll. Subclasses can override this to provide custom behavior during scroll.
+     */
+    protected void onScrollOverPageChanged() {
+    }
+
     protected boolean shouldFlingForVelocity(int velocity) {
         float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
         return Math.abs(velocity) > threshold;
@@ -1553,7 +1578,7 @@
         return getDisplacementFromScreenCenter(childIndex, screenCenter);
     }
 
-    private int getScreenCenter(int primaryScroll) {
+    protected int getScreenCenter(int primaryScroll) {
         float primaryScale = mOrientationHandler.getPrimaryScale(this);
         float primaryPivot =  mOrientationHandler.getPrimaryValue(getPivotX(), getPivotY());
         int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
@@ -1692,6 +1717,15 @@
     }
 
     @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        int newDestinationPage = getDestinationPage();
+        if (newDestinationPage >= 0 && newDestinationPage != mCurrentScrollOverPage) {
+            mCurrentScrollOverPage = newDestinationPage;
+            onScrollOverPageChanged();
+        }
+    }
+
+    @Override
     public CharSequence getAccessibilityClassName() {
         // Some accessibility services have special logic for ScrollView. Since we provide same
         // accessibility info as ScrollView, inform the service to handle use the same way.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a21c8e3..54b2c96 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -338,15 +338,17 @@
         int paddingBottom = grid.cellLayoutBottomPaddingPx;
 
         int panelCount = getPanelCount();
+        int rightPanelModulus = mIsRtl ? 0 : panelCount - 1;
+        int leftPanelModulus = mIsRtl ? panelCount - 1 : 0;
         int numberOfScreens = mScreenOrder.size();
         for (int i = 0; i < numberOfScreens; i++) {
             int paddingLeft = paddingLeftRight;
             int paddingRight = paddingLeftRight;
             if (panelCount > 1) {
-                if (i % panelCount == 0) { // left side panel
+                if (i % panelCount == leftPanelModulus) {
                     paddingLeft = paddingLeftRight;
                     paddingRight = 0;
-                } else if (i % panelCount == panelCount - 1) { // right side panel
+                } else if (i % panelCount == rightPanelModulus) {
                     paddingLeft = 0;
                     paddingRight = paddingLeftRight;
                 } else { // middle panel
@@ -857,18 +859,13 @@
         mWorkspaceScreens.remove(emptyScreenId);
         mScreenOrder.removeValue(emptyScreenId);
 
-        int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
-                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
-
-        // When two panel home is enabled and the last page (the page on the right) doesn't
-        // have any items, then Launcher database doesn't know about this page because it was added
-        // by Launcher::bindAddScreens but wasn't inserted into the database. LauncherSettings's
-        // generate new screen ID method will return the ID for the left page,
-        // so we need to increment it.
-        if (isTwoPanelEnabled() && emptyScreenId == EXTRA_EMPTY_SCREEN_ID && newScreenId % 2 == 1) {
-            newScreenId++;
+        int newScreenId = -1;
+        // Launcher database isn't aware of empty pages that are already bound, so we need to
+        // skip those IDs manually.
+        while (newScreenId == -1 || mWorkspaceScreens.containsKey(newScreenId)) {
+            newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
         }
 
         mWorkspaceScreens.put(newScreenId, cl);
@@ -2453,21 +2450,32 @@
         }
 
         int nextPage = getNextPage();
-        if (layout == null && !isPageInTransition()) {
-            layout = verifyInsidePage(nextPage + (mIsRtl ? 1 : -1), Math.min(centerX, d.x), d.y);
+        IntSet pageIndexesToVerify = IntSet.wrap(nextPage - 1, nextPage + 1);
+        if (isTwoPanelEnabled()) {
+            // If two panel is enabled, users can also drag items to nextPage + 2
+            pageIndexesToVerify.add(nextPage + 2);
         }
 
-        if (layout == null && !isPageInTransition()) {
-            layout = verifyInsidePage(nextPage + (mIsRtl ? -1 : 1), Math.max(centerX, d.x), d.y);
+        int touchX = (int) Math.min(centerX, d.x);
+        int touchY = d.y;
+
+        // Go through the pages and check if the dragged item is inside one of them
+        for (int pageIndex : pageIndexesToVerify) {
+            if (layout != null || isPageInTransition()) {
+                break;
+            }
+            layout = verifyInsidePage(pageIndex, touchX, touchY);
         }
 
-        // If two panel is enabled, users can also drag items to currentPage + 2
-        if (isTwoPanelEnabled() && layout == null && !isPageInTransition()) {
-            layout = verifyInsidePage(nextPage + (mIsRtl ? -2 : 2), Math.max(centerX, d.x), d.y);
-        }
-
-        // Always pick the current page.
+        // If the dragged item isn't located in one of the pages above, the icon will stay on the
+        // current screen. For two panel pick the closest panel on the current screen,
+        // on one panel just choose the current page.
         if (layout == null && nextPage >= 0 && nextPage < getPageCount()) {
+            if (isTwoPanelEnabled()) {
+                nextPage = getScreenCenter(getScrollX()) > touchX
+                        ? (mIsRtl ? nextPage + 1 : nextPage) // left side
+                        : (mIsRtl ? nextPage : nextPage + 1); // right side
+            }
             layout = (CellLayout) getChildAt(nextPage);
         }
         if (layout != mDragTargetLayout) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 424ea22..ee5f7e4 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -623,6 +623,9 @@
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].padding.top = padding;
             mAH[i].applyPadding();
+            if (mAH[i].recyclerView != null) {
+                mAH[i].recyclerView.scrollToTop();
+            }
         }
         mHeaderTop = mHeader.getTop();
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a0551f0..a6d8552 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
@@ -28,6 +29,7 @@
 import android.animation.Animator.AnimatorListener;
 import android.animation.ObjectAnimator;
 import android.util.FloatProperty;
+import android.view.HapticFeedbackConstants;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -168,6 +170,11 @@
         builder.add(anim);
 
         setAlphas(toState, config, builder);
+
+        if (ALL_APPS.equals(toState) && mLauncher.isInState(NORMAL)) {
+            mLauncher.getAppsView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+        }
     }
 
     public Animator createSpringAnimation(float... progressValues) {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index 9bf6043..6ff2132 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -47,6 +47,8 @@
 
     /**
      * Scrolls the content vertically.
+     * @param scroll scrolled distance in pixels for active recyclerview.
+     * @param isScrolledOut bool to determine if row is scrolled out of view
      */
     void setVerticalScroll(int scroll, boolean isScrolledOut);
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index f091262..382f7a7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -147,6 +147,11 @@
     public static final BooleanFlag ENABLE_THEMED_ICONS = getDebugFlag(
             "ENABLE_THEMED_ICONS", true, "Enable themed icons on workspace");
 
+    public static final BooleanFlag ENABLE_BULK_WORKSPACE_ICON_LOADING = getDebugFlag(
+            "ENABLE_BULK_WORKSPACE_ICON_LOADING",
+            false,
+            "Enable loading workspace icons in bulk.");
+
     // Keep as DeviceFlag for remote disable in emergency.
     public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
             "ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 1a468ae..60d6e83 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -19,6 +19,8 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import static java.util.stream.Collectors.groupingBy;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -30,10 +32,15 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ShortcutInfo;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
 import android.graphics.drawable.Drawable;
 import android.os.Process;
+import android.os.Trace;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 
@@ -47,6 +54,7 @@
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -56,8 +64,13 @@
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.stream.Stream;
 
 /**
  * Cache of application icons.  Icons can be made from any thread.
@@ -306,6 +319,87 @@
         applyCacheEntry(entry, infoInOut);
     }
 
+    /**
+     * Creates an sql cursor for a query of a set of ItemInfoWithIcon icons and titles.
+     *
+     * @param iconRequestInfos List of IconRequestInfos representing titles and icons to query.
+     * @param user UserHandle all the given iconRequestInfos share
+     * @param useLowResIcons whether we should exclude the icon column from the sql results.
+     */
+    private <T extends ItemInfoWithIcon> Cursor createBulkQueryCursor(
+            List<IconRequestInfo<T>> iconRequestInfos, UserHandle user, boolean useLowResIcons)
+            throws SQLiteException {
+        String[] queryParams = Stream.concat(
+                iconRequestInfos.stream()
+                        .map(r -> r.itemInfo.getTargetComponent())
+                        .filter(Objects::nonNull)
+                        .distinct()
+                        .map(ComponentName::flattenToString),
+                Stream.of(Long.toString(getSerialNumberForUser(user)))).toArray(String[]::new);
+        String componentNameQuery = TextUtils.join(
+                ",", Collections.nCopies(queryParams.length - 1, "?"));
+
+        return mIconDb.query(
+                useLowResIcons ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
+                IconDB.COLUMN_COMPONENT
+                        + " IN ( " + componentNameQuery + " )"
+                        + " AND " + IconDB.COLUMN_USER + " = ?",
+                queryParams);
+    }
+
+    /**
+     * Load and fill icons requested in iconRequestInfos using a single bulk sql query.
+     */
+    public synchronized <T extends ItemInfoWithIcon> void getTitlesAndIconsInBulk(
+            List<IconRequestInfo<T>> iconRequestInfos) {
+        Map<Pair<UserHandle, Boolean>, List<IconRequestInfo<T>>> iconLoadSubsectionsMap =
+                iconRequestInfos.stream()
+                        .collect(groupingBy(iconRequest ->
+                                Pair.create(iconRequest.itemInfo.user, iconRequest.useLowResIcon)));
+
+        Trace.beginSection("loadIconsInBulk");
+        iconLoadSubsectionsMap.forEach((sectionKey, filteredList) -> {
+            Map<ComponentName, List<IconRequestInfo<T>>> duplicateIconRequestsMap =
+                    filteredList.stream()
+                            .collect(groupingBy(iconRequest ->
+                                    iconRequest.itemInfo.getTargetComponent()));
+
+            Trace.beginSection("loadIconSubsectionInBulk");
+            try (Cursor c = createBulkQueryCursor(
+                    filteredList,
+                    /* user = */ sectionKey.first,
+                    /* useLowResIcons = */ sectionKey.second)) {
+                int componentNameColumnIndex = c.getColumnIndexOrThrow(IconDB.COLUMN_COMPONENT);
+                while (c.moveToNext()) {
+                    ComponentName cn = ComponentName.unflattenFromString(
+                            c.getString(componentNameColumnIndex));
+                    List<IconRequestInfo<T>> duplicateIconRequests =
+                            duplicateIconRequestsMap.get(cn);
+
+                    if (cn != null) {
+                        CacheEntry entry = cacheLocked(
+                                cn,
+                                /* user = */ sectionKey.first,
+                                () -> duplicateIconRequests.get(0).launcherActivityInfo,
+                                mLauncherActivityInfoCachingLogic,
+                                c,
+                                /* usePackageIcon= */ false,
+                                /* useLowResIcons = */ sectionKey.second);
+
+                        for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
+                            applyCacheEntry(entry, iconRequest.itemInfo);
+                        }
+                    }
+                }
+            } catch (SQLiteException e) {
+                Log.d(TAG, "Error reading icon cache", e);
+            } finally {
+                Trace.endSection();
+            }
+        });
+        Trace.endSection();
+    }
+
 
     /**
      * Fill in {@param infoInOut} with the corresponding icon and label.
diff --git a/src/com/android/launcher3/logging/InstanceId.java b/src/com/android/launcher3/logging/InstanceId.java
index e720d75..3c4a644 100644
--- a/src/com/android/launcher3/logging/InstanceId.java
+++ b/src/com/android/launcher3/logging/InstanceId.java
@@ -36,10 +36,10 @@
  */
 public final class InstanceId implements Parcelable {
     // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
-    static final int INSTANCE_ID_MAX = 1 << 20;
+    public static final int INSTANCE_ID_MAX = 1 << 20;
 
     private final int mId;
-    InstanceId(int id) {
+    public InstanceId(int id) {
         mId = min(max(0, id), INSTANCE_ID_MAX);
     }
 
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 5ed651f..d987212 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * Handles the user event logging in R+.
@@ -53,6 +54,9 @@
     public static final int LAUNCHER_STATE_UNCHANGED = 5;
 
     private InstanceId mInstanceId;
+
+    protected @Nullable ActivityContext mActivityContext = null;
+
     /**
      * Returns event enum based on the two state transition information when swipe
      * gesture happens(to be removed during UserEventDispatcher cleanup).
@@ -281,6 +285,9 @@
         @UiEvent(doc = "User tapped on the share button on overview")
         LAUNCHER_OVERVIEW_ACTIONS_SHARE(582),
 
+        @UiEvent(doc = "User tapped on the split screen button on overview")
+        LAUNCHER_OVERVIEW_ACTIONS_SPLIT(895),
+
         @UiEvent(doc = "User tapped on the close button in select mode")
         LAUNCHER_SELECT_MODE_CLOSE(583),
 
@@ -505,7 +512,13 @@
         LAUNCHER_TURN_OFF_WORK_APPS_TAP(839),
 
         @UiEvent(doc = "Launcher item drop failed since there was not enough room on the screen.")
-        LAUNCHER_ITEM_DROP_FAILED_INSUFFICIENT_SPACE(872);
+        LAUNCHER_ITEM_DROP_FAILED_INSUFFICIENT_SPACE(872),
+
+        @UiEvent(doc = "User long pressed on the taskbar background to hide the taskbar")
+        LAUNCHER_TASKBAR_LONGPRESS_HIDE(896),
+
+        @UiEvent(doc = "User long pressed on the taskbar gesture handle to show the taskbar")
+        LAUNCHER_TASKBAR_LONGPRESS_SHOW(897);
 
         // ADD MORE
 
@@ -645,7 +658,7 @@
     public StatsLogger logger() {
         StatsLogger logger = createLogger();
         if (mInstanceId != null) {
-            return logger.withInstanceId(mInstanceId);
+            logger.withInstanceId(mInstanceId);
         }
         return logger;
     }
@@ -668,7 +681,9 @@
      * Creates a new instance of {@link StatsLogManager} based on provided context.
      */
     public static StatsLogManager newInstance(Context context) {
-        return Overrides.getObject(StatsLogManager.class,
+        StatsLogManager manager = Overrides.getObject(StatsLogManager.class,
                 context.getApplicationContext(), R.string.stats_log_manager_class);
+        manager.mActivityContext = ActivityContext.lookupContextNoThrow(context);
+        return manager;
     }
 }
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 761053d..0fc4c2d 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -25,10 +25,15 @@
 import android.content.SharedPreferences;
 import android.text.TextUtils;
 
+import androidx.annotation.IntDef;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.util.IntSet;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -41,13 +46,23 @@
     public static final String KEY_HOTSEAT_COUNT = "migration_src_hotseat_count";
     public static final String KEY_DEVICE_TYPE = "migration_src_device_type";
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
+    public @interface DeviceType{}
     public static final int TYPE_PHONE = 0;
     public static final int TYPE_MULTI_DISPLAY = 1;
     public static final int TYPE_TABLET = 2;
 
+    private static final IntSet COMPATIBLE_TYPES = IntSet.wrap(TYPE_PHONE, TYPE_MULTI_DISPLAY);
+
+    public static boolean deviceTypeCompatible(@DeviceType int typeA, @DeviceType int typeB) {
+        return typeA == typeB
+                || (COMPATIBLE_TYPES.contains(typeA) && COMPATIBLE_TYPES.contains(typeB));
+    }
+
     private final String mGridSizeString;
     private final int mNumHotseat;
-    private final int mDeviceType;
+    private final @DeviceType int mDeviceType;
 
     public DeviceGridState(InvariantDeviceProfile idp) {
         mGridSizeString = String.format(Locale.ENGLISH, "%d,%d", idp.numColumns, idp.numRows);
@@ -69,7 +84,7 @@
     /**
      * Returns the device type for the grid
      */
-    public int getDeviceType() {
+    public @DeviceType int getDeviceType() {
         return mDeviceType;
     }
 
@@ -104,12 +119,23 @@
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        DeviceGridState that = (DeviceGridState) o;
-        return mNumHotseat == that.mNumHotseat
-                && mDeviceType == that.mDeviceType
-                && Objects.equals(mGridSizeString, that.mGridSizeString);
+    public String toString() {
+        return "DeviceGridState{"
+                + "mGridSizeString='" + mGridSizeString + '\''
+                + ", mNumHotseat=" + mNumHotseat
+                + ", mDeviceType=" + mDeviceType
+                + '}';
+    }
+
+    /**
+     * Returns true if the database from another DeviceGridState can be loaded into the current
+     * DeviceGridState without migration, or false otherwise.
+     */
+    public boolean isCompatible(DeviceGridState other) {
+        if (this == other) return true;
+        if (other == null) return false;
+        return mNumHotseat == other.mNumHotseat
+                && deviceTypeCompatible(mDeviceType, other.mDeviceType)
+                && Objects.equals(mGridSizeString, other.mGridSizeString);
     }
 }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index e7d0749..fc86cf8 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -103,7 +103,13 @@
      * Check given a new IDP, if migration is necessary.
      */
     public static boolean needsToMigrate(Context context, InvariantDeviceProfile idp) {
-        return !new DeviceGridState(idp).equals(new DeviceGridState(context));
+        DeviceGridState idpGridState = new DeviceGridState(idp);
+        DeviceGridState contextGridState = new DeviceGridState(context);
+        boolean needsToMigrate = !idpGridState.isCompatible(contextGridState);
+        // TODO: Revert this change after b/200010396 is fixed
+        Log.d(TAG, "Migration is needed. idpGridState: " + idpGridState
+                + ", contextGridState: " + contextGridState);
+        return needsToMigrate;
     }
 
     /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 7e3bcee..8a5a9bf 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,13 +16,10 @@
 
 package com.android.launcher3.model;
 
-import static android.graphics.BitmapFactory.decodeByteArray;
-
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-import android.content.Intent.ShortcutIconResource;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
@@ -45,11 +42,10 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
@@ -184,32 +180,21 @@
      * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
      */
     protected boolean loadIcon(WorkspaceItemInfo info) {
-        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
-            if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
-                String packageName = getString(iconPackageIndex);
-                String resourceName = getString(iconResourceIndex);
-                if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
-                    info.iconResource = new ShortcutIconResource();
-                    info.iconResource.packageName = packageName;
-                    info.iconResource.resourceName = resourceName;
-                    BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
-                    if (iconInfo != null) {
-                        info.bitmap = iconInfo;
-                        return true;
-                    }
-                }
-            }
+        return createIconRequestInfo(info, false).loadWorkspaceIcon(mContext);
+    }
 
-            // Failed to load from resource, try loading from DB.
-            byte[] data = getBlob(iconIndex);
-            try {
-                info.bitmap = li.createIconBitmap(decodeByteArray(data, 0, data.length));
-                return true;
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to decode byte array for info " + info, e);
-                return false;
-            }
-        }
+    public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
+            WorkspaceItemInfo wai, boolean useLowResIcon) {
+        String packageName = itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+                ? getString(iconPackageIndex) : null;
+        String resourceName = itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+                ? getString(iconResourceIndex) : null;
+        byte[] iconBlob = itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+                || restoreFlag != 0
+                ? getBlob(iconIndex) : null;
+
+        return new IconRequestInfo<>(
+                wai, mActivityInfo, packageName, resourceName, iconBlob, useLowResIcon);
     }
 
     /**
@@ -262,6 +247,11 @@
      */
     public WorkspaceItemInfo getAppShortcutInfo(
             Intent intent, boolean allowMissingTarget, boolean useLowResIcon) {
+        return getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, true);
+    }
+
+    public WorkspaceItemInfo getAppShortcutInfo(
+            Intent intent, boolean allowMissingTarget, boolean useLowResIcon, boolean loadIcon) {
         if (user == null) {
             Log.d(TAG, "Null user found in getShortcutInfo");
             return null;
@@ -288,9 +278,11 @@
         info.user = user;
         info.intent = newIntent;
 
-        mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
-        if (mIconCache.isDefaultIcon(info.bitmap, user)) {
-            loadIcon(info);
+        if (loadIcon) {
+            mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
+            if (mIconCache.isDefaultIcon(info.bitmap, user)) {
+                loadIcon(info);
+            }
         }
 
         if (mActivityInfo != null) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index f4a0eb8..1249606 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -43,6 +43,7 @@
 import android.graphics.Point;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
@@ -71,6 +72,7 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -197,7 +199,12 @@
         TimingLogger logger = new TimingLogger(TAG, "run");
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
-            loadWorkspace(allShortcuts);
+            Trace.beginSection("LoadWorkspace");
+            try {
+                loadWorkspace(allShortcuts);
+            } finally {
+                Trace.endSection();
+            }
             logASplit(logger, "loadWorkspace");
 
             // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
@@ -225,7 +232,13 @@
             verifyNotStopped();
 
             // second step
-            List<LauncherActivityInfo> allActivityList = loadAllApps();
+            Trace.beginSection("LoadAllApps");
+            List<LauncherActivityInfo> allActivityList;
+            try {
+               allActivityList = loadAllApps();
+            } finally {
+                Trace.endSection();
+            }
             logASplit(logger, "loadAllApps");
 
             verifyNotStopped();
@@ -408,6 +421,7 @@
                 LauncherAppWidgetProviderInfo widgetProviderInfo;
                 Intent intent;
                 String targetPkg;
+                List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
 
                 while (!mStopped && c.moveToNext()) {
                     try {
@@ -530,7 +544,10 @@
                             } else if (c.itemType ==
                                     LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                 info = c.getAppShortcutInfo(
-                                        intent, allowMissingTarget, useLowResIcon);
+                                        intent,
+                                        allowMissingTarget,
+                                        useLowResIcon,
+                                        !FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get());
                             } else if (c.itemType ==
                                     LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
 
@@ -582,6 +599,8 @@
                             }
 
                             if (info != null) {
+                                iconRequestInfos.add(c.createIconRequestInfo(info, useLowResIcon));
+
                                 c.applyCommonProperties(info);
 
                                 info.intent = intent;
@@ -799,6 +818,21 @@
                         Log.e(TAG, "Desktop items loading interrupted", e);
                     }
                 }
+                if (FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get()) {
+                    Trace.beginSection("LoadWorkspaceIconsInBulk");
+                    try {
+                        mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
+                        for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo :
+                                iconRequestInfos) {
+                            WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
+                            if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
+                                iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
+                            }
+                        }
+                    } finally {
+                        Trace.endSection();
+                    }
+                }
             } finally {
                 IOUtils.closeSilently(c);
             }
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 13ec1ec..765141a 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -40,19 +40,21 @@
      * Creates and initializes a new instance of the delegate
      */
     public static ModelDelegate newInstance(
-            Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel) {
+            Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel,
+            boolean isPrimaryInstance) {
         ModelDelegate delegate = Overrides.getObject(
                 ModelDelegate.class, context, R.string.model_delegate_class);
-
         delegate.mApp = app;
         delegate.mAppsList = appsList;
         delegate.mDataModel = dataModel;
+        delegate.mIsPrimaryInstance = isPrimaryInstance;
         return delegate;
     }
 
     protected LauncherAppState mApp;
     protected AllAppsList mAppsList;
     protected BgDataModel mDataModel;
+    protected boolean mIsPrimaryInstance;
 
     public ModelDelegate() { }
 
diff --git a/src/com/android/launcher3/model/data/IconRequestInfo.java b/src/com/android/launcher3/model/data/IconRequestInfo.java
new file mode 100644
index 0000000..2f566f6
--- /dev/null
+++ b/src/com/android/launcher3/model/data/IconRequestInfo.java
@@ -0,0 +1,101 @@
+/*
+ * 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.model.data;
+
+import static android.graphics.BitmapFactory.decodeByteArray;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
+
+/**
+ * Class representing one request for an icon to be queried in a sql database.
+ *
+ * @param <T> ItemInfoWithIcon subclass whose title and icon can be loaded and filled by an sql
+ *           query.
+ */
+public class IconRequestInfo<T extends ItemInfoWithIcon> {
+
+    private static final String TAG = "IconRequestInfo";
+
+    @NonNull public final T itemInfo;
+    @Nullable public final LauncherActivityInfo launcherActivityInfo;
+    @Nullable public final String packageName;
+    @Nullable public final String resourceName;
+    @Nullable public final byte[] iconBlob;
+    public final boolean useLowResIcon;
+
+    public IconRequestInfo(
+            @NonNull T itemInfo,
+            @Nullable LauncherActivityInfo launcherActivityInfo,
+            @Nullable String packageName,
+            @Nullable String resourceName,
+            @Nullable byte[] iconBlob,
+            boolean useLowResIcon) {
+        this.itemInfo = itemInfo;
+        this.launcherActivityInfo = launcherActivityInfo;
+        this.packageName = packageName;
+        this.resourceName = resourceName;
+        this.iconBlob = iconBlob;
+        this.useLowResIcon = useLowResIcon;
+    }
+
+    /** Loads  */
+    public boolean loadWorkspaceIcon(Context context) {
+        if (!(itemInfo instanceof WorkspaceItemInfo)) {
+            throw new IllegalStateException(
+                    "loadWorkspaceIcon should only be use for a WorkspaceItemInfos: " + itemInfo);
+        }
+
+        try (LauncherIcons li = LauncherIcons.obtain(context)) {
+            WorkspaceItemInfo info = (WorkspaceItemInfo) itemInfo;
+            if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+                if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
+                    info.iconResource = new Intent.ShortcutIconResource();
+                    info.iconResource.packageName = packageName;
+                    info.iconResource.resourceName = resourceName;
+                    BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
+                    if (iconInfo != null) {
+                        info.bitmap = iconInfo;
+                        return true;
+                    }
+                }
+            }
+
+            // Failed to load from resource, try loading from DB.
+            try {
+                if (iconBlob == null) {
+                    return false;
+                }
+                info.bitmap = li.createIconBitmap(decodeByteArray(
+                        iconBlob, 0, iconBlob.length));
+                return true;
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to decode byte array for info " + info, e);
+                return false;
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 8d02a4a..d59429d 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -84,9 +84,10 @@
     }
 
     private static boolean performRestore(Context context, DatabaseHelper helper) {
-        if (new DeviceGridState(LauncherAppState.getIDP(context)).getDeviceType()
-                != Utilities.getPrefs(context).getInt(RESTORED_DEVICE_TYPE, TYPE_PHONE)) {
-            // DO not restore if the device types are different
+        if (!DeviceGridState.deviceTypeCompatible(
+                new DeviceGridState(LauncherAppState.getIDP(context)).getDeviceType(),
+                Utilities.getPrefs(context).getInt(RESTORED_DEVICE_TYPE, TYPE_PHONE))) {
+            // DO NOT restore if the device types are incompatible.
             return false;
         }
         SQLiteDatabase db = helper.getWritableDatabase();
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index 4d63218..b06b8a1 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -20,6 +20,7 @@
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
 
@@ -29,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
@@ -44,6 +46,7 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.EditText;
+import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -57,12 +60,15 @@
 import androidx.preference.SwitchPreference;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.FlagTogglerPrefUi;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.OnboardingPrefs;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -104,6 +110,7 @@
         initFlags();
         loadPluginPrefs();
         maybeAddSandboxCategory();
+        addOnboardingPrefsCatergory();
 
         if (getActivity() != null) {
             getActivity().setTitle("Developer Options");
@@ -153,6 +160,15 @@
             }
         });
 
+        if (getArguments() != null) {
+            String filter = getArguments().getString(EXTRA_FRAGMENT_ARG_KEY);
+            // Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given
+            // key. This is a slight variation where we instead filter by the human-readable titles.
+            if (filter != null) {
+                filterBox.setText(filter);
+            }
+        }
+
         View listView = getListView();
         final int bottomPadding = listView.getPaddingBottom();
         listView.setOnApplyWindowInsetsListener((v, insets) -> {
@@ -355,6 +371,28 @@
         sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
     }
 
+    private void addOnboardingPrefsCatergory() {
+        PreferenceCategory onboardingCategory = newCategory("Onboarding Flows");
+        onboardingCategory.setSummary("Reset these if you want to see the education again.");
+        for (Map.Entry<String, String[]> titleAndKeys : OnboardingPrefs.ALL_PREF_KEYS.entrySet()) {
+            String title = titleAndKeys.getKey();
+            String[] keys = titleAndKeys.getValue();
+            Preference onboardingPref = new Preference(getContext());
+            onboardingPref.setTitle(title);
+            onboardingPref.setSummary("Tap to reset");
+            onboardingPref.setOnPreferenceClickListener(preference -> {
+                SharedPreferences.Editor sharedPrefsEdit = Utilities.getPrefs(getContext()).edit();
+                for (String key : keys) {
+                    sharedPrefsEdit.remove(key);
+                }
+                sharedPrefsEdit.apply();
+                Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
+                return true;
+            });
+            onboardingCategory.addPreference(onboardingPref);
+        }
+    }
+
     private String toName(String action) {
         String str = action.replace("com.android.systemui.action.PLUGIN_", "")
                 .replace("com.android.launcher3.action.PLUGIN_", "");
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 24d3fd4..b34af97 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -18,7 +18,6 @@
 
 import static android.animation.ValueAnimator.areAnimatorsEnabled;
 
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
 
@@ -28,14 +27,12 @@
 import android.animation.AnimatorSet;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.Log;
 
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.testing.TestProtocol;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -256,9 +253,6 @@
         if (listener != null) {
             animation.addListener(listener);
         }
-        if (TestProtocol.sDebugTracing && state == NORMAL) {
-            Log.d(TestProtocol.L3_SWIPE_TO_HOME, "goToStateAnimated: " + state);
-        }
         mUiHandler.post(new StartAnimRunnable(animation));
     }
 
@@ -334,17 +328,11 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 // Change the internal state only when the transition actually starts
-                if (TestProtocol.sDebugTracing && state == NORMAL) {
-                    Log.d(TestProtocol.L3_SWIPE_TO_HOME, "onAnimationStart: " + state);
-                }
                 onStateTransitionStart(state);
             }
 
             @Override
             public void onAnimationSuccess(Animator animator) {
-                if (TestProtocol.sDebugTracing && state == NORMAL) {
-                    Log.d(TestProtocol.L3_SWIPE_TO_HOME, "onAnimationEnd: " + state);
-                }
                 onStateTransitionEnd(state);
             }
         };
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index ed52e20..3aecaa5 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -116,8 +116,6 @@
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
     public static final String WORK_PROFILE_REMOVED = "b/159671700";
-    public static final String FALLBACK_ACTIVITY_NO_SET = "b/181019015";
     public static final String TASK_VIEW_ID_CRASH = "b/195430732";
-    public static final String L3_SWIPE_TO_HOME = "b/192018189";
     public static final String NO_DROP_TARGET = "b/195031154";
 }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 895ca08..2ac6cea 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -408,22 +408,30 @@
     }
 
     @Override
-    public void setGroupedTaskViewThumbnailBounds(View mSnapshotView, View mSnapshotView2,
-            View taskParent, SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
-            DeviceProfile dp) {
+    public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
+            int parentWidth, int parentHeight,
+            SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-        int totalThumbnailHeight = taskParent.getHeight() - spaceAboveSnapshot;
-        int totalThumbnailWidth = taskParent.getWidth();
+        int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
         int dividerBar = splitBoundsConfig.visualDividerBounds.width();
-        ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
-        ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
+        int primarySnapshotHeight;
+        int primarySnapshotWidth;
+        int secondarySnapshotHeight;
+        int secondarySnapshotWidth;
 
-        primaryLp.width = totalThumbnailWidth;
-        primaryLp.height = (int) (totalThumbnailHeight * splitBoundsConfig.leftTaskPercent);
+        primarySnapshotWidth = parentWidth;
+        primarySnapshotHeight = (int) (totalThumbnailHeight * splitBoundsConfig.leftTaskPercent);
 
-        secondaryLp.width = totalThumbnailWidth;
-        secondaryLp.height = totalThumbnailHeight - primaryLp.height - dividerBar;
-        mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar);
+        secondarySnapshotWidth = parentWidth;
+        secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
+        secondarySnapshot.setTranslationY(primarySnapshotHeight + spaceAboveSnapshot + dividerBar);
+        primarySnapshot.measure(
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
+        secondarySnapshot.measure(
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
+                        View.MeasureSpec.EXACTLY));
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index b34a81e..b119e2c 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -162,7 +162,8 @@
             SplitConfigurationOptions.StagedSplitBounds splitInfo,
             @SplitConfigurationOptions.StagePosition int desiredStagePosition);
 
-    void setGroupedTaskViewThumbnailBounds(View mSnapshot1, View mSnapshot2, View taskParent,
+    void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
+            int parentWidth, int parentHeight,
             SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig, DeviceProfile dp);
 
     // Overview TaskMenuView methods
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 064d808..99ad050 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -35,7 +35,6 @@
 import android.view.Surface;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
@@ -503,43 +502,56 @@
             if (dp.isLandscape) {
                 splitOffset.x = splitInfo.leftTopBounds.width() +
                         splitInfo.visualDividerBounds.width();
+                splitOffset.y = 0;
             } else {
                 splitOffset.y = splitInfo.leftTopBounds.height() +
                         splitInfo.visualDividerBounds.height();
+                splitOffset.x = 0;
             }
         }
     }
 
     @Override
-    public void setGroupedTaskViewThumbnailBounds(View mSnapshotView, View mSnapshotView2,
-            View taskParent, SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
-            DeviceProfile dp) {
+    public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
+            int parentWidth, int parentHeight,
+            SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-        int totalThumbnailHeight = taskParent.getHeight() - spaceAboveSnapshot;
-        int totalThumbnailWidth = taskParent.getWidth();
-        int dividerBar = (dp.isLandscape ?
-                splitBoundsConfig.visualDividerBounds.width() :
-                splitBoundsConfig.visualDividerBounds.height());
-        ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
-        ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
-
+        int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
+        int dividerBar = (splitBoundsConfig.appsStackedVertically ?
+                splitBoundsConfig.visualDividerBounds.height() :
+                splitBoundsConfig.visualDividerBounds.width());
+        int primarySnapshotHeight;
+        int primarySnapshotWidth;
+        int secondarySnapshotHeight;
+        int secondarySnapshotWidth;
+        float taskPercent = splitBoundsConfig.appsStackedVertically ?
+                splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
         if (dp.isLandscape) {
-            primaryLp.height = totalThumbnailHeight;
-            primaryLp.width = (int) (totalThumbnailWidth * splitBoundsConfig.leftTaskPercent);
+            primarySnapshotHeight = totalThumbnailHeight;
+            primarySnapshotWidth = (int) (parentWidth * taskPercent);
 
-            secondaryLp.height = totalThumbnailHeight;
-            secondaryLp.width = totalThumbnailWidth - primaryLp.width - dividerBar;
-            mSnapshotView2.setTranslationX(primaryLp.width + dividerBar);
-            mSnapshotView2.setTranslationY(spaceAboveSnapshot);
+            secondarySnapshotHeight = totalThumbnailHeight;
+            secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
+            int translationX = primarySnapshotWidth + dividerBar;
+            secondarySnapshot.setTranslationX(translationX);
+            secondarySnapshot.setTranslationY(spaceAboveSnapshot);
         } else {
-            primaryLp.width = totalThumbnailWidth;
-            primaryLp.height = (int) (totalThumbnailHeight * splitBoundsConfig.topTaskPercent);
+            primarySnapshotWidth = parentWidth;
+            primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent);
 
-            secondaryLp.width = totalThumbnailWidth;
-            secondaryLp.height = totalThumbnailHeight - primaryLp.height - dividerBar;
-            mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar);
-            mSnapshotView2.setTranslationX(0);
+            secondarySnapshotWidth = parentWidth;
+            secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
+            int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar;
+            secondarySnapshot.setTranslationY(translationY);
+            secondarySnapshot.setTranslationX(0);
         }
+        primarySnapshot.measure(
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY));
+        secondarySnapshot.measure(
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight,
+                        View.MeasureSpec.EXACTLY));
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index cf1467a..5ba0d30 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -39,6 +39,14 @@
     public static final String SEARCH_EDU_SEEN = "launcher.search_edu_seen";
     public static final String SEARCH_SNACKBAR_COUNT = "launcher.keyboard_snackbar_count";
     public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen";
+    // When adding a new key, add it here as well, to be able to reset it from Developer Options.
+    public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
+            "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
+            "Hybrid Hotseat Education", new String[] { HOTSEAT_DISCOVERY_TIP_COUNT,
+                    HOTSEAT_LONGPRESS_TIP_SEEN },
+            "Search Education", new String[] { SEARCH_EDU_SEEN, SEARCH_SNACKBAR_COUNT },
+            "Taskbar Education", new String[] { TASKBAR_EDU_SEEN }
+    );
 
     /**
      * Events that either have happened or have not (booleans).
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 5093d85..0b083e3 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -93,7 +93,13 @@
         // This class is orientation-agnostic, so we compute both for later use
         public final float topTaskPercent;
         public final float leftTaskPercent;
-
+        /**
+         * If {@code true}, that means at the time of creation of this object, the
+         * split-screened apps were vertically stacked. This is useful in scenarios like
+         * rotation where the bounds won't change, but this variable can indicate what orientation
+         * the bounds were originally in
+         */
+        public final boolean appsStackedVertically;
 
         public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds) {
             this.leftTopBounds = leftTopBounds;
@@ -103,10 +109,12 @@
                 // vertical apps, horizontal divider
                 this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom,
                         leftTopBounds.right, rightBottomBounds.top);
+                appsStackedVertically = true;
             } else {
                 // horizontal apps, vertical divider
                 this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top,
                         rightBottomBounds.left, leftTopBounds.bottom);
+                appsStackedVertically = false;
             }
 
             leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index dc5fe06..ebcd379 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ViewCache;
@@ -122,15 +123,33 @@
     }
 
     /**
-     * Returns the ActivityContext associated with the given Context.
+     * Called just before logging the given item.
+     */
+    default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { }
+
+    /**
+     * Returns the ActivityContext associated with the given Context, or throws an exception if
+     * the Context is not associated with any ActivityContext.
      */
     static <T extends Context & ActivityContext> T lookupContext(Context context) {
+        T activityContext = lookupContextNoThrow(context);
+        if (activityContext == null) {
+            throw new IllegalArgumentException("Cannot find ActivityContext in parent tree");
+        }
+        return activityContext;
+    }
+
+    /**
+     * Returns the ActivityContext associated with the given Context, or null if
+     * the Context is not associated with any ActivityContext.
+     */
+    static <T extends Context & ActivityContext> T lookupContextNoThrow(Context context) {
         if (context instanceof ActivityContext) {
             return (T) context;
         } else if (context instanceof ContextWrapper) {
-            return lookupContext(((ContextWrapper) context).getBaseContext());
+            return lookupContextNoThrow(((ContextWrapper) context).getBaseContext());
         } else {
-            throw new IllegalArgumentException("Cannot find ActivityContext in parent tree");
+            return null;
         }
     }
 }
diff --git a/src_plugins/com/android/systemui/plugins/OneSearch.java b/src_plugins/com/android/systemui/plugins/OneSearch.java
index 6d57c19..8bd0b75 100644
--- a/src_plugins/com/android/systemui/plugins/OneSearch.java
+++ b/src_plugins/com/android/systemui/plugins/OneSearch.java
@@ -16,8 +16,7 @@
 
 package com.android.systemui.plugins;
 
-import android.graphics.Bitmap;
-import android.text.Spanned;
+import android.os.Parcelable;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
@@ -29,7 +28,7 @@
 @ProvidesInterface(action = OneSearch.ACTION, version = OneSearch.VERSION)
 public interface OneSearch extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_ONE_SEARCH";
-    int VERSION = 2;
+    int VERSION = 3;
 
     /**
      * Get the content provider warmed up.
@@ -37,23 +36,8 @@
     void warmUp();
 
     /**
-     * Get the suggests for the query.
+     * Get the suggest search target list for the query.
      * @param query The query to get the search suggests for.
      */
-    ArrayList<Spanned> getSuggests(String query);
-
-    /**
-     * Get the image bitmap for the suggest.
-     * @param suggest The suggest to get the image bitmap for.
-     */
-    Bitmap getImageBitmap(Spanned suggest);
-
-    /**
-     * Get the subtitle for the suggest.
-     * @param suggest The suggest to get the subtitle for.
-     */
-    String getSubtitle(Spanned suggest);
-
-    /** Clear any cached data or storage used in search. */
-    void clear();
+    ArrayList<Parcelable> getSuggests(Parcelable query);
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 1a6ce8c..cd0c7f2 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -153,8 +153,6 @@
     public static String dumpHprofData() {
         String result;
         if (sDumpWasGenerated) {
-            Log.d("b/195319692", "dump has already been generated by another test",
-                    new Exception());
             result = "dump has already been generated by another test";
         } else {
             try {
@@ -169,7 +167,6 @@
                             "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
                 }
                 sDumpWasGenerated = true;
-                Log.d("b/195319692", "sDumpWasGenerated := true", new Exception());
                 result = "memory dump filename: " + fileName;
             } catch (Throwable e) {
                 Log.e(TAG, "dumpHprofData failed", e);
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index f9a9997..65aaa24 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -2,6 +2,7 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import android.content.Context;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.util.Log;
@@ -45,11 +46,30 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
+                boolean success = false;
                 try {
                     Log.d("b/196820244", "Before evaluate");
+                    mDevice.executeShellCommand("cmd statusbar tracing start");
                     FailureWatcher.super.apply(base, description).evaluate();
                     Log.d("b/196820244", "After evaluate");
+                    success = true;
                 } finally {
+                    // Save artifact for Launcher Winscope trace.
+                    mDevice.executeShellCommand("cmd statusbar tracing stop");
+                    final Context nexusLauncherContext =
+                            getInstrumentation().getTargetContext()
+                                    .createPackageContext("com.google.android.apps.nexuslauncher",
+                                            0);
+                    final File launcherTrace =
+                            new File(nexusLauncherContext.getFilesDir(), "launcher_trace.pb");
+                    if (success) {
+                        mDevice.executeShellCommand("rm " + launcherTrace);
+                    } else {
+                        mDevice.executeShellCommand("mv " + launcherTrace + " "
+                                + diagFile(description, "LauncherWinscope", "pb"));
+                    }
+
+                    // Detect touch events coming from physical screen.
                     if (mLauncher.hadNontestEvents()) {
                         throw new AssertionError(
                                 "Launcher received events not sent by the test. This may mean "
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 98d081b..ea8a295 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1559,10 +1559,12 @@
     }
 
     float getWindowCornerRadius() {
+        // TODO(b/197326121): Check if the touch is overlapping with the corners by offsetting
+        final float tmpBuffer = 100f;
         final Resources resources = getResources();
         if (!supportsRoundedCornersOnWindows(resources)) {
             Log.d(TAG, "No rounded corners");
-            return 0f;
+            return tmpBuffer;
         }
 
         // Radius that should be used in case top or bottom aren't defined.
@@ -1581,7 +1583,7 @@
         // Always use the smallest radius to make sure the rounded corners will
         // completely cover the display.
         Log.d(TAG, "Rounded corners top: " + topRadius + " bottom: " + bottomRadius);
-        return Math.max(topRadius, bottomRadius);
+        return Math.max(topRadius, bottomRadius) + tmpBuffer;
     }
 
     private static boolean supportsRoundedCornersOnWindows(Resources resources) {